blank-agentic-cli 0.1.0__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.
- blank_agentic_cli-0.1.0/LICENSE +21 -0
- blank_agentic_cli-0.1.0/PKG-INFO +88 -0
- blank_agentic_cli-0.1.0/README.md +65 -0
- blank_agentic_cli-0.1.0/pyproject.toml +54 -0
- blank_agentic_cli-0.1.0/setup.cfg +4 -0
- blank_agentic_cli-0.1.0/src/blank_agentic_cli.egg-info/PKG-INFO +88 -0
- blank_agentic_cli-0.1.0/src/blank_agentic_cli.egg-info/SOURCES.txt +30 -0
- blank_agentic_cli-0.1.0/src/blank_agentic_cli.egg-info/dependency_links.txt +1 -0
- blank_agentic_cli-0.1.0/src/blank_agentic_cli.egg-info/entry_points.txt +2 -0
- blank_agentic_cli-0.1.0/src/blank_agentic_cli.egg-info/top_level.txt +1 -0
- blank_agentic_cli-0.1.0/src/blank_cli/__init__.py +3 -0
- blank_agentic_cli-0.1.0/src/blank_cli/__main__.py +4 -0
- blank_agentic_cli-0.1.0/src/blank_cli/cli.py +116 -0
- blank_agentic_cli-0.1.0/src/blank_cli/scaffold.py +191 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/.claude/settings.local.json.tpl +5 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/.codex/config.toml.tpl +8 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/.codex/project.md.tpl +19 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/.gitignore.tpl +11 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/.here.tpl +0 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/README.md.tpl +20 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/data/codebook.md.tpl +9 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/output/figures/.gitkeep.tpl +0 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/output/results/.gitkeep.tpl +0 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/output/tables/.gitkeep.tpl +0 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/scripts/00_setup.R.tpl +9 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/scripts/20_data.R.tpl +6 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/analysis/scripts/30_results_main.R.tpl +7 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/paper/aesthetics.typ.tpl +1 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/paper/paper_blank.typ.tpl +1 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/paper/paper_latex.typ.tpl +12 -0
- blank_agentic_cli-0.1.0/src/blank_cli/templates/paper/ref.bib.tpl +6 -0
- blank_agentic_cli-0.1.0/tests/test_cli.py +71 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Shusuke Ioku
|
|
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,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blank-agentic-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI scaffolder for agentic AI research projects
|
|
5
|
+
Author: Shusuke Ioku
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/shusuke-ioku/blank
|
|
8
|
+
Project-URL: Repository, https://github.com/shusuke-ioku/blank
|
|
9
|
+
Project-URL: Issues, https://github.com/shusuke-ioku/blank/issues
|
|
10
|
+
Keywords: cli,scaffold,research,agentic-ai,typst
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# blank
|
|
25
|
+
|
|
26
|
+
`blank` is a CLI scaffolder for research projects driven by agentic AI workflows.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
From PyPI (after release):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install blank-agentic-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
From GitHub:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pipx install git+https://github.com/shusuke-ioku/blank.git
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
blank init
|
|
46
|
+
blank init my-project
|
|
47
|
+
blank init my-project --project-name "My Project"
|
|
48
|
+
blank init my-project --dry-run
|
|
49
|
+
blank init my-project --force
|
|
50
|
+
blank init my-project --no-agents
|
|
51
|
+
blank init my-project --paper-template latex
|
|
52
|
+
blank init my-project --paper-template blank
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If `--paper-template` is omitted and you run in a terminal, `blank init` asks you to choose:
|
|
56
|
+
- `latex`: LaTeX-like Typst starter
|
|
57
|
+
- `blank`: minimal empty Typst file
|
|
58
|
+
|
|
59
|
+
## Generated scaffold
|
|
60
|
+
|
|
61
|
+
- `analysis/scripts/`
|
|
62
|
+
- `analysis/data/`
|
|
63
|
+
- `analysis/output/`
|
|
64
|
+
- `paper/`
|
|
65
|
+
- `idea/`
|
|
66
|
+
- `.codex/` and `.claude/` by default
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 -m venv .venv
|
|
72
|
+
source .venv/bin/activate
|
|
73
|
+
pip install -e . pytest
|
|
74
|
+
pytest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Release (PyPI)
|
|
78
|
+
|
|
79
|
+
1. Create PyPI project `blank-agentic-cli` and enable Trusted Publishing for this GitHub repo.
|
|
80
|
+
2. (Optional) Run GitHub Action `publish` manually with `testpypi` to verify packaging.
|
|
81
|
+
3. Tag a release:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git tag v0.1.0
|
|
85
|
+
git push origin v0.1.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
4. The `publish` workflow builds and uploads to PyPI.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# blank
|
|
2
|
+
|
|
3
|
+
`blank` is a CLI scaffolder for research projects driven by agentic AI workflows.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
From PyPI (after release):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pipx install blank-agentic-cli
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
From GitHub:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pipx install git+https://github.com/shusuke-ioku/blank.git
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Usage
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
blank init
|
|
23
|
+
blank init my-project
|
|
24
|
+
blank init my-project --project-name "My Project"
|
|
25
|
+
blank init my-project --dry-run
|
|
26
|
+
blank init my-project --force
|
|
27
|
+
blank init my-project --no-agents
|
|
28
|
+
blank init my-project --paper-template latex
|
|
29
|
+
blank init my-project --paper-template blank
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If `--paper-template` is omitted and you run in a terminal, `blank init` asks you to choose:
|
|
33
|
+
- `latex`: LaTeX-like Typst starter
|
|
34
|
+
- `blank`: minimal empty Typst file
|
|
35
|
+
|
|
36
|
+
## Generated scaffold
|
|
37
|
+
|
|
38
|
+
- `analysis/scripts/`
|
|
39
|
+
- `analysis/data/`
|
|
40
|
+
- `analysis/output/`
|
|
41
|
+
- `paper/`
|
|
42
|
+
- `idea/`
|
|
43
|
+
- `.codex/` and `.claude/` by default
|
|
44
|
+
|
|
45
|
+
## Development
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
python3 -m venv .venv
|
|
49
|
+
source .venv/bin/activate
|
|
50
|
+
pip install -e . pytest
|
|
51
|
+
pytest
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Release (PyPI)
|
|
55
|
+
|
|
56
|
+
1. Create PyPI project `blank-agentic-cli` and enable Trusted Publishing for this GitHub repo.
|
|
57
|
+
2. (Optional) Run GitHub Action `publish` manually with `testpypi` to verify packaging.
|
|
58
|
+
3. Tag a release:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git tag v0.1.0
|
|
62
|
+
git push origin v0.1.0
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
4. The `publish` workflow builds and uploads to PyPI.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "blank-agentic-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "CLI scaffolder for agentic AI research projects"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "Shusuke Ioku" }
|
|
13
|
+
]
|
|
14
|
+
license = { text = "MIT" }
|
|
15
|
+
keywords = ["cli", "scaffold", "research", "agentic-ai", "typst"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Science/Research",
|
|
19
|
+
"Topic :: Software Development :: Code Generators",
|
|
20
|
+
"Topic :: Utilities",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"License :: OSI Approved :: MIT License",
|
|
24
|
+
"Operating System :: OS Independent"
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://github.com/shusuke-ioku/blank"
|
|
29
|
+
Repository = "https://github.com/shusuke-ioku/blank"
|
|
30
|
+
Issues = "https://github.com/shusuke-ioku/blank/issues"
|
|
31
|
+
|
|
32
|
+
[project.scripts]
|
|
33
|
+
blank = "blank_cli.cli:main"
|
|
34
|
+
|
|
35
|
+
[tool.setuptools]
|
|
36
|
+
package-dir = {"" = "src"}
|
|
37
|
+
|
|
38
|
+
[tool.setuptools.packages.find]
|
|
39
|
+
where = ["src"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-data]
|
|
42
|
+
blank_cli = [
|
|
43
|
+
"templates/**/*.tpl",
|
|
44
|
+
"templates/.gitignore.tpl",
|
|
45
|
+
"templates/.here.tpl",
|
|
46
|
+
"templates/analysis/output/figures/.gitkeep.tpl",
|
|
47
|
+
"templates/analysis/output/tables/.gitkeep.tpl",
|
|
48
|
+
"templates/analysis/output/results/.gitkeep.tpl",
|
|
49
|
+
"templates/.codex/*.tpl",
|
|
50
|
+
"templates/.claude/*.tpl",
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: blank-agentic-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI scaffolder for agentic AI research projects
|
|
5
|
+
Author: Shusuke Ioku
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/shusuke-ioku/blank
|
|
8
|
+
Project-URL: Repository, https://github.com/shusuke-ioku/blank
|
|
9
|
+
Project-URL: Issues, https://github.com/shusuke-ioku/blank/issues
|
|
10
|
+
Keywords: cli,scaffold,research,agentic-ai,typst
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Science/Research
|
|
13
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
14
|
+
Classifier: Topic :: Utilities
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Requires-Python: >=3.9
|
|
20
|
+
Description-Content-Type: text/markdown
|
|
21
|
+
License-File: LICENSE
|
|
22
|
+
Dynamic: license-file
|
|
23
|
+
|
|
24
|
+
# blank
|
|
25
|
+
|
|
26
|
+
`blank` is a CLI scaffolder for research projects driven by agentic AI workflows.
|
|
27
|
+
|
|
28
|
+
## Install
|
|
29
|
+
|
|
30
|
+
From PyPI (after release):
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
pipx install blank-agentic-cli
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
From GitHub:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
pipx install git+https://github.com/shusuke-ioku/blank.git
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Usage
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
blank init
|
|
46
|
+
blank init my-project
|
|
47
|
+
blank init my-project --project-name "My Project"
|
|
48
|
+
blank init my-project --dry-run
|
|
49
|
+
blank init my-project --force
|
|
50
|
+
blank init my-project --no-agents
|
|
51
|
+
blank init my-project --paper-template latex
|
|
52
|
+
blank init my-project --paper-template blank
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
If `--paper-template` is omitted and you run in a terminal, `blank init` asks you to choose:
|
|
56
|
+
- `latex`: LaTeX-like Typst starter
|
|
57
|
+
- `blank`: minimal empty Typst file
|
|
58
|
+
|
|
59
|
+
## Generated scaffold
|
|
60
|
+
|
|
61
|
+
- `analysis/scripts/`
|
|
62
|
+
- `analysis/data/`
|
|
63
|
+
- `analysis/output/`
|
|
64
|
+
- `paper/`
|
|
65
|
+
- `idea/`
|
|
66
|
+
- `.codex/` and `.claude/` by default
|
|
67
|
+
|
|
68
|
+
## Development
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
python3 -m venv .venv
|
|
72
|
+
source .venv/bin/activate
|
|
73
|
+
pip install -e . pytest
|
|
74
|
+
pytest
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Release (PyPI)
|
|
78
|
+
|
|
79
|
+
1. Create PyPI project `blank-agentic-cli` and enable Trusted Publishing for this GitHub repo.
|
|
80
|
+
2. (Optional) Run GitHub Action `publish` manually with `testpypi` to verify packaging.
|
|
81
|
+
3. Tag a release:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git tag v0.1.0
|
|
85
|
+
git push origin v0.1.0
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
4. The `publish` workflow builds and uploads to PyPI.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/blank_agentic_cli.egg-info/PKG-INFO
|
|
5
|
+
src/blank_agentic_cli.egg-info/SOURCES.txt
|
|
6
|
+
src/blank_agentic_cli.egg-info/dependency_links.txt
|
|
7
|
+
src/blank_agentic_cli.egg-info/entry_points.txt
|
|
8
|
+
src/blank_agentic_cli.egg-info/top_level.txt
|
|
9
|
+
src/blank_cli/__init__.py
|
|
10
|
+
src/blank_cli/__main__.py
|
|
11
|
+
src/blank_cli/cli.py
|
|
12
|
+
src/blank_cli/scaffold.py
|
|
13
|
+
src/blank_cli/templates/.gitignore.tpl
|
|
14
|
+
src/blank_cli/templates/.here.tpl
|
|
15
|
+
src/blank_cli/templates/README.md.tpl
|
|
16
|
+
src/blank_cli/templates/.claude/settings.local.json.tpl
|
|
17
|
+
src/blank_cli/templates/.codex/config.toml.tpl
|
|
18
|
+
src/blank_cli/templates/.codex/project.md.tpl
|
|
19
|
+
src/blank_cli/templates/analysis/data/codebook.md.tpl
|
|
20
|
+
src/blank_cli/templates/analysis/output/figures/.gitkeep.tpl
|
|
21
|
+
src/blank_cli/templates/analysis/output/results/.gitkeep.tpl
|
|
22
|
+
src/blank_cli/templates/analysis/output/tables/.gitkeep.tpl
|
|
23
|
+
src/blank_cli/templates/analysis/scripts/00_setup.R.tpl
|
|
24
|
+
src/blank_cli/templates/analysis/scripts/20_data.R.tpl
|
|
25
|
+
src/blank_cli/templates/analysis/scripts/30_results_main.R.tpl
|
|
26
|
+
src/blank_cli/templates/paper/aesthetics.typ.tpl
|
|
27
|
+
src/blank_cli/templates/paper/paper_blank.typ.tpl
|
|
28
|
+
src/blank_cli/templates/paper/paper_latex.typ.tpl
|
|
29
|
+
src/blank_cli/templates/paper/ref.bib.tpl
|
|
30
|
+
tests/test_cli.py
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blank_cli
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import sys
|
|
5
|
+
from importlib import resources
|
|
6
|
+
from pathlib import Path
|
|
7
|
+
|
|
8
|
+
from .scaffold import ScaffoldConflictError, apply_actions, plan_actions
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def _build_parser() -> argparse.ArgumentParser:
|
|
12
|
+
parser = argparse.ArgumentParser(prog="blank", description="Scaffold research projects")
|
|
13
|
+
subparsers = parser.add_subparsers(dest="command", required=True)
|
|
14
|
+
|
|
15
|
+
init_parser = subparsers.add_parser("init", help="Initialize a research project scaffold")
|
|
16
|
+
init_parser.add_argument("target_dir", nargs="?", default=".", help="Target directory (default: current directory)")
|
|
17
|
+
init_parser.add_argument(
|
|
18
|
+
"--project-name",
|
|
19
|
+
help="Project name used in generated template files (default: target directory name)",
|
|
20
|
+
)
|
|
21
|
+
init_parser.add_argument(
|
|
22
|
+
"--force",
|
|
23
|
+
action="store_true",
|
|
24
|
+
help="Overwrite scaffold files when they already exist and differ",
|
|
25
|
+
)
|
|
26
|
+
init_parser.add_argument("--dry-run", action="store_true", help="Show planned actions without writing files")
|
|
27
|
+
init_parser.add_argument(
|
|
28
|
+
"--no-agents",
|
|
29
|
+
action="store_true",
|
|
30
|
+
help="Skip generating .codex/ and .claude/ files",
|
|
31
|
+
)
|
|
32
|
+
init_parser.add_argument(
|
|
33
|
+
"--paper-template",
|
|
34
|
+
choices=["latex", "blank"],
|
|
35
|
+
help="Paper template style for paper/paper.typ (latex or blank)",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
return parser
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def _prompt_paper_template() -> str:
|
|
42
|
+
print("Choose paper template for paper/paper.typ:")
|
|
43
|
+
print("1) latex (LaTeX-like layout) [recommended]")
|
|
44
|
+
print("2) blank (empty starter file)")
|
|
45
|
+
while True:
|
|
46
|
+
choice = input("Enter 1 or 2 [1]: ").strip()
|
|
47
|
+
if choice in ("", "1"):
|
|
48
|
+
return "latex"
|
|
49
|
+
if choice == "2":
|
|
50
|
+
return "blank"
|
|
51
|
+
print("Invalid choice. Please enter 1 or 2.")
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _command_init(args: argparse.Namespace) -> int:
|
|
55
|
+
target_dir = Path(args.target_dir).expanduser().resolve()
|
|
56
|
+
project_name = args.project_name or target_dir.name
|
|
57
|
+
include_agents = not args.no_agents
|
|
58
|
+
paper_template = args.paper_template
|
|
59
|
+
if paper_template is None:
|
|
60
|
+
if sys.stdin.isatty():
|
|
61
|
+
paper_template = _prompt_paper_template()
|
|
62
|
+
else:
|
|
63
|
+
paper_template = "latex"
|
|
64
|
+
|
|
65
|
+
templates_dir = resources.files("blank_cli") / "templates"
|
|
66
|
+
templates_path = Path(str(templates_dir))
|
|
67
|
+
|
|
68
|
+
target_dir.mkdir(parents=True, exist_ok=True)
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
actions = plan_actions(
|
|
72
|
+
target_dir=target_dir,
|
|
73
|
+
templates_dir=templates_path,
|
|
74
|
+
project_name=project_name,
|
|
75
|
+
include_agents=include_agents,
|
|
76
|
+
paper_template=paper_template,
|
|
77
|
+
force=args.force,
|
|
78
|
+
)
|
|
79
|
+
except ScaffoldConflictError as exc:
|
|
80
|
+
print(str(exc), file=sys.stderr)
|
|
81
|
+
return 3
|
|
82
|
+
|
|
83
|
+
counts = apply_actions(
|
|
84
|
+
actions=actions,
|
|
85
|
+
target_dir=target_dir,
|
|
86
|
+
templates_dir=templates_path,
|
|
87
|
+
project_name=project_name,
|
|
88
|
+
include_agents=include_agents,
|
|
89
|
+
paper_template=paper_template,
|
|
90
|
+
dry_run=args.dry_run,
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
mode = "DRY RUN" if args.dry_run else "DONE"
|
|
94
|
+
print(f"[{mode}] blank init -> {target_dir}")
|
|
95
|
+
print(f"paper_template={paper_template}")
|
|
96
|
+
print(
|
|
97
|
+
f"created_dirs={counts['created_dirs']} created_files={counts['created_files']} "
|
|
98
|
+
f"replaced_files={counts['replaced_files']} skipped={counts['skipped']}"
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
return 0
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
def main(argv: list[str] | None = None) -> int:
|
|
105
|
+
parser = _build_parser()
|
|
106
|
+
args = parser.parse_args(argv)
|
|
107
|
+
|
|
108
|
+
if args.command == "init":
|
|
109
|
+
return _command_init(args)
|
|
110
|
+
|
|
111
|
+
parser.print_usage()
|
|
112
|
+
return 2
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
if __name__ == "__main__":
|
|
116
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from datetime import date
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Dict, List
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(frozen=True)
|
|
10
|
+
class ScaffoldAction:
|
|
11
|
+
action: str
|
|
12
|
+
path: Path
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ScaffoldConflictError(RuntimeError):
|
|
16
|
+
pass
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
DIRECTORIES: List[str] = [
|
|
20
|
+
"analysis/scripts",
|
|
21
|
+
"analysis/data/base",
|
|
22
|
+
"analysis/data/covariates",
|
|
23
|
+
"analysis/data/rworg",
|
|
24
|
+
"analysis/data/ocr_tables",
|
|
25
|
+
"analysis/output/figures",
|
|
26
|
+
"analysis/output/tables",
|
|
27
|
+
"analysis/output/results",
|
|
28
|
+
"paper",
|
|
29
|
+
"idea",
|
|
30
|
+
]
|
|
31
|
+
|
|
32
|
+
TEMPLATE_FILES: List[str] = [
|
|
33
|
+
"README.md.tpl",
|
|
34
|
+
".gitignore.tpl",
|
|
35
|
+
".here.tpl",
|
|
36
|
+
"analysis/scripts/00_setup.R.tpl",
|
|
37
|
+
"analysis/scripts/20_data.R.tpl",
|
|
38
|
+
"analysis/scripts/30_results_main.R.tpl",
|
|
39
|
+
"analysis/data/codebook.md.tpl",
|
|
40
|
+
"paper/ref.bib.tpl",
|
|
41
|
+
"paper/aesthetics.typ.tpl",
|
|
42
|
+
"analysis/output/figures/.gitkeep.tpl",
|
|
43
|
+
"analysis/output/tables/.gitkeep.tpl",
|
|
44
|
+
"analysis/output/results/.gitkeep.tpl",
|
|
45
|
+
".codex/project.md.tpl",
|
|
46
|
+
".codex/config.toml.tpl",
|
|
47
|
+
".claude/settings.local.json.tpl",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
PAPER_TEMPLATE_MAP = {
|
|
51
|
+
"latex": ("paper/paper_latex.typ.tpl", "paper/paper.typ"),
|
|
52
|
+
"blank": ("paper/paper_blank.typ.tpl", "paper/paper.typ"),
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _render_template(content: str, variables: Dict[str, str]) -> str:
|
|
57
|
+
rendered = content
|
|
58
|
+
for key, value in variables.items():
|
|
59
|
+
rendered = rendered.replace(f"{{{{{key}}}}}", value)
|
|
60
|
+
return rendered
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _is_agent_file(template_path: str) -> bool:
|
|
64
|
+
return template_path.startswith(".codex/") or template_path.startswith(".claude/")
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def _output_path_from_template(template_path: str) -> Path:
|
|
68
|
+
if not template_path.endswith(".tpl"):
|
|
69
|
+
raise ValueError(f"Template path must end with .tpl: {template_path}")
|
|
70
|
+
return Path(template_path[:-4])
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _effective_templates(paper_template: str) -> List[str]:
|
|
74
|
+
if paper_template not in PAPER_TEMPLATE_MAP:
|
|
75
|
+
raise ValueError(f"Unsupported paper template: {paper_template}")
|
|
76
|
+
_paper_src, paper_out = PAPER_TEMPLATE_MAP[paper_template]
|
|
77
|
+
return TEMPLATE_FILES + [f"{paper_out}.tpl"]
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def plan_actions(
|
|
81
|
+
target_dir: Path,
|
|
82
|
+
templates_dir: Path,
|
|
83
|
+
project_name: str,
|
|
84
|
+
include_agents: bool,
|
|
85
|
+
paper_template: str,
|
|
86
|
+
force: bool,
|
|
87
|
+
) -> List[ScaffoldAction]:
|
|
88
|
+
variables = {
|
|
89
|
+
"project_name": project_name,
|
|
90
|
+
"today": str(date.today()),
|
|
91
|
+
}
|
|
92
|
+
actions: List[ScaffoldAction] = []
|
|
93
|
+
|
|
94
|
+
for rel_dir in DIRECTORIES:
|
|
95
|
+
out_dir = target_dir / rel_dir
|
|
96
|
+
if out_dir.exists():
|
|
97
|
+
actions.append(ScaffoldAction("skip_dir", out_dir))
|
|
98
|
+
else:
|
|
99
|
+
actions.append(ScaffoldAction("create_dir", out_dir))
|
|
100
|
+
|
|
101
|
+
effective_templates = _effective_templates(paper_template)
|
|
102
|
+
paper_src, paper_out = PAPER_TEMPLATE_MAP[paper_template]
|
|
103
|
+
virtual_paths = {f"{paper_out}.tpl": paper_src}
|
|
104
|
+
|
|
105
|
+
for template_rel in effective_templates:
|
|
106
|
+
source_template_rel = virtual_paths.get(template_rel, template_rel)
|
|
107
|
+
if not include_agents and _is_agent_file(template_rel):
|
|
108
|
+
continue
|
|
109
|
+
|
|
110
|
+
template_file = templates_dir / source_template_rel
|
|
111
|
+
if not template_file.exists():
|
|
112
|
+
raise FileNotFoundError(f"Missing template file: {template_file}")
|
|
113
|
+
|
|
114
|
+
out_file = target_dir / _output_path_from_template(template_rel)
|
|
115
|
+
rendered_content = _render_template(template_file.read_text(encoding="utf-8"), variables)
|
|
116
|
+
|
|
117
|
+
if out_file.exists():
|
|
118
|
+
existing_content = out_file.read_text(encoding="utf-8")
|
|
119
|
+
if existing_content == rendered_content:
|
|
120
|
+
actions.append(ScaffoldAction("skip_file", out_file))
|
|
121
|
+
elif force:
|
|
122
|
+
actions.append(ScaffoldAction("replace_file", out_file))
|
|
123
|
+
else:
|
|
124
|
+
raise ScaffoldConflictError(
|
|
125
|
+
f"Refusing to overwrite existing file: {out_file}. Use --force to replace scaffold files."
|
|
126
|
+
)
|
|
127
|
+
else:
|
|
128
|
+
actions.append(ScaffoldAction("create_file", out_file))
|
|
129
|
+
|
|
130
|
+
return actions
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def apply_actions(
|
|
134
|
+
actions: List[ScaffoldAction],
|
|
135
|
+
target_dir: Path,
|
|
136
|
+
templates_dir: Path,
|
|
137
|
+
project_name: str,
|
|
138
|
+
include_agents: bool,
|
|
139
|
+
paper_template: str,
|
|
140
|
+
dry_run: bool,
|
|
141
|
+
) -> Dict[str, int]:
|
|
142
|
+
variables = {
|
|
143
|
+
"project_name": project_name,
|
|
144
|
+
"today": str(date.today()),
|
|
145
|
+
}
|
|
146
|
+
counts = {
|
|
147
|
+
"created_dirs": 0,
|
|
148
|
+
"created_files": 0,
|
|
149
|
+
"replaced_files": 0,
|
|
150
|
+
"skipped": 0,
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
effective_templates = _effective_templates(paper_template)
|
|
154
|
+
paper_src, paper_out = PAPER_TEMPLATE_MAP[paper_template]
|
|
155
|
+
virtual_paths = {f"{paper_out}.tpl": paper_src}
|
|
156
|
+
template_index = {}
|
|
157
|
+
for template_rel in effective_templates:
|
|
158
|
+
source_template_rel = virtual_paths.get(template_rel, template_rel)
|
|
159
|
+
if include_agents or not _is_agent_file(template_rel):
|
|
160
|
+
template_index[_output_path_from_template(template_rel)] = templates_dir / source_template_rel
|
|
161
|
+
|
|
162
|
+
for item in actions:
|
|
163
|
+
if item.action == "create_dir":
|
|
164
|
+
counts["created_dirs"] += 1
|
|
165
|
+
if not dry_run:
|
|
166
|
+
item.path.mkdir(parents=True, exist_ok=True)
|
|
167
|
+
continue
|
|
168
|
+
|
|
169
|
+
if item.action == "skip_dir" or item.action == "skip_file":
|
|
170
|
+
counts["skipped"] += 1
|
|
171
|
+
continue
|
|
172
|
+
|
|
173
|
+
output_rel = item.path.relative_to(target_dir)
|
|
174
|
+
template_path = template_index.get(output_rel)
|
|
175
|
+
if template_path is None:
|
|
176
|
+
raise RuntimeError(f"No template found for output file {output_rel}")
|
|
177
|
+
|
|
178
|
+
content = _render_template(template_path.read_text(encoding="utf-8"), variables)
|
|
179
|
+
|
|
180
|
+
if item.action == "create_file":
|
|
181
|
+
counts["created_files"] += 1
|
|
182
|
+
elif item.action == "replace_file":
|
|
183
|
+
counts["replaced_files"] += 1
|
|
184
|
+
else:
|
|
185
|
+
raise RuntimeError(f"Unsupported action: {item.action}")
|
|
186
|
+
|
|
187
|
+
if not dry_run:
|
|
188
|
+
item.path.parent.mkdir(parents=True, exist_ok=True)
|
|
189
|
+
item.path.write_text(content, encoding="utf-8")
|
|
190
|
+
|
|
191
|
+
return counts
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Project: {{project_name}}
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Research workspace scaffolded by `blank` on {{today}}.
|
|
6
|
+
|
|
7
|
+
## Agent Rules
|
|
8
|
+
|
|
9
|
+
- Ask before destructive file operations.
|
|
10
|
+
- Keep `analysis/data/codebook.md` synchronized with data changes.
|
|
11
|
+
- Regenerate outputs when analysis scripts change.
|
|
12
|
+
|
|
13
|
+
## Directory Structure
|
|
14
|
+
|
|
15
|
+
- `analysis/scripts/`
|
|
16
|
+
- `analysis/data/`
|
|
17
|
+
- `analysis/output/`
|
|
18
|
+
- `paper/`
|
|
19
|
+
- `idea/`
|
|
File without changes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# {{project_name}}
|
|
2
|
+
|
|
3
|
+
Research project scaffold initialized by `blank` on {{today}}.
|
|
4
|
+
|
|
5
|
+
## Structure
|
|
6
|
+
|
|
7
|
+
- `analysis/`: data pipelines, scripts, generated outputs
|
|
8
|
+
- `paper/`: manuscript files
|
|
9
|
+
- `idea/`: brainstorming notes
|
|
10
|
+
- `.codex/`: agent workflow config
|
|
11
|
+
|
|
12
|
+
## Quickstart
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
# initialize from repo root
|
|
16
|
+
blank init .
|
|
17
|
+
|
|
18
|
+
# run the analysis skeleton
|
|
19
|
+
Rscript analysis/scripts/30_results_main.R
|
|
20
|
+
```
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
// Placeholder style helpers for Typst manuscript customization.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# {{project_name}}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
#set page(margin: (x: 1in, y: 1in), numbering: "1")
|
|
2
|
+
#set text(font: "Libertinus Serif", size: 11pt)
|
|
3
|
+
#set par(justify: true, leading: 0.7em)
|
|
4
|
+
#set heading(numbering: "1.")
|
|
5
|
+
|
|
6
|
+
#heading(level: 1)[{{project_name}}]
|
|
7
|
+
|
|
8
|
+
#outline(title: [Contents])
|
|
9
|
+
|
|
10
|
+
#heading(level: 1)[Introduction]
|
|
11
|
+
|
|
12
|
+
Write your manuscript here.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from blank_cli.cli import main
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def test_init_creates_scaffold(tmp_path: Path) -> None:
|
|
9
|
+
target = tmp_path / "demo"
|
|
10
|
+
|
|
11
|
+
rc = main(["init", str(target), "--project-name", "Demo Project"])
|
|
12
|
+
|
|
13
|
+
assert rc == 0
|
|
14
|
+
assert (target / "analysis/scripts/00_setup.R").exists()
|
|
15
|
+
assert (target / "paper/paper.typ").exists()
|
|
16
|
+
assert (target / ".codex/project.md").exists()
|
|
17
|
+
assert (target / ".claude/settings.local.json").exists()
|
|
18
|
+
assert "Demo Project" in (target / "README.md").read_text(encoding="utf-8")
|
|
19
|
+
assert "Introduction" in (target / "paper/paper.typ").read_text(encoding="utf-8")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_init_no_agents(tmp_path: Path) -> None:
|
|
23
|
+
target = tmp_path / "demo"
|
|
24
|
+
|
|
25
|
+
rc = main(["init", str(target), "--no-agents"])
|
|
26
|
+
|
|
27
|
+
assert rc == 0
|
|
28
|
+
assert not (target / ".codex").exists()
|
|
29
|
+
assert not (target / ".claude").exists()
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_conflict_without_force(tmp_path: Path) -> None:
|
|
33
|
+
target = tmp_path / "demo"
|
|
34
|
+
target.mkdir(parents=True)
|
|
35
|
+
readme = target / "README.md"
|
|
36
|
+
readme.write_text("custom", encoding="utf-8")
|
|
37
|
+
|
|
38
|
+
rc = main(["init", str(target)])
|
|
39
|
+
|
|
40
|
+
assert rc == 3
|
|
41
|
+
assert readme.read_text(encoding="utf-8") == "custom"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def test_force_replaces_conflict(tmp_path: Path) -> None:
|
|
45
|
+
target = tmp_path / "demo"
|
|
46
|
+
target.mkdir(parents=True)
|
|
47
|
+
readme = target / "README.md"
|
|
48
|
+
readme.write_text("custom", encoding="utf-8")
|
|
49
|
+
|
|
50
|
+
rc = main(["init", str(target), "--force", "--project-name", "Forced"])
|
|
51
|
+
|
|
52
|
+
assert rc == 0
|
|
53
|
+
assert "Forced" in readme.read_text(encoding="utf-8")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def test_dry_run_writes_nothing(tmp_path: Path) -> None:
|
|
57
|
+
target = tmp_path / "demo"
|
|
58
|
+
|
|
59
|
+
rc = main(["init", str(target), "--dry-run"])
|
|
60
|
+
|
|
61
|
+
assert rc == 0
|
|
62
|
+
assert not (target / "README.md").exists()
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def test_init_blank_paper_template(tmp_path: Path) -> None:
|
|
66
|
+
target = tmp_path / "demo"
|
|
67
|
+
|
|
68
|
+
rc = main(["init", str(target), "--paper-template", "blank", "--project-name", "Bare"])
|
|
69
|
+
|
|
70
|
+
assert rc == 0
|
|
71
|
+
assert (target / "paper/paper.typ").read_text(encoding="utf-8").strip() == "# Bare"
|