Skip to main content

Evidence-backed application prep for academic and professional jobs.

Project description

CanISend

CI TestPyPI Python License

这也能投. Evidence-backed application prep for academic and professional jobs.

Core principle: 别编了 / No claims without receipts.

CanISend is a local-first CLI for preparing job application materials from a job advert and a private profile. It prepares materials only. It does not submit applications, create accounts, fill portals, scrape full job pages, upload packages, or answer sensitive declarations.

What It Does

  • Creates a private workspace for profile evidence, job folders, prompts, Typst templates, and agent instructions.
  • Imports and filters jobs.ac.uk RSS leads without scraping full job pages.
  • Creates one local job folder per application and keeps the full advert entry manual.
  • Extracts normalized evidence from Typst-first profile sources into profile/generated/.
  • Generates parsed job data, fit reports, cover letter drafts, CV tailoring notes, criteria checklists, material review checklists, and structured Typst content.
  • Supports deterministic local generation by default, with explicit opt-in for an LLM-backed parser or LLM-backed drafts.
  • Provides Codex, Claude Code, Gemini, and IDE-agent bridge files through AGENTS.md, CLAUDE.md, GEMINI.md, and agent-skills/canisend/SKILL.md.

Quick Start

Install from PyPI after the production release:

uv tool install canisend

For the current TestPyPI alpha:

uv tool install \
  --index-url https://test.pypi.org/simple/ \
  --extra-index-url https://pypi.org/simple/ \
  canisend==0.1.0

Run the packaged fake-data workflow before using private profile or job data:

canisend run-example --workspace /tmp/canisend-example --overwrite

Inspect:

/tmp/canisend-example/jobs/2026-06-15_example-university_lecturer-in-applied-economics/
  parsed_job.json
  02_fit_report.md
  03_cover_letter_draft.md
  05_criteria_checklist.md
  07_material_review_checklist.md
  typst/

From a development checkout, prefix CLI commands with uv run:

uv run canisend --help
uv run canisend run-example --workspace /tmp/canisend-example --overwrite

Core Workflow

1. Initialize a private workspace

Normal users should use an installed CLI plus a separate private workspace. They do not need to fork this repository.

canisend init-workspace --workspace ~/CanISendWorkspace
canisend doctor --workspace ~/CanISendWorkspace

The workspace contains private profile data, job leads, job folders, editable prompt copies, Typst templates, schemas, examples, and agent-readable skills:

~/CanISendWorkspace/
  canisend.yaml
  .env.example
  .gitignore
  AGENTS.md
  CLAUDE.md
  GEMINI.md
  profile/
  jobs/
  job_leads/
  prompts/
  templates/
  schemas/
  agent-skills/

After upgrades:

uv tool upgrade canisend
canisend update-workspace --workspace ~/CanISendWorkspace
canisend doctor --workspace ~/CanISendWorkspace

update-workspace preserves local prompt, template, and skill edits by default. Use --overwrite only when you intentionally want packaged defaults to replace local copies.

2. Prepare profile evidence

Put your real modernpro CV and statements under ~/CanISendWorkspace/profile/typst/. These files stay local, and profile/ is ignored by git except for .gitkeep.

Create starter profile files if needed:

canisend init-profile --workspace ~/CanISendWorkspace --mode typst

Generate normalized evidence:

canisend extract-profile-evidence --workspace ~/CanISendWorkspace

The profile manifest lives at profile/profile.yaml. Generated evidence is written to profile/generated/ and cited with item-level references such as:

profile/generated/cv.evidence.md#Teaching/cv-001

Review item-level evidence citations before trusting any generated claim.

3. Import leads and create one job folder

Fetch jobs.ac.uk RSS leads locally:

canisend fetch-jobs-ac-uk \
  --workspace ~/CanISendWorkspace \
  --feed-url "<jobs.ac.uk RSS url>" \
  --include economics \
  --exclude phd

Create a job folder from a selected zero-based lead index:

canisend new-job-from-lead \
  --workspace ~/CanISendWorkspace \
  --lead-index 0 \
  --institution "University X" \
  --deadline "2026-06-15"

You can also create a job manually:

