feat(cli): mentions target user
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
### Fixed
|
||||
- CLI `--version`: read version from `package.json`/`VERSION` (no hardcoded string) + append git sha when available.
|
||||
|
||||
### Changed
|
||||
- `mentions`: no hardcoded user; defaults to authenticated user or accepts `--user @handle`.
|
||||
|
||||
## 0.1.1 — 2025-12-26
|
||||
|
||||
### Changed
|
||||
|
||||
+50
-5
@@ -19,6 +19,7 @@ import { resolveCliInvocation } from './lib/cli-args.js';
|
||||
import { resolveCredentials } from './lib/cookies.js';
|
||||
import { type EngineMode, resolveEngineMode, shouldUseSweetistics } from './lib/engine.js';
|
||||
import { extractTweetId } from './lib/extract-tweet-id.js';
|
||||
import { normalizeHandle } from './lib/normalize-handle.js';
|
||||
import { SweetisticsClient } from './lib/sweetistics-client.js';
|
||||
import { type TweetData, TwitterClient } from './lib/twitter-client.js';
|
||||
import { getCliVersion } from './lib/version.js';
|
||||
@@ -804,10 +805,11 @@ program
|
||||
// Mentions command - shortcut to search for @username mentions
|
||||
program
|
||||
.command('mentions')
|
||||
.description('Find tweets mentioning @clawdbot')
|
||||
.description('Find tweets mentioning a user (defaults to current user)')
|
||||
.option('-u, --user <handle>', 'User handle (e.g. @steipete)')
|
||||
.option('-n, --count <number>', 'Number of tweets to fetch', '10')
|
||||
.option('--json', 'Output as JSON')
|
||||
.action(async (cmdOpts: { count?: string; json?: boolean }) => {
|
||||
.action(async (cmdOpts: { user?: string; count?: string; json?: boolean }) => {
|
||||
const opts = program.opts();
|
||||
const timeoutMs = resolveTimeoutFromOptions(opts);
|
||||
const count = Number.parseInt(cmdOpts.count || '10', 10);
|
||||
@@ -815,6 +817,9 @@ program
|
||||
const engine = resolveEngineMode(opts.engine);
|
||||
const useSweetistics = shouldUseSweetistics(engine, Boolean(sweetistics.apiKey));
|
||||
|
||||
const explicitHandle = normalizeHandle(cmdOpts.user);
|
||||
let query: string | null = explicitHandle ? `@${explicitHandle}` : null;
|
||||
|
||||
if (useSweetistics) {
|
||||
if (!sweetistics.apiKey) {
|
||||
console.error('❌ Sweetistics engine selected but no API key provided.');
|
||||
@@ -825,7 +830,18 @@ program
|
||||
apiKey: sweetistics.apiKey,
|
||||
timeoutMs,
|
||||
});
|
||||
const result = await client.search('@clawdbot', count);
|
||||
|
||||
if (!query) {
|
||||
const who = await client.getCurrentUser();
|
||||
const handle = normalizeHandle(who.user?.username);
|
||||
if (!handle) {
|
||||
console.error(`❌ Could not determine current user (${who.error ?? 'Unknown error'}). Use --user <handle>.`);
|
||||
process.exit(1);
|
||||
}
|
||||
query = `@${handle}`;
|
||||
}
|
||||
|
||||
const result = await client.search(query, count);
|
||||
if (result.success && result.tweets) {
|
||||
printTweets(result.tweets, { json: cmdOpts.json, emptyMessage: 'No mentions found.' });
|
||||
return;
|
||||
@@ -850,7 +866,36 @@ program
|
||||
}
|
||||
|
||||
const client = new TwitterClient({ cookies, timeoutMs });
|
||||
const result = await client.search('@clawdbot', count);
|
||||
|
||||
if (!query) {
|
||||
const who = await client.getCurrentUser();
|
||||
const handle = normalizeHandle(who.user?.username);
|
||||
if (!handle) {
|
||||
if (sweetistics.apiKey) {
|
||||
console.error(
|
||||
`⚠️ Could not determine current user (${who.error ?? 'Unknown error'}); trying Sweetistics fallback...`,
|
||||
);
|
||||
const fallbackWho = await new SweetisticsClient({
|
||||
baseUrl: sweetistics.baseUrl,
|
||||
apiKey: sweetistics.apiKey,
|
||||
timeoutMs,
|
||||
}).getCurrentUser();
|
||||
const fallbackHandle = normalizeHandle(fallbackWho.user?.username);
|
||||
if (!fallbackHandle) {
|
||||
console.error(`❌ Could not determine current user. Use --user <handle>.`);
|
||||
process.exit(1);
|
||||
}
|
||||
query = `@${fallbackHandle}`;
|
||||
} else {
|
||||
console.error(`❌ Could not determine current user (${who.error ?? 'Unknown error'}). Use --user <handle>.`);
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
query = `@${handle}`;
|
||||
}
|
||||
}
|
||||
|
||||
const result = await client.search(query, count);
|
||||
|
||||
if (result.success && result.tweets) {
|
||||
printTweets(result.tweets, { json: cmdOpts.json, emptyMessage: 'No mentions found.' });
|
||||
@@ -860,7 +905,7 @@ program
|
||||
baseUrl: sweetistics.baseUrl,
|
||||
apiKey: sweetistics.apiKey,
|
||||
timeoutMs,
|
||||
}).search('@clawdbot', count);
|
||||
}).search(query, count);
|
||||
if (fallback.success && fallback.tweets) {
|
||||
printTweets(fallback.tweets, { json: cmdOpts.json, emptyMessage: 'No mentions found.' });
|
||||
} else {
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
export function normalizeHandle(input?: string | null): string | null {
|
||||
const raw = (input ?? '').trim();
|
||||
if (!raw) return null;
|
||||
|
||||
const withoutAt = raw.startsWith('@') ? raw.slice(1) : raw;
|
||||
const handle = withoutAt.trim();
|
||||
if (!handle) return null;
|
||||
|
||||
// X/Twitter handles are traditionally max 15 chars; keep strict to avoid surprising queries.
|
||||
if (!/^[A-Za-z0-9_]{1,15}$/.test(handle)) return null;
|
||||
|
||||
return handle;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { normalizeHandle } from '../src/lib/normalize-handle.js';
|
||||
|
||||
describe('normalizeHandle', () => {
|
||||
it('accepts bare handle', () => {
|
||||
expect(normalizeHandle('steipete')).toBe('steipete');
|
||||
});
|
||||
|
||||
it('accepts @handle and trims whitespace', () => {
|
||||
expect(normalizeHandle(' @steipete ')).toBe('steipete');
|
||||
});
|
||||
|
||||
it('rejects empty input', () => {
|
||||
expect(normalizeHandle('')).toBeNull();
|
||||
expect(normalizeHandle(' ')).toBeNull();
|
||||
expect(normalizeHandle(null)).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects invalid characters and too-long handles', () => {
|
||||
expect(normalizeHandle('@stei-pete')).toBeNull();
|
||||
expect(normalizeHandle('@steipete!')).toBeNull();
|
||||
expect(normalizeHandle('a'.repeat(16))).toBeNull();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user