canisend 0.2.0b2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. canisend-0.2.0b2/.env.example +6 -0
  2. canisend-0.2.0b2/.github/workflows/ci.yml +45 -0
  3. canisend-0.2.0b2/.github/workflows/release.yml +196 -0
  4. canisend-0.2.0b2/.gitignore +16 -0
  5. canisend-0.2.0b2/CHANGELOG.md +15 -0
  6. canisend-0.2.0b2/LICENSE +21 -0
  7. canisend-0.2.0b2/PKG-INFO +378 -0
  8. canisend-0.2.0b2/README.md +347 -0
  9. canisend-0.2.0b2/RELEASE.md +120 -0
  10. canisend-0.2.0b2/agent-skills/canisend/SKILL.md +67 -0
  11. canisend-0.2.0b2/agent-skills/canisend/agents/openai.yaml +4 -0
  12. canisend-0.2.0b2/agent-skills/canisend/references/agent-orchestration.md +73 -0
  13. canisend-0.2.0b2/agent-skills/canisend/references/file-contracts.md +103 -0
  14. canisend-0.2.0b2/agent-skills/canisend/references/job-lifecycle.md +63 -0
  15. canisend-0.2.0b2/agent-skills/canisend/references/platforms.md +62 -0
  16. canisend-0.2.0b2/agent-skills/canisend/references/privacy.md +59 -0
  17. canisend-0.2.0b2/agent-skills/canisend/references/provider-config.md +70 -0
  18. canisend-0.2.0b2/agent-skills/canisend/references/quality-gates.md +68 -0
  19. canisend-0.2.0b2/agent-skills/canisend/references/typst-profile.md +71 -0
  20. canisend-0.2.0b2/agent-skills/canisend/references/workflow.md +126 -0
  21. canisend-0.2.0b2/canisend_v1_proposal.md +1149 -0
  22. canisend-0.2.0b2/examples/end_to_end/README.md +80 -0
  23. canisend-0.2.0b2/examples/end_to_end/fake_llm_provider.py +119 -0
  24. canisend-0.2.0b2/examples/end_to_end/full_job_advert.md +19 -0
  25. canisend-0.2.0b2/examples/end_to_end/jobs_ac_uk_sample.xml +18 -0
  26. canisend-0.2.0b2/examples/end_to_end/profile/generated/.gitkeep +1 -0
  27. canisend-0.2.0b2/examples/end_to_end/profile/profile.yaml +13 -0
  28. canisend-0.2.0b2/examples/end_to_end/profile/typst/cover_letter_base.typ +19 -0
  29. canisend-0.2.0b2/examples/end_to_end/profile/typst/cv.typ +18 -0
  30. canisend-0.2.0b2/examples/end_to_end/profile/typst/research_statement.typ +13 -0
  31. canisend-0.2.0b2/examples/end_to_end/profile/typst/teaching_statement.typ +13 -0
  32. canisend-0.2.0b2/jobs/.gitkeep +1 -0
  33. canisend-0.2.0b2/platform-bridges/AGENTS.md +22 -0
  34. canisend-0.2.0b2/platform-bridges/CLAUDE.md +21 -0
  35. canisend-0.2.0b2/platform-bridges/GEMINI.md +21 -0
  36. canisend-0.2.0b2/profile/.gitkeep +1 -0
  37. canisend-0.2.0b2/prompts/cover_letter_writer.md +45 -0
  38. canisend-0.2.0b2/prompts/criteria_checker.md +44 -0
  39. canisend-0.2.0b2/prompts/cv_tailor.md +44 -0
  40. canisend-0.2.0b2/prompts/job_parser.md +38 -0
  41. canisend-0.2.0b2/prompts/package_builder.md +27 -0
  42. canisend-0.2.0b2/prompts/profile_matcher.md +37 -0
  43. canisend-0.2.0b2/pyproject.toml +74 -0
  44. canisend-0.2.0b2/schemas/criteria_check.schema.json +22 -0
  45. canisend-0.2.0b2/schemas/fit_report.schema.json +12 -0
  46. canisend-0.2.0b2/schemas/parsed_job.schema.json +51 -0
  47. canisend-0.2.0b2/scripts/release.sh +265 -0
  48. canisend-0.2.0b2/src/canisend/__init__.py +3 -0
  49. canisend-0.2.0b2/src/canisend/cli.py +426 -0
  50. canisend-0.2.0b2/src/canisend/config_schema.py +56 -0
  51. canisend-0.2.0b2/src/canisend/evidence.py +288 -0
  52. canisend-0.2.0b2/src/canisend/examples.py +112 -0
  53. canisend-0.2.0b2/src/canisend/jobs.py +178 -0
  54. canisend-0.2.0b2/src/canisend/llm.py +104 -0
  55. canisend-0.2.0b2/src/canisend/match.py +389 -0
  56. canisend-0.2.0b2/src/canisend/material_review.py +92 -0
  57. canisend-0.2.0b2/src/canisend/materials.py +175 -0
  58. canisend-0.2.0b2/src/canisend/package_check.py +71 -0
  59. canisend-0.2.0b2/src/canisend/parse.py +167 -0
  60. canisend-0.2.0b2/src/canisend/pipeline.py +220 -0
  61. canisend-0.2.0b2/src/canisend/profile.py +189 -0
  62. canisend-0.2.0b2/src/canisend/resource_files.py +62 -0
  63. canisend-0.2.0b2/src/canisend/resources/__init__.py +1 -0
  64. canisend-0.2.0b2/src/canisend/rss.py +89 -0
  65. canisend-0.2.0b2/src/canisend/typst.py +29 -0
  66. canisend-0.2.0b2/src/canisend/typst_mapping.py +209 -0
  67. canisend-0.2.0b2/src/canisend/workspace.py +214 -0
  68. canisend-0.2.0b2/templates/typst/application_package.typ +35 -0
  69. canisend-0.2.0b2/templates/typst/cover_letter.typ +40 -0
  70. canisend-0.2.0b2/templates/typst/cv_notes.typ +18 -0
  71. canisend-0.2.0b2/tests/test_cli.py +39 -0
  72. canisend-0.2.0b2/tests/test_evidence.py +158 -0
  73. canisend-0.2.0b2/tests/test_examples.py +133 -0
  74. canisend-0.2.0b2/tests/test_jobs.py +152 -0
  75. canisend-0.2.0b2/tests/test_llm.py +90 -0
  76. canisend-0.2.0b2/tests/test_match.py +229 -0
  77. canisend-0.2.0b2/tests/test_materials_llm.py +136 -0
  78. canisend-0.2.0b2/tests/test_parser_llm.py +83 -0
  79. canisend-0.2.0b2/tests/test_pipeline.py +348 -0
  80. canisend-0.2.0b2/tests/test_profile.py +62 -0
  81. canisend-0.2.0b2/tests/test_release_productization.py +185 -0
  82. canisend-0.2.0b2/tests/test_release_script.py +152 -0
  83. canisend-0.2.0b2/tests/test_repository_contract.py +271 -0
  84. canisend-0.2.0b2/tests/test_rss.py +74 -0
  85. canisend-0.2.0b2/tests/test_typst.py +58 -0
  86. canisend-0.2.0b2/tests/test_typst_mapping.py +106 -0
  87. canisend-0.2.0b2/tests/test_workspace_productization.py +258 -0
  88. canisend-0.2.0b2/uv.lock +370 -0
@@ -0,0 +1,6 @@
1
+ ACADEMIC_PREP_LLM_PROVIDER=openai-compatible
2
+ OPENAI_API_KEY=
3
+ OPENAI_BASE_URL=
4
+ OPENAI_MODEL=
5
+ ACADEMIC_PREP_LLM_COMMAND=
6
+ ACADEMIC_PREP_LLM_TIMEOUT_SECONDS=300
@@ -0,0 +1,45 @@
1
+ name: ci
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - main
7
+ - "feature/**"
8
+ pull_request:
9
+
10
+ jobs:
11
+ test-build:
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - name: Install uv
17
+ uses: astral-sh/setup-uv@v5
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v5
21
+ with:
22
+ python-version: "3.12"
23
+
24
+ - name: Install project
25
+ run: uv sync --all-extras --dev
26
+
27
+ - name: Run tests
28
+ run: uv run pytest -v
29
+
30
+ - name: Build distributions
31
+ run: uv build
32
+
33
+ - name: Check package metadata
34
+ run: uvx twine check dist/*
35
+
36
+ - name: Check packaged resources
37
+ run: uv run python -m canisend.package_check dist/*.whl
38
+
39
+ - name: Smoke test built wheel
40
+ run: |
41
+ python -m venv /tmp/canisend-smoke
42
+ /tmp/canisend-smoke/bin/pip install dist/*.whl
43
+ /tmp/canisend-smoke/bin/canisend --help
44
+ /tmp/canisend-smoke/bin/canisend init-workspace --workspace /tmp/canisend-workspace
45
+ /tmp/canisend-smoke/bin/canisend doctor --workspace /tmp/canisend-workspace
@@ -0,0 +1,196 @@
1
+ name: release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - "v*"
7
+ - "test/v*"
8
+
9
+ jobs:
10
+ build:
11
+ runs-on: ubuntu-latest
12
+ outputs:
13
+ version: ${{ steps.release-metadata.outputs.version }}
14
+ release_channel: ${{ steps.release-metadata.outputs.release_channel }}
15
+ prerelease: ${{ steps.release-metadata.outputs.prerelease }}
16
+ publish_pypi: ${{ steps.release-metadata.outputs.publish_pypi }}
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - name: Resolve release metadata
21
+ id: release-metadata
22
+ shell: bash
23
+ run: |
24
+ tag="$GITHUB_REF_NAME"
25
+ publish_pypi=false
26
+ prerelease=false
27
+
28
+ if [[ "$tag" == test/v* ]]; then
29
+ version="${tag#test/v}"
30
+ release_channel=test
31
+ elif [[ "$tag" == v* ]]; then
32
+ version="${tag#v}"
33
+ publish_pypi=true
34
+ if [[ "$version" =~ (a|b|rc)[0-9]+$ ]]; then
35
+ release_channel=beta
36
+ prerelease=true
37
+ else
38
+ release_channel=stable
39
+ fi
40
+ else
41
+ echo "Unsupported release tag: $tag" >&2
42
+ exit 1
43
+ fi
44
+
45
+ pyproject_version="$(sed -nE 's/^version = "([^"]+)"/\1/p' pyproject.toml | head -n 1)"
46
+ package_version="$(sed -nE 's/^__version__ = "([^"]+)"/\1/p' src/canisend/__init__.py | head -n 1)"
47
+
48
+ if [[ "$version" != "$pyproject_version" ]]; then
49
+ echo "Tag version $version does not match pyproject.toml $pyproject_version" >&2
50
+ exit 1
51
+ fi
52
+
53
+ if [[ "$pyproject_version" != "$package_version" ]]; then
54
+ echo "pyproject.toml $pyproject_version does not match __init__.py $package_version" >&2
55
+ exit 1
56
+ fi
57
+
58
+ echo "version=$version" >> "$GITHUB_OUTPUT"
59
+ echo "release_channel=$release_channel" >> "$GITHUB_OUTPUT"
60
+ echo "prerelease=$prerelease" >> "$GITHUB_OUTPUT"
61
+ echo "publish_pypi=$publish_pypi" >> "$GITHUB_OUTPUT"
62
+
63
+ - name: Install uv
64
+ uses: astral-sh/setup-uv@v5
65
+
66
+ - name: Set up Python
67
+ uses: actions/setup-python@v5
68
+ with:
69
+ python-version: "3.12"
70
+
71
+ - name: Install project
72
+ run: uv sync --all-extras --dev
73
+
74
+ - name: Run tests
75
+ run: uv run pytest -v
76
+
77
+ - name: Build distributions
78
+ run: uv build
79
+
80
+ - name: Check package metadata
81
+ run: uvx twine check dist/*
82
+
83
+ - name: Check packaged resources
84
+ run: uv run python -m canisend.package_check dist/*.whl
85
+
86
+ - name: Smoke test built wheel
87
+ run: |
88
+ python -m venv /tmp/canisend-smoke
89
+ /tmp/canisend-smoke/bin/pip install dist/*.whl
90
+ /tmp/canisend-smoke/bin/canisend --help
91
+ /tmp/canisend-smoke/bin/canisend init-workspace --workspace /tmp/canisend-workspace
92
+ /tmp/canisend-smoke/bin/canisend doctor --workspace /tmp/canisend-workspace
93
+
94
+ - name: Upload distributions
95
+ uses: actions/upload-artifact@v4
96
+ with:
97
+ name: python-distributions
98
+ path: |
99
+ dist/*.tar.gz
100
+ dist/*.whl
101
+
102
+ publish-testpypi:
103
+ needs: build
104
+ runs-on: ubuntu-latest
105
+ environment:
106
+ name: testpypi
107
+ url: https://test.pypi.org/p/canisend
108
+ permissions:
109
+ id-token: write
110
+ steps:
111
+ - name: Download distributions
112
+ uses: actions/download-artifact@v4
113
+ with:
114
+ name: python-distributions
115
+ path: dist
116
+
117
+ - name: Publish package distributions to TestPyPI
118
+ uses: pypa/gh-action-pypi-publish@release/v1
119
+ with:
120
+ repository-url: https://test.pypi.org/legacy/
121
+
122
+ smoke-test-testpypi:
123
+ needs: [build, publish-testpypi]
124
+ runs-on: ubuntu-latest
125
+ steps:
126
+ - name: Set up Python
127
+ uses: actions/setup-python@v5
128
+ with:
129
+ python-version: "3.12"
130
+
131
+ - name: Smoke test TestPyPI package
132
+ shell: bash
133
+ run: |
134
+ version="${{ needs.build.outputs.version }}"
135
+ python -m venv /tmp/canisend-testpypi
136
+ for attempt in 1 2 3 4 5; do
137
+ if /tmp/canisend-testpypi/bin/pip install \
138
+ --index-url https://test.pypi.org/simple/ \
139
+ --extra-index-url https://pypi.org/simple/ \
140
+ "canisend==$version"; then
141
+ break
142
+ fi
143
+ if [[ "$attempt" == "5" ]]; then
144
+ exit 1
145
+ fi
146
+ sleep 20
147
+ done
148
+ /tmp/canisend-testpypi/bin/canisend --help
149
+ /tmp/canisend-testpypi/bin/canisend init-workspace --workspace /tmp/canisend-testpypi-workspace
150
+ /tmp/canisend-testpypi/bin/canisend doctor --workspace /tmp/canisend-testpypi-workspace
151
+
152
+ publish-pypi:
153
+ if: needs.build.outputs.publish_pypi == 'true'
154
+ needs: [build, publish-testpypi, smoke-test-testpypi]
155
+ runs-on: ubuntu-latest
156
+ environment:
157
+ name: pypi
158
+ url: https://pypi.org/p/canisend
159
+ permissions:
160
+ id-token: write
161
+ steps:
162
+ - name: Download distributions
163
+ uses: actions/download-artifact@v4
164
+ with:
165
+ name: python-distributions
166
+ path: dist
167
+
168
+ - name: Publish package distributions to PyPI
169
+ uses: pypa/gh-action-pypi-publish@release/v1
170
+
171
+ create-github-release:
172
+ if: needs.build.outputs.publish_pypi == 'true'
173
+ needs: [build, publish-pypi]
174
+ runs-on: ubuntu-latest
175
+ permissions:
176
+ contents: write
177
+ steps:
178
+ - name: Create GitHub Release
179
+ env:
180
+ GH_TOKEN: ${{ github.token }}
181
+ VERSION: ${{ needs.build.outputs.version }}
182
+ RELEASE_CHANNEL: ${{ needs.build.outputs.release_channel }}
183
+ PRERELEASE: ${{ needs.build.outputs.prerelease }}
184
+ shell: bash
185
+ run: |
186
+ label="${RELEASE_CHANNEL^}"
187
+ args=(
188
+ gh release create "$GITHUB_REF_NAME"
189
+ --verify-tag
190
+ --title "CanISend $VERSION"
191
+ --notes "$label release $VERSION. See CHANGELOG.md and RELEASE.md for details."
192
+ )
193
+ if [[ "$PRERELEASE" == "true" ]]; then
194
+ args+=(--prerelease)
195
+ fi
196
+ "${args[@]}"
@@ -0,0 +1,16 @@
1
+ .DS_Store
2
+ .env
3
+ .venv/
4
+ __pycache__/
5
+ *.py[cod]
6
+ .pytest_cache/
7
+ .ruff_cache/
8
+ .claude/
9
+ dist/
10
+ build/
11
+ jobs/*
12
+ !jobs/.gitkeep
13
+ job_leads/
14
+ profile/*
15
+ !profile/.gitkeep
16
+ .vscode/
@@ -0,0 +1,15 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 - Alpha
4
+
5
+ Initial alpha release of the local-first academic application preparation CLI.
6
+
7
+ - Added installable `canisend` CLI and private workspace workflow.
8
+ - Added jobs.ac.uk RSS lead import with local keyword filtering.
9
+ - Added Typst-first private profile scaffolding and normalized evidence extraction.
10
+ - Added OpenAI-compatible and local command LLM provider support.
11
+ - Added job advert parsing, evidence matching, cover letter draft, CV tailoring notes, criteria checklist, final package, and material review checklist outputs.
12
+ - Added modernpro Typst source generation with optional local PDF rendering.
13
+ - Added cross-platform agent skill resources for Codex, Claude Code, Gemini CLI, and IDE agents.
14
+
15
+ This release prepares application materials only. It does not submit applications, fill web portals, create accounts, scrape full job pages, or answer sensitive declarations.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Peng Jiaxin
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,378 @@
1
+ Metadata-Version: 2.4
2
+ Name: canisend
3
+ Version: 0.2.0b2
4
+ Summary: Evidence-backed application prep for academic and professional jobs.
5
+ Project-URL: Homepage, https://github.com/jxpeng98/CanISend
6
+ Project-URL: Repository, https://github.com/jxpeng98/CanISend
7
+ Project-URL: Issues, https://github.com/jxpeng98/CanISend/issues
8
+ Project-URL: Documentation, https://github.com/jxpeng98/CanISend#readme
9
+ Author: Peng Jiaxin
10
+ Maintainer: Peng Jiaxin
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: academic jobs,applications,cli,llm,professional jobs,typst
14
+ Classifier: Development Status :: 3 - Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Education
17
+ Classifier: Intended Audience :: End Users/Desktop
18
+ Classifier: License :: OSI Approved :: MIT License
19
+ Classifier: Operating System :: OS Independent
20
+ Classifier: Programming Language :: Python :: 3
21
+ Classifier: Programming Language :: Python :: 3.11
22
+ Classifier: Programming Language :: Python :: 3.12
23
+ Classifier: Programming Language :: Python :: 3.13
24
+ Classifier: Topic :: Office/Business
25
+ Classifier: Topic :: Text Processing :: Markup
26
+ Requires-Python: >=3.11
27
+ Requires-Dist: pydantic>=2.7
28
+ Requires-Dist: pyyaml>=6.0
29
+ Requires-Dist: typer>=0.12
30
+ Description-Content-Type: text/markdown
31
+
32
+ # CanISend
33
+
34
+ [![CI](https://img.shields.io/github/actions/workflow/status/jxpeng98/CanISend/ci.yml?branch=main&label=ci)](https://github.com/jxpeng98/CanISend/actions/workflows/ci.yml)
35
+ [![TestPyPI](https://img.shields.io/badge/TestPyPI-0.1.0-blue)](https://test.pypi.org/project/canisend/0.1.0/)
36
+ ![Python](https://img.shields.io/badge/python-3.11%2B-blue)
37
+ ![License](https://img.shields.io/badge/license-MIT-green)
38
+
39
+ 这也能投. Evidence-backed application prep for academic and professional jobs.
40
+
41
+ Core principle: 别编了 / No claims without receipts.
42
+
43
+ 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.
44
+
45
+ ## What It Does
46
+
47
+ - Creates a private workspace for profile evidence, job folders, prompts, Typst templates, and agent instructions.
48
+ - Imports and filters jobs.ac.uk RSS leads without scraping full job pages.
49
+ - Creates one local job folder per application and keeps the full advert entry manual.
50
+ - Extracts normalized evidence from Typst-first profile sources into `profile/generated/`.
51
+ - Generates parsed job data, fit reports, cover letter drafts, CV tailoring notes, criteria checklists, material review checklists, and structured Typst content.
52
+ - Supports deterministic local generation by default, with explicit opt-in for an LLM-backed parser or LLM-backed drafts.
53
+ - Provides Codex, Claude Code, Gemini, and IDE-agent bridge files through `AGENTS.md`, `CLAUDE.md`, `GEMINI.md`, and `agent-skills/canisend/SKILL.md`.
54
+
55
+ ## Quick Start
56
+
57
+ Install from PyPI after the production release:
58
+
59
+ ```bash
60
+ uv tool install canisend
61
+ ```
62
+
63
+ For the current TestPyPI alpha:
64
+
65
+ ```bash
66
+ uv tool install \
67
+ --index-url https://test.pypi.org/simple/ \
68
+ --extra-index-url https://pypi.org/simple/ \
69
+ canisend==0.1.0
70
+ ```
71
+
72
+ Run the packaged fake-data workflow before using private profile or job data:
73
+
74
+ ```bash
75
+ canisend run-example --workspace /tmp/canisend-example --overwrite
76
+ ```
77
+
78
+ Inspect:
79
+
80
+ ```text
81
+ /tmp/canisend-example/jobs/2026-06-15_example-university_lecturer-in-applied-economics/
82
+ parsed_job.json
83
+ 02_fit_report.md
84
+ 03_cover_letter_draft.md
85
+ 05_criteria_checklist.md
86
+ 07_material_review_checklist.md
87
+ typst/
88
+ ```
89
+
90
+ From a development checkout, prefix CLI commands with `uv run`:
91
+
92
+ ```bash
93
+ uv run canisend --help
94
+ uv run canisend run-example --workspace /tmp/canisend-example --overwrite
95
+ ```
96
+
97
+ ## Core Workflow
98
+
99
+ ### 1. Initialize a private workspace
100
+
101
+ Normal users should use an installed CLI plus a separate private workspace. They do not need to fork this repository.
102
+
103
+ ```bash
104
+ canisend init-workspace --workspace ~/CanISendWorkspace
105
+ canisend doctor --workspace ~/CanISendWorkspace
106
+ ```
107
+
108
+ The workspace contains private profile data, job leads, job folders, editable prompt copies, Typst templates, schemas, examples, and agent-readable skills:
109
+
110
+ ```text
111
+ ~/CanISendWorkspace/
112
+ canisend.yaml
113
+ .env.example
114
+ .gitignore
115
+ AGENTS.md
116
+ CLAUDE.md
117
+ GEMINI.md
118
+ profile/
119
+ jobs/
120
+ job_leads/
121
+ prompts/
122
+ templates/
123
+ schemas/
124
+ agent-skills/
125
+ ```
126
+
127
+ After upgrades:
128
+
129
+ ```bash
130
+ uv tool upgrade canisend
131
+ canisend update-workspace --workspace ~/CanISendWorkspace
132
+ canisend doctor --workspace ~/CanISendWorkspace
133
+ ```
134
+
135
+ `update-workspace` preserves local prompt, template, and skill edits by default. Use `--overwrite` only when you intentionally want packaged defaults to replace local copies.
136
+
137
+ ### 2. Prepare profile evidence
138
+
139
+ Put your real modernpro CV and statements under `~/CanISendWorkspace/profile/typst/`. These files stay local, and `profile/` is ignored by git except for `.gitkeep`.
140
+
141
+ Create starter profile files if needed:
142
+
143
+ ```bash
144
+ canisend init-profile --workspace ~/CanISendWorkspace --mode typst
145
+ ```
146
+
147
+ Generate normalized evidence:
148
+
149
+ ```bash
150
+ canisend extract-profile-evidence --workspace ~/CanISendWorkspace
151
+ ```
152
+
153
+ The profile manifest lives at `profile/profile.yaml`. Generated evidence is written to `profile/generated/` and cited with item-level references such as:
154
+
155
+ ```text
156
+ profile/generated/cv.evidence.md#Teaching/cv-001
157
+ ```
158
+
159
+ Review item-level evidence citations before trusting any generated claim.
160
+
161
+ ### 3. Import leads and create one job folder
162
+
163
+ Fetch jobs.ac.uk RSS leads locally:
164
+
165
+ ```bash
166
+ canisend fetch-jobs-ac-uk \
167
+ --workspace ~/CanISendWorkspace \
168
+ --feed-url "<jobs.ac.uk RSS url>" \
169
+ --include economics \
170
+ --exclude phd
171
+ ```
172
+
173
+ Create a job folder from a selected zero-based lead index:
174
+
175
+ ```bash
176
+ canisend new-job-from-lead \
177
+ --workspace ~/CanISendWorkspace \
178
+ --lead-index 0 \
179
+ --institution "University X" \
180
+ --deadline "2026-06-15"
181
+ ```
182
+
183
+ You can also create a job manually:
184
+
185
+ ```bash
186
+ canisend new-job \
187
+ --workspace ~/CanISendWorkspace \
188
+ --title "Lecturer in Economics" \
189
+ --institution "University X" \
190
+ --deadline "2026-06-15" \
191
+ --source-url "https://www.jobs.ac.uk/job/example"
192
+ ```
193
+
194
+ 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.
195
+
196
+ ### 4. Generate draft materials
197
+
198
+ Run the deterministic local pipeline:
199
+
200
+ ```bash
201
+ canisend run \
202
+ --workspace ~/CanISendWorkspace \
203
+ --job jobs/<job-slug>
204
+ ```
205
+
206
+ Generated output includes:
207
+
208
+ ```text
209
+ jobs/<job-slug>/
210
+ parsed_job.json
211
+ 01_job_summary.md
212
+ 02_fit_report.md
213
+ 03_cover_letter_draft.md
214
+ 04_cv_tailoring_notes.md
215
+ 05_criteria_checklist.md
216
+ 06_final_application_package.md
217
+ 07_material_review_checklist.md
218
+ typst/
219
+ cover_letter_content.json
220
+ cover_letter.typ
221
+ application_package_content.json
222
+ application_package.typ
223
+ ```
224
+
225
+ LLM-backed parser and draft generation are explicit opt-in modes. Configure a provider before using them:
226
+
227
+ ```bash
228
+ ACADEMIC_PREP_LLM_PROVIDER=openai-compatible
229
+ OPENAI_API_KEY=...
230
+ OPENAI_BASE_URL=https://api.openai.com/v1
231
+ OPENAI_MODEL=...
232
+
233
+ canisend run \
234
+ --workspace ~/CanISendWorkspace \
235
+ --job jobs/<job-slug> \
236
+ --llm-parser \
237
+ --llm-drafts
238
+ ```
239
+
240
+ For local CLI model access, use the command provider:
241
+
242
+ ```bash
243
+ ACADEMIC_PREP_LLM_PROVIDER=command
244
+ ACADEMIC_PREP_LLM_COMMAND="codex exec --json"
245
+ ACADEMIC_PREP_LLM_TIMEOUT_SECONDS=300
246
+ ```
247
+
248
+ 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.
249
+
250
+ ### 5. Review, render, and submit manually
251
+
252
+ Review files in this order:
253
+
254
+ 1. `parsed_job.json`
255
+ 2. `05_criteria_checklist.md`
256
+ 3. `02_fit_report.md`
257
+ 4. `03_cover_letter_draft.md`
258
+ 5. `04_cv_tailoring_notes.md`
259
+ 6. `07_material_review_checklist.md`
260
+ 7. `typst/cover_letter_content.json`
261
+ 8. `06_final_application_package.md`
262
+
263
+ 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.
264
+
265
+ Render Typst only when needed:
266
+
267
+ ```bash
268
+ canisend render-typst \
269
+ --workspace ~/CanISendWorkspace \
270
+ --job jobs/<job-slug>
271
+ ```
272
+
273
+ Rendering requires a local `typst` binary. Source generation does not. Submit manually through the institution portal outside CanISend.
274
+
275
+ ## Agent Usage
276
+
277
+ Codex, Claude Code, Gemini, and other local agents can run CanISend by opening the private workspace as the project root.
278
+
279
+ - Codex and AGENTS.md-aware tools should read `AGENTS.md`.
280
+ - Claude Code should read `CLAUDE.md`, which imports `agent-skills/canisend/SKILL.md`.
281
+ - Gemini CLI should read `GEMINI.md`.
282
+ - IDE agents can read any bridge file and then `agent-skills/canisend/SKILL.md`.
283
+
284
+ Agents should start with:
285
+
286
+ ```bash
287
+ canisend doctor --workspace .
288
+ ```
289
+
290
+ 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.
291
+
292
+ Detailed agent guidance lives in:
293
+
294
+ ```text
295
+ agent-skills/canisend/
296
+ SKILL.md
297
+ references/
298
+ workflow.md
299
+ job-lifecycle.md
300
+ file-contracts.md
301
+ typst-profile.md
302
+ provider-config.md
303
+ quality-gates.md
304
+ agent-orchestration.md
305
+ platforms.md
306
+ privacy.md
307
+ ```
308
+
309
+ `prompts/` contains LLM prompt files used by the application pipeline. `agent-skills/` contains agent-readable workflow and quality guidance.
310
+
311
+ ## Privacy Boundaries
312
+
313
+ This repository is intended to be open source. Personal application data should stay local:
314
+
315
+ - `profile/` is ignored by git except for `.gitkeep`.
316
+ - `jobs/` generated job folders are ignored by git.
317
+ - `job_leads/` RSS outputs are ignored by git.
318
+ - `.env`, API keys, rendered PDFs, real source URLs, and generated application packages should not be committed.
319
+ - Sensitive declarations such as right-to-work, visa, disability, equality monitoring, health, criminal record, and conflicts remain user-only.
320
+
321
+ CanISend is a preparation dossier tool, not proof of submission.
322
+
323
+ ## Maintainer Release
324
+
325
+ Release automation lives in GitHub Actions for `jxpeng98/CanISend`.
326
+
327
+ Local checks:
328
+
329
+ ```bash
330
+ uv run pytest -v
331
+ uv build
332
+ uvx twine check dist/*
333
+ uv run python -m canisend.package_check dist/*.whl
334
+ ```
335
+
336
+ CI runs the same test/build/resource-check sequence on pushes and pull requests. The release workflow uses PyPI Trusted Publishing with OIDC:
337
+
338
+ - Pushing `test/v<version>` publishes to TestPyPI only.
339
+ - 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.
340
+ - Pushing `v<version>` publishes to TestPyPI, smoke-tests the TestPyPI package, then publishes a stable PyPI release and creates a GitHub Release.
341
+ - TestPyPI and PyPI need a Trusted Publisher for `.github/workflows/release.yml` with environments named `testpypi` and `pypi`.
342
+
343
+ Preferred tag-driven release orchestration:
344
+
345
+ ```bash
346
+ scripts/release.sh test --version 0.2.0.dev1
347
+ scripts/release.sh beta --version 0.2.0b1
348
+ scripts/release.sh stable --version 0.2.0
349
+ ```
350
+
351
+ 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:
352
+
353
+ ```bash
354
+ git tag -a test/v0.2.0.dev1 HEAD -m "CanISend 0.2.0.dev1 TestPyPI"
355
+ git tag -a v0.2.0b1 HEAD -m "CanISend 0.2.0b1 beta"
356
+ git tag -a v0.2.0 HEAD -m "CanISend 0.2.0 stable"
357
+ ```
358
+
359
+ `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.
360
+ Use `test/v*` with a disposable version because TestPyPI package versions cannot be overwritten.
361
+
362
+ Use `RELEASE.md` for the full TestPyPI and PyPI release playbook. Version updates must change both `pyproject.toml` and `src/canisend/__init__.py`.
363
+
364
+ ## Repository Layout
365
+
366
+ ```text
367
+ src/canisend/ CLI and application pipeline
368
+ prompts/ LLM prompt templates
369
+ templates/typst/ modernpro Typst templates
370
+ schemas/ JSON schema contracts
371
+ agent-skills/ CanISend skill and agent references
372
+ platform-bridges/ AGENTS.md, CLAUDE.md, GEMINI.md workspace bridges
373
+ examples/end_to_end/ fully local fake-data workflow
374
+ tests/ CLI, pipeline, packaging, release, and contract tests
375
+ RELEASE.md maintainer release playbook
376
+ ```
377
+
378
+ See `canisend_v1_proposal.md` for the original V1 engineering proposal.