From 104-job-auto-apply
Automate job applications on 104.com.tw using Playwright MCP tools. Features target-based stopping, keyboard controls (P/R/Q), on-page status indicator, and proven 92.5% success rate over 2,495 applications.
npx claudepluginhub yennanliu/104skill --plugin 104-job-auto-applyThis skill uses the workspace's default tool permissions.
**Proven at Scale**: 2,495 successful applications | 92.5% success rate | 609 apps/hour
Mandates invoking relevant skills via tools before any response in coding sessions. Covers access, priorities, and adaptations for Claude Code, Copilot CLI, Gemini CLI.
Share bugs, ideas, or general feedback.
Proven at Scale: 2,495 successful applications | 92.5% success rate | 609 apps/hour
This skill enables automated job applications on 104.com.tw using Playwright MCP browser automation tools with intelligent target-based execution and real-time keyboard controls.
Use this skill when:
This skill includes ready-to-use JavaScript files:
autoApply104Jobs.js - Main automation with target-based execution and keyboard controls (~340 lines)applySingleJob.js - Helper functions for single job application and listing (~120 lines)README.md - Script documentation and usage guideSimply copy/paste the functions into your Playwright MCP code block to use them.
Before using this skill, ensure:
claude
# Add to marketplace
/plugin marketplace add yennanliu/104Skill
# Install the skill
/plugin install 104-job-auto-apply
# Use the skill
/104-job-auto-apply
# Run the installer (installs to both Claude Code and Gemini CLI paths)
git clone https://github.com/yennanliu/104Skill.git
cd 104Skill
./install.sh
# The skill is installed to ~/.gemini/skills/104-job-auto-apply/
# In Gemini CLI, ask it to read the SKILL.md and follow the instructions
# gemini --skill 104-job-auto-apply
For testing local modifications:
# Add local marketplace (Claude Code)
/plugin marketplace add /path/to/104Skill
# Install from local source
/plugin install 104-job-auto-apply@local
Use this for testing or applying to one job at a time. Perfect for verifying the automation works before running batch jobs.
Implementation:
Helper functions are available in applySingleJob.js.
Quick Usage:
// Copy/paste the functions from applySingleJob.js, then:
// Step 1: Navigate to search page
await page.goto('https://www.104.com.tw/jobs/search/?area=6001001000,6001002000&keyword=%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB&order=15&remoteWork=1,2&page=1');
await page.waitForTimeout(2000);
// Step 2: List available jobs
const jobs = await listJobs(page);
console.log('Available jobs:', jobs);
// Output: [{ index: 0, title: "軟體工程師", company: "...", alreadyApplied: false }, ...]
// Step 3: Apply to first job (index 0)
const result = await applySingleJob(page, 0, '自訂推薦信1');
console.log(result);
// Output: { status: 'success', job: {...}, finalUrl: '...' }
// Step 4: Apply to another job (index 2)
const result2 = await applySingleJob(page, 2, '自訂推薦信1');
Returns:
{
status: 'success' | 'failed' | 'skipped' | 'error',
job: {
title: "軟體工程師",
company: "某科技公司",
alreadyApplied: false
},
finalUrl: "https://www.104.com.tw/job/apply/done/..."
}
💡 Tip: Always test with Mode 1 (single job) before running batch automation to verify your cover letter name is correct and the flow works.
Use this for applying to jobs with real-time keyboard controls and target-based stopping.
Features:
Configuration:
const options = {
startPage: 1, // Starting page number
targetApplications: 20, // Target number of successful applications
maxPages: 20, // Maximum number of pages to search
coverLetter: '自訂推薦信1' // Cover letter name (must match exactly)
};
Implementation:
The complete automation function is available in autoApply104Jobs.js.
Quick Usage:
// Copy/paste the function from autoApply104Jobs.js, then run:
// Basic: Apply to 20 jobs
await autoApply104Jobs(page, { targetApplications: 20 });
// Advanced: Custom configuration
await autoApply104Jobs(page, {
startPage: 1, // Start from page 1
targetApplications: 50, // Target 50 applications
maxPages: 30, // Search up to 30 pages
coverLetter: '自訂推薦信1' // Your cover letter name
});
// While running, use keyboard controls:
// - Press P to pause
// - Press R to resume
// - Press Q to quit gracefully
Returns:
{
successful: 15, // Number of successful applications
failed: 2, // Number of failed applications
skipped: 10, // Number of skipped (already applied)
pages: [ // Per-page details
{ pageNumber: 1, successful: 8, failed: 1, skipped: 5 },
{ pageNumber: 2, successful: 7, failed: 1, skipped: 5 }
]
}
💡 Tip: For full implementation details, see
autoApply104Jobs.js. The script is ~340 lines with comprehensive error handling, keyboard controls, and status indicator setup.
| Option | Type | Default | Description |
|---|---|---|---|
startPage | number | 2 | Starting page number for job search (page 2 avoids sponsored results) |
targetApplications | number | 20 | Target number of successful applications (stops when reached) |
maxPages | number | 20 | Maximum number of pages to search |
coverLetter | string | '自訂推薦信1' | Cover letter name (must match exactly in your 104 account) |
// Apply buttons on search results page
'.apply-button__button' // ✅ CORRECT - These are DIV elements, not buttons
// Job containers
'[class*="job-list-container"]' // Contains job info
// Cover letter dropdown
'span' with text '系統預設' // Click parent element to open
// Cover letter options
'.multiselect__option' // Options in dropdown
// Submit button
'button' with text '確認送出' // Final submit
page.context().pages()/job/apply/done/// After navigation
await page.waitForTimeout(2000); // Let page load
// After clicking apply button
await page.waitForTimeout(1000); // Let new tab open
// After switching to new tab
await newTab.waitForTimeout(1000); // Let form load
// After opening dropdown
await newTab.waitForTimeout(500); // Let dropdown render
// After selecting cover letter
await newTab.waitForTimeout(500); // Let selection register
// After submit
await newTab.waitForTimeout(2000); // Let success page load
// Between jobs (human-like)
const delay = 2000 + Math.random() * 2000; // 2-4 seconds random
// Between pages
await page.waitForTimeout(5000); // Avoid rate limiting
// ✅ RELIABLE - URL pattern matching
const finalUrl = newTab.url();
if (finalUrl.includes('/job/apply/done/')) {
// Confirmed success
}
// ❌ UNRELIABLE - Don't rely on page text
// Page text can be inconsistent or change
Solution: Verify user is logged in and page has fully loaded
Solution: Verify exact cover letter name in 104.com.tw account settings
Solutions:
Solutions:
Important: This tool is for educational and personal productivity purposes only.
Apply to 20 jobs starting from page 2 (skips sponsored/featured results), with keyboard controls available.
// Most common use case - apply to 20 jobs (startPage defaults to 2)
await autoApply104Jobs(page, {
targetApplications: 20
});
// While running:
// - Press P to pause (take a break)
// - Press R to resume
// - Press Q to quit gracefully
Target more applications across more pages.
// Apply to 50 jobs, search up to 30 pages
await autoApply104Jobs(page, {
startPage: 1,
targetApplications: 50,
maxPages: 30
});
If you've already processed pages 1-5, start from page 6.
// Continue from page 6
await autoApply104Jobs(page, {
startPage: 6,
targetApplications: 20,
maxPages: 20
});
Use a different cover letter than the default.
// Use custom cover letter (must match name in 104 account)
await autoApply104Jobs(page, {
targetApplications: 20,
coverLetter: '我的客製推薦信' // Your cover letter name
});
Test the automation with Mode 1 (single job) first before batch processing.
// Navigate to search page
await page.goto('https://www.104.com.tw/jobs/search/?area=6001001000,6001002000&keyword=%20%20%20%20%E8%BB%9F%E9%AB%94%E5%B7%A5%E7%A8%8B%E5%B8%AB&order=15&remoteWork=1,2&page=1');
await page.waitForTimeout(2000);
// List available jobs
const jobCount = await page.evaluate(() => {
return document.querySelectorAll('.apply-button__button').length;
});
console.log(`Found ${jobCount} jobs on this page`);
// Apply to first job (index 0) using Mode 1 code above
// ... then run batch automation if successful
This skill is based on real-world proven results: 2,495 successful job applications with 92.5% success rate.
Target-Based Execution ⭐
Keyboard Controls ⭐
Search Page Automation ⭐
Proper Tab Management ⭐
On-Page Status Indicator ⭐
URL-Based Success Verification ⭐
/job/apply/done/ in URLBased on actual 2,495-application case study:
Q: How do I know if the automation is working? A: Watch for the on-page status indicator (green box in top-right corner) and console output showing "✅ SUCCESS" messages. Run a dry-run or test with 3 jobs first.
Q: Can I pause the automation? A: Yes! Press P to pause, R to resume, Q to quit gracefully at any time during execution.
Q: What if I get rate limited? A: Based on 2,495-application case study, no rate limiting was detected up to 900 applications in a single session. The automation uses human-like delays (2-4 seconds) to avoid detection.
Q: How long does it take to apply to 20 jobs? A: Approximately 2-3 minutes. Based on case study data: ~6 seconds per job × 20 jobs = 120 seconds, plus page navigation time.
Q: Can I run this overnight? A: Not recommended. The automation requires an active browser session and may encounter errors that need attention. Best run in 20-50 job batches while monitoring.
Q: How do I find my cover letter name? A: Log in to 104.com.tw → Go to "Resume/Documents" section → Note the exact name of your cover letter (e.g., "自訂推薦信1"). It must match exactly.
Q: What if my cover letter name is different?
A: Change the coverLetter parameter: await autoApply104Jobs(page, { coverLetter: 'YOUR_EXACT_NAME' })
Q: Do I need to be logged in first? A: Yes! You must be logged in to 104.com.tw before running the automation. The script cannot log you in automatically.
Q: What search URL should I use?
A: See examples/search-urls.md for templates, or build your own by using 104's search UI and copying the URL.
Q: "No jobs found" - what's wrong?
A: Check that you're on a job search results page, not the homepage. Navigate to your search page first with await page.goto('YOUR_SEARCH_URL').
Q: "Cover letter not found" error?
A: Your cover letter name doesn't match. Run a single job test to see available cover letters: await testApplicationFlow(page, 'TEST_NAME').
Q: Applications failing frequently?
A: Run the validation script first: await validateSetup(page, { coverLetter: 'YOUR_NAME' }). Also check internet connection and verify login status.
Q: Browser crashes after many applications? A: Close and restart browser between sessions. Process in smaller batches (20-50 instead of 100+). Chrome may run out of memory on very long sessions.
Q: "Already applied" to all jobs? A: You've applied to all available jobs in your search. Expand search criteria (more locations, different keywords) or try again tomorrow when new jobs are posted.
Q: How many jobs should I apply to per day? A: Recommended: 20-30 per day for quality. Case study showed 609 apps/hour is possible, but focus on jobs you're genuinely interested in.
Q: Should I apply to every job? A: No. Use search filters to target relevant positions. Quality > quantity. Only apply to jobs you'd actually accept.
Q: What's the best time to apply? A: Early morning (8-10 AM) when jobs are freshly posted and HR is active. The automation sorts by "Latest" to catch new postings.
Q: Can I use different cover letters for different jobs? A: Yes! Create multiple configs with different cover letters, then run automation separately for each job type (frontend, backend, etc.).
Q: How often should I run the automation? A: Daily for best results. New jobs are posted throughout the day. Running daily ensures you're among the first applicants.
Q: Does this work with Playwright MCP? A: Yes, it's specifically designed for Playwright MCP in Claude Code. Just copy/paste the functions and run.
Q: Can I modify the code?
A: Yes! All scripts are in skills/104-job-auto-apply/. Customize delays, selectors, or flow as needed.
Q: How do I test without actually applying?
A: Use dry-run mode: await dryRun(page, { targetApplications: 20 }). It shows what would happen without submitting applications.
Q: What if 104.com.tw changes their website?
A: Run selector tests to detect changes: await testSelectors(page). Update selectors in the scripts if failures detected.
Q: Can I run this on multiple accounts? A: Yes, but you'll need to switch accounts manually in the browser between runs. Log out, log in to different account, then run automation.
Q: How do I know which jobs I applied to? A: Check 104.com.tw → "My Job Applications" section. The automation also logs job titles in console output.
Q: What's a good success rate? A: 90%+ is excellent (case study: 92.5%). Below 80% indicates issues that need fixing.
Q: Why did some applications fail? A: Common reasons: Network timeout, form changed, cover letter not found, page didn't load fully. Check console output for specific error messages.
Q: Can I export results?
A: Results are returned as a JavaScript object. See examples/sample-results.json for format. You can log or save this data.
Q: Is this legal? A: Automation itself is generally legal for personal use. However, check 104.com.tw Terms of Service and use responsibly. Don't spam applications.
Q: Will I get banned? A: Unlikely if used responsibly. The automation uses human-like delays and follows normal application flow. Case study: 2,495 applications with no issues.
Q: Should I apply to jobs I'm not qualified for? A: No. Use search filters to find suitable positions. Applying to irrelevant jobs wastes everyone's time and may harm your reputation.
Q: What about privacy? A: The automation only interacts with public job listings and your own application data. No data is stored or transmitted outside your local environment.
For implementation details and learnings:
../ai_experiment/104/104_auto_apply_target.js - Target-based implementation../ai_experiment/104/LEARNINGS.md - 467 lines of insights../ai_experiment/104/DOCUMENTATION_INDEX.md - Full docs.apply-button__button is correct)Remember: This skill is proven to work. Use it responsibly for genuine job applications to suitable positions. Quality matters more than quantity.