canisend new-job \
  --workspace ~/CanISendWorkspace \
  --title "Lecturer in Economics" \
  --institution "University X" \
  --deadline "2026-06-15" \
  --source-url "https://www.jobs.ac.uk/job/example"

Paste the full advert into jobs/<job-slug>/job_advert.md before relying on parsed criteria or generated drafts. V1 does not scrape full job pages.

4. Generate draft materials

Run the deterministic local pipeline:

canisend run \
  --workspace ~/CanISendWorkspace \
  --job jobs/<job-slug>

Generated output includes:

jobs/<job-slug>/
  parsed_job.json
  01_job_summary.md
  02_fit_report.md
  03_cover_letter_draft.md
  04_cv_tailoring_notes.md
  05_criteria_checklist.md
  06_final_application_package.md
  07_material_review_checklist.md
  typst/
    cover_letter_content.json
    cover_letter.typ
    application_package_content.json
    application_package.typ

LLM-backed parser and draft generation are explicit opt-in modes. Configure a provider before using them:

ACADEMIC_PREP_LLM_PROVIDER=openai-compatible
OPENAI_API_KEY=...
OPENAI_BASE_URL=https://api.openai.com/v1
OPENAI_MODEL=...

canisend run \
  --workspace ~/CanISendWorkspace \
  --job jobs/<job-slug> \
  --llm-parser \
  --llm-drafts

For local CLI model access, use the command provider:

ACADEMIC_PREP_LLM_PROVIDER=command
ACADEMIC_PREP_LLM_COMMAND="codex exec --json"
ACADEMIC_PREP_LLM_TIMEOUT_SECONDS=300

The LLM-backed parser must return JSON matching the parsed_job.json contract. Draft outputs must cite profile evidence; unknown citations fail validation. Missing evidence should be marked as a gap, not replaced with unsupported claims.

5. Review, render, and submit manually

Review files in this order:

  1. parsed_job.json
  2. 05_criteria_checklist.md
  3. 02_fit_report.md
  4. 03_cover_letter_draft.md
  5. 04_cv_tailoring_notes.md
  6. 07_material_review_checklist.md
  7. typst/cover_letter_content.json
  8. 06_final_application_package.md

Use 07_material_review_checklist.md to track the cover letter draft, CV tailoring notes, placeholders, item-level evidence citation checks, and next manual actions.

Render Typst only when needed:

canisend render-typst \
  --workspace ~/CanISendWorkspace \
  --job jobs/<job-slug>

Rendering requires a local typst binary. Source generation does not. Submit manually through the institution portal outside CanISend.

Agent Usage

Codex, Claude Code, Gemini, and other local agents can run CanISend by opening the private workspace as the project root.

  • Codex and AGENTS.md-aware tools should read AGENTS.md.
  • Claude Code should read CLAUDE.md, which imports agent-skills/canisend/SKILL.md.
  • Gemini CLI should read GEMINI.md.
  • IDE agents can read any bridge file and then agent-skills/canisend/SKILL.md.

Agents should start with:

canisend doctor --workspace .

They may run local CLI commands, inspect generated evidence, and review current job artifacts. They must ask first before reading full private CVs, full job adverts, references, source URLs, or enabling LLM-backed flags. They must not scrape pages, submit applications, upload packages, fabricate evidence, or commit private profile/job data.

Detailed agent guidance lives in:

agent-skills/canisend/
  SKILL.md
  references/
    workflow.md
    job-lifecycle.md
    file-contracts.md
    typst-profile.md
    provider-config.md
    quality-gates.md
    agent-orchestration.md
    platforms.md
    privacy.md

prompts/ contains LLM prompt files used by the application pipeline. agent-skills/ contains agent-readable workflow and quality guidance.

Privacy Boundaries

This repository is intended to be open source. Personal application data should stay local:

  • profile/ is ignored by git except for .gitkeep.
  • jobs/ generated job folders are ignored by git.
  • job_leads/ RSS outputs are ignored by git.
  • .env, API keys, rendered PDFs, real source URLs, and generated application packages should not be committed.
  • Sensitive declarations such as right-to-work, visa, disability, equality monitoring, health, criminal record, and conflicts remain user-only.

CanISend is a preparation dossier tool, not proof of submission.

Maintainer Release

Release automation lives in GitHub Actions for jxpeng98/CanISend.

Local checks:

uv run pytest -v
uv build
uvx twine check dist/*
uv run python -m canisend.package_check dist/*.whl

CI runs the same test/build/resource-check sequence on pushes and pull requests. The release workflow uses PyPI Trusted Publishing with OIDC:

  • Pushing test/v<version> publishes to TestPyPI only.
  • Pushing v<version>bN or v<version>rcN publishes to TestPyPI, smoke-tests the TestPyPI package, then publishes a PyPI prerelease and creates a GitHub prerelease.
  • Pushing v<version> publishes to TestPyPI, smoke-tests the TestPyPI package, then publishes a stable PyPI release and creates a GitHub Release.
  • TestPyPI and PyPI need a Trusted Publisher for .github/workflows/release.yml with environments named testpypi and pypi.

Preferred tag-driven release orchestration:

scripts/release.sh test --version 0.2.0.dev1
scripts/release.sh beta --version 0.2.0b1
scripts/release.sh stable --version 0.2.0

The script updates pyproject.toml and src/canisend/__init__.py, runs local checks, commits the version bump, pushes the current branch, then creates and pushes the matching git tag:

git tag -a test/v0.2.0.dev1 HEAD -m "CanISend 0.2.0.dev1 TestPyPI"
git tag -a v0.2.0b1 HEAD -m "CanISend 0.2.0b1 beta"
git tag -a v0.2.0 HEAD -m "CanISend 0.2.0 stable"

release.yml is the only remote publisher. It always publishes to TestPyPI first, and only promotes v* tags to beta or stable PyPI after the TestPyPI publish and smoke test succeed. Use test/v* with a disposable version because TestPyPI package versions cannot be overwritten.

Use RELEASE.md for the full TestPyPI and PyPI release playbook. Version updates must change both pyproject.toml and src/canisend/__init__.py.

Repository Layout

src/canisend/             CLI and application pipeline
prompts/                  LLM prompt templates
templates/typst/          modernpro Typst templates
schemas/                  JSON schema contracts
agent-skills/             CanISend skill and agent references
platform-bridges/         AGENTS.md, CLAUDE.md, GEMINI.md workspace bridges
examples/end_to_end/      fully local fake-data workflow
tests/                    CLI, pipeline, packaging, release, and contract tests
RELEASE.md                maintainer release playbook

See canisend_v1_proposal.md for the original V1 engineering proposal.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

canisend-0.2.0b2.tar.gz (98.2 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

canisend-0.2.0b2-py3-none-any.whl (72.5 kB view details)

Uploaded Python 3

File details

Details for the file canisend-0.2.0b2.tar.gz.

File metadata

  • Download URL: canisend-0.2.0b2.tar.gz
  • Upload date:
  • Size: 98.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for canisend-0.2.0b2.tar.gz
Algorithm Hash digest
SHA256 5c3d44db728979c74ec6a9138126d8635aa8a5613fffa9cb700c51a22537840b
MD5 f1ab033c31974ee0c57bc8714035d621
BLAKE2b-256 642af0b243c3a91af42480c0a2eeb31ccfbcb61a1a877e75feda2d19678a8673

See more details on using hashes here.

Provenance

The following attestation bundles were made for canisend-0.2.0b2.tar.gz:

Publisher: release.yml on jxpeng98/CanISend

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file canisend-0.2.0b2-py3-none-any.whl.

File metadata

  • Download URL: canisend-0.2.0b2-py3-none-any.whl
  • Upload date:
  • Size: 72.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for canisend-0.2.0b2-py3-none-any.whl
Algorithm Hash digest
SHA256 114936cac78aa7b7590e06f91683cb4d1505be08fbbc82d58c67a1e24ef50a01
MD5 b97fca1eb1b8fdebd93532d8a357b12b
BLAKE2b-256 dfb021fb341213a51b586e399e676095f33f5440ba3c3425746b9a8c9ae1a18a

See more details on using hashes here.

Provenance

The following attestation bundles were made for canisend-0.2.0b2-py3-none-any.whl:

Publisher: release.yml on jxpeng98/CanISend

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page