mirror of
https://github.com/typefully/agent-skills.git
synced 2026-09-14 20:50:23 +08:00
2b48317e44
## TL;DR Broken or expired Typefully API keys now produce a clear authentication failure instead of a generic `HTTP 401` or a softened setup warning. The skill instructions also tell agents to stop and ask the user to refresh setup, rather than falling back to browser, web UI, or localhost access. ## Root Cause The CLI only had hard-stop handling for missing API keys. Invalid keys came back as generic 401 API errors, and setup/default-social-set paths wrapped those errors as softer fetch/access failures, leaving agents room to improvise another access path. ## Summary - Fix **401 auth handling** by adding a shared authentication failure message and details for Typefully API 401 responses. - Fix **setup and default social set flows** so invalid keys also hard-stop with setup guidance instead of being wrapped as generic fetch/access failures. - Update **agent-facing docs and changelog** to cover missing, invalid, and expired keys, and explicitly forbid web UI/browser/localhost fallback for draft access. - Clarify **X Article Markdown review guidance** so agents do not flag Typefully-normalized bold/link Markdown as broken when the rendered article is correct. - Clean up **dated changelog sections** so shipped Typefully skill entries use plain date headings instead of `Unreleased` or bracketed dates. - Add **regression coverage** for direct API calls, setup default validation, setup social-set discovery, and default social set configuration. ## Validation - [x] `git diff --check` - [x] `npm run test:common` - [x] `npm run test:config` - [x] `npm test` ## After Merge - Users with an installed copy of the Typefully skill should update it through their normal skills.sh / Claude plugin flow to receive the new behavior.
112 lines
3.8 KiB
JavaScript
112 lines
3.8 KiB
JavaScript
const {
|
|
describe,
|
|
it,
|
|
assert,
|
|
withCliHarness,
|
|
authAssertFactory,
|
|
expectCliOk,
|
|
expectCliError,
|
|
parseJsonOrNull,
|
|
} = require('./typefully-cli.test-helpers');
|
|
|
|
describe('argument parsing', () => {
|
|
it('errors cleanly when a value-taking alias is missing its value', withCliHarness(async ({ server, run }) => {
|
|
const result = await run(['create-draft', 'hi', '--social-set-id', '123', '--tags', '--share']);
|
|
|
|
expectCliError(result, { error: '--tags requires a value' });
|
|
assert.equal(server.requests.length, 0);
|
|
}));
|
|
|
|
it('errors cleanly when --social-set-id is missing a value', withCliHarness(async ({ server, run }) => {
|
|
const result = await run(['drafts:list', '--social-set-id']);
|
|
|
|
expectCliError(result, { error: '--social-set-id (or --social_set_id) requires a value' });
|
|
assert.equal(server.requests.length, 0);
|
|
}));
|
|
|
|
it('errors when positional social_set_id conflicts with --social-set-id', withCliHarness(async ({ server, run }) => {
|
|
const result = await run(['drafts:list', '111', '--social-set-id', '222']);
|
|
|
|
expectCliError(result, {
|
|
error: 'Conflicting social_set_id values',
|
|
positional: '111',
|
|
flag: '222',
|
|
});
|
|
assert.equal(server.requests.length, 0);
|
|
}));
|
|
});
|
|
|
|
describe('help', () => {
|
|
it('prints usage for help command', withCliHarness(async ({ run }) => {
|
|
const result = await run(['help'], { env: { TYPEFULLY_API_KEY: '' } });
|
|
|
|
expectCliOk(result);
|
|
assert.ok(result.stdout.includes('Typefully CLI - Manage social media posts'));
|
|
assert.ok(result.stdout.includes('USAGE:'));
|
|
}));
|
|
|
|
it('supports --help and -h aliases', withCliHarness(async ({ run }) => {
|
|
const longResult = await run(['--help']);
|
|
expectCliOk(longResult);
|
|
assert.ok(longResult.stdout.includes('Typefully CLI'));
|
|
|
|
const shortResult = await run(['-h']);
|
|
expectCliOk(shortResult);
|
|
assert.ok(shortResult.stdout.includes('Typefully CLI'));
|
|
}));
|
|
});
|
|
|
|
describe('global flag behavior', () => {
|
|
it('supports --api-base-url and appends /v2 when omitted', withCliHarness(async ({ server, apiKey, baseUrl, run }) => {
|
|
const rootUrl = baseUrl.replace(/\/v2$/, '');
|
|
server.expect('GET', '/v2/social-sets/9/tags', {
|
|
assert: (req) => {
|
|
authAssertFactory(apiKey)(req);
|
|
assert.equal(req.search, '?limit=50');
|
|
},
|
|
json: { results: [] },
|
|
});
|
|
|
|
const result = await run(['--api-base-url', rootUrl, 'tags:list', '--social-set-id', '9'], {
|
|
env: { TYPEFULLY_API_BASE: 'https://example.invalid/v2' },
|
|
});
|
|
|
|
expectCliOk(result, { results: [] });
|
|
}));
|
|
|
|
it('allows tags:list with --social-set-id and no positional social set', withCliHarness(async ({ server, apiKey, run }) => {
|
|
server.expect('GET', '/v2/social-sets/9/tags', {
|
|
assert: (req) => {
|
|
authAssertFactory(apiKey)(req);
|
|
assert.equal(req.search, '?limit=50');
|
|
},
|
|
json: { results: [] },
|
|
});
|
|
|
|
const result = await run(['tags:list', '--social-set-id', '9']);
|
|
expectCliOk(result, { results: [] });
|
|
}));
|
|
});
|
|
|
|
describe('api errors', () => {
|
|
it('explains 401 responses as authentication failures', withCliHarness(async ({ server, apiKey, run }) => {
|
|
server.expect('GET', '/v2/me', {
|
|
assert: authAssertFactory(apiKey),
|
|
status: 401,
|
|
json: { error: 'Invalid token' },
|
|
});
|
|
|
|
const result = await run(['me:get']);
|
|
|
|
expectCliError(result);
|
|
const out = parseJsonOrNull(result.stdout);
|
|
assert.equal(
|
|
out.error,
|
|
`Authentication failed: Typefully API key is invalid, expired, or lacks access. Run 'typefully.js setup' to configure a valid key.`,
|
|
);
|
|
assert.equal(out.action, 'Run: typefully.js setup');
|
|
assert.equal(out.api_key_url, 'https://typefully.com/?settings=api');
|
|
assert.deepEqual(out.response, { error: 'Invalid token' });
|
|
}));
|
|
});
|