unaltraweb 0.3.0
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.
- checksums.yaml +7 -0
- data/LICENSE +20 -0
- data/Makefile +412 -0
- data/README.md +226 -0
- data/_config.yml +842 -0
- data/_data/i18n/ca.yml +278 -0
- data/_data/i18n/en.yml +278 -0
- data/_data/i18n/es.yml +278 -0
- data/_includes/audio.liquid +30 -0
- data/_includes/bib_search.liquid +5 -0
- data/_includes/blog-list.liquid +66 -0
- data/_includes/book-citation.liquid +33 -0
- data/_includes/citation.liquid +26 -0
- data/_includes/custom_head.liquid +3 -0
- data/_includes/custom_scripts.liquid +5 -0
- data/_includes/cv/list.liquid +5 -0
- data/_includes/cv/list_groups.liquid +51 -0
- data/_includes/cv/map.liquid +29 -0
- data/_includes/cv/nested_list.liquid +16 -0
- data/_includes/cv/time_table.liquid +118 -0
- data/_includes/cv-download-card.liquid +26 -0
- data/_includes/developer-mode-switcher.liquid +24 -0
- data/_includes/disqus.liquid +13 -0
- data/_includes/distill_scripts.liquid +298 -0
- data/_includes/documentation-download-detail.liquid +76 -0
- data/_includes/documentation-downloads.liquid +71 -0
- data/_includes/documentation-profile-attributes.liquid +4 -0
- data/_includes/documentation-section-toc.liquid +9 -0
- data/_includes/documentation-sidebar.liquid +112 -0
- data/_includes/figure.liquid +87 -0
- data/_includes/footer.liquid +66 -0
- data/_includes/giscus.liquid +25 -0
- data/_includes/head.liquid +220 -0
- data/_includes/header.liquid +544 -0
- data/_includes/latest_posts.liquid +61 -0
- data/_includes/localized-date.liquid +5 -0
- data/_includes/manual-bibliography.liquid +2 -0
- data/_includes/manual-featured-readings.liquid +12 -0
- data/_includes/manual-other-readings.liquid +12 -0
- data/_includes/manual-page-toc.liquid +11 -0
- data/_includes/manual-sidebar.liquid +58 -0
- data/_includes/manual-teachers.liquid +74 -0
- data/_includes/metadata.liquid +278 -0
- data/_includes/news.liquid +48 -0
- data/_includes/newsletter.liquid +68 -0
- data/_includes/page-hero.liquid +96 -0
- data/_includes/page-link.liquid +8 -0
- data/_includes/pagination.liquid +22 -0
- data/_includes/post-reading-time.liquid +29 -0
- data/_includes/previous-next.liquid +37 -0
- data/_includes/profile-card.liquid +49 -0
- data/_includes/profile-feature-matrix.liquid +75 -0
- data/_includes/profile-highlights.liquid +122 -0
- data/_includes/project-grid.liquid +26 -0
- data/_includes/project-resource-link.liquid +45 -0
- data/_includes/project-resources.liquid +21 -0
- data/_includes/projects.liquid +38 -0
- data/_includes/projects_horizontal.liquid +37 -0
- data/_includes/publications-metrics-summary.liquid +69 -0
- data/_includes/publications-summary.liquid +127 -0
- data/_includes/reading-biblio-controls.liquid +65 -0
- data/_includes/reading-cover-card.liquid +93 -0
- data/_includes/reading-rating.liquid +11 -0
- data/_includes/recommended-readings.liquid +30 -0
- data/_includes/related_posts.liquid +39 -0
- data/_includes/repository/repo.liquid +48 -0
- data/_includes/repository/repo_trophies.liquid +44 -0
- data/_includes/repository/repo_user.liquid +35 -0
- data/_includes/resume/awards.liquid +19 -0
- data/_includes/resume/basics.liquid +28 -0
- data/_includes/resume/certificates.liquid +35 -0
- data/_includes/resume/education.liquid +54 -0
- data/_includes/resume/interests.liquid +34 -0
- data/_includes/resume/languages.liquid +31 -0
- data/_includes/resume/projects.liquid +32 -0
- data/_includes/resume/publications.liquid +28 -0
- data/_includes/resume/references.liquid +31 -0
- data/_includes/resume/skills.liquid +33 -0
- data/_includes/resume/volunteer.liquid +52 -0
- data/_includes/resume/work.liquid +52 -0
- data/_includes/scripts.liquid +364 -0
- data/_includes/search-data.js.liquid +341 -0
- data/_includes/selected_papers.liquid +3 -0
- data/_includes/t.liquid +31 -0
- data/_includes/team-grid.liquid +172 -0
- data/_includes/video.liquid +97 -0
- data/_layouts/about.liquid +93 -0
- data/_layouts/archive.liquid +45 -0
- data/_layouts/bib.liquid +677 -0
- data/_layouts/book-review.liquid +225 -0
- data/_layouts/book-shelf.liquid +66 -0
- data/_layouts/cv.liquid +127 -0
- data/_layouts/default.liquid +70 -0
- data/_layouts/distill.liquid +132 -0
- data/_layouts/documentation-download-detail.liquid +5 -0
- data/_layouts/documentation-home.liquid +100 -0
- data/_layouts/documentation-page.liquid +45 -0
- data/_layouts/manual-bib.liquid +73 -0
- data/_layouts/manual-chapter.liquid +99 -0
- data/_layouts/manual-featured-bib.liquid +100 -0
- data/_layouts/manual-home.liquid +120 -0
- data/_layouts/none.liquid +1 -0
- data/_layouts/outputs.liquid +29 -0
- data/_layouts/page.liquid +52 -0
- data/_layouts/post.liquid +95 -0
- data/_layouts/profile.liquid +36 -0
- data/_layouts/profiles.liquid +36 -0
- data/_layouts/project.liquid +7 -0
- data/_layouts/repositories.liquid +58 -0
- data/_layouts/theses.liquid +126 -0
- data/_plugins/bibliography_profiles.rb +164 -0
- data/_plugins/callouts.rb +104 -0
- data/_plugins/code_blocks.rb +277 -0
- data/_plugins/computation_figure_images.rb +266 -0
- data/_plugins/computation_sources.rb +42 -0
- data/_plugins/content_search_index.rb +210 -0
- data/_plugins/details.rb +23 -0
- data/_plugins/external-posts.rb +124 -0
- data/_plugins/figure_captions.rb +800 -0
- data/_plugins/file-exists.rb +22 -0
- data/_plugins/google-scholar-citations.rb +86 -0
- data/_plugins/hide-custom-bibtex.rb +18 -0
- data/_plugins/inspirehep-citations.rb +57 -0
- data/_plugins/localized_visual_sources.rb +145 -0
- data/_plugins/manual_release_metadata.rb +47 -0
- data/_plugins/mermaid_mmd_images.rb +233 -0
- data/_plugins/profile-pages.rb +28 -0
- data/_plugins/remove-accents.rb +32 -0
- data/_plugins/reproducible_build_time.rb +55 -0
- data/_plugins/search-data.rb +83 -0
- data/_plugins/theme-cache-bust.rb +56 -0
- data/_plugins/vega_visual_images.rb +290 -0
- data/_plugins/web_capture_images.rb +154 -0
- data/_sass/_base.scss +2133 -0
- data/_sass/_blog.scss +67 -0
- data/_sass/_callouts.scss +100 -0
- data/_sass/_custom.scss +437 -0
- data/_sass/_cv.scss +274 -0
- data/_sass/_developer.scss +97 -0
- data/_sass/_distill.scss +185 -0
- data/_sass/_documentation.scss +2220 -0
- data/_sass/_layout.scss +218 -0
- data/_sass/_manual.scss +2691 -0
- data/_sass/_profile.scss +323 -0
- data/_sass/_projects.scss +163 -0
- data/_sass/_reading.scss +465 -0
- data/_sass/_site-custom.scss +3 -0
- data/_sass/_tabs.scss +48 -0
- data/_sass/_team.scss +230 -0
- data/_sass/_themes.scss +254 -0
- data/_sass/_typograms.scss +132 -0
- data/_sass/_variables.scss +52 -0
- data/_sass/font-awesome/_animated.scss +152 -0
- data/_sass/font-awesome/_bordered-pulled.scss +20 -0
- data/_sass/font-awesome/_core.scss +49 -0
- data/_sass/font-awesome/_fixed-width.scss +7 -0
- data/_sass/font-awesome/_functions.scss +57 -0
- data/_sass/font-awesome/_icons.scss +13 -0
- data/_sass/font-awesome/_list.scss +18 -0
- data/_sass/font-awesome/_mixins.scss +65 -0
- data/_sass/font-awesome/_rotated-flipped.scss +31 -0
- data/_sass/font-awesome/_screen-reader.scss +14 -0
- data/_sass/font-awesome/_shims.scss +1578 -0
- data/_sass/font-awesome/_sizing.scss +16 -0
- data/_sass/font-awesome/_stacked.scss +32 -0
- data/_sass/font-awesome/_variables.scss +5044 -0
- data/_sass/font-awesome/brands.scss +30 -0
- data/_sass/font-awesome/fontawesome.scss +21 -0
- data/_sass/font-awesome/regular.scss +27 -0
- data/_sass/font-awesome/solid.scss +27 -0
- data/_sass/font-awesome/v4-shims.scss +11 -0
- data/_sass/tabler-icons/tabler-icons-filled.scss +1646 -0
- data/_sass/tabler-icons/tabler-icons-outline.scss +9859 -0
- data/_sass/tabler-icons/tabler-icons.scss +11463 -0
- data/_scripts/cronitor-analytics-setup.js +9 -0
- data/_scripts/giscus-setup.js +48 -0
- data/_scripts/google-analytics-setup.js +9 -0
- data/_scripts/open-panel-analytics-setup.js +14 -0
- data/_scripts/photoswipe-setup.js +11 -0
- data/assets/audio/epicaly-short-113909.mp3 +0 -0
- data/assets/bibliography/2018-12-22-distill.bib +7 -0
- data/assets/css/academicons.min.css +1 -0
- data/assets/css/bootstrap-toc.min.css +4 -0
- data/assets/css/bootstrap.min.css +7 -0
- data/assets/css/bootstrap.min.css.map +1 -0
- data/assets/css/jekyll-pygments-themes-github.css +209 -0
- data/assets/css/jekyll-pygments-themes-native.css +232 -0
- data/assets/css/jupyter-grade3.css +3758 -0
- data/assets/css/jupyter-monokai.css +3758 -0
- data/assets/css/jupyter.css +383 -0
- data/assets/css/main.scss +36 -0
- data/assets/css/mdb.min.css +48 -0
- data/assets/css/mdb.min.css.map +1 -0
- data/assets/css/scholar-icons.css +386 -0
- data/assets/fonts/academicons.ttf +0 -0
- data/assets/fonts/academicons.woff +0 -0
- data/assets/fonts/scholar-icons.ttf +0 -0
- data/assets/fonts/scholar-icons.woff +0 -0
- data/assets/fonts/tabler-icons-filled.ttf +0 -0
- data/assets/fonts/tabler-icons-filled.woff +0 -0
- data/assets/fonts/tabler-icons-filled.woff2 +0 -0
- data/assets/fonts/tabler-icons-outline.ttf +0 -0
- data/assets/fonts/tabler-icons-outline.woff +0 -0
- data/assets/fonts/tabler-icons-outline.woff2 +0 -0
- data/assets/fonts/tabler-icons.ttf +0 -0
- data/assets/fonts/tabler-icons.woff +0 -0
- data/assets/fonts/tabler-icons.woff2 +0 -0
- data/assets/html/relativity.html +15 -0
- data/assets/img/1.jpg +0 -0
- data/assets/img/10.jpg +0 -0
- data/assets/img/11.jpg +0 -0
- data/assets/img/12.jpg +0 -0
- data/assets/img/2.jpg +0 -0
- data/assets/img/3.jpg +0 -0
- data/assets/img/4.jpg +0 -0
- data/assets/img/5.jpg +0 -0
- data/assets/img/6.jpg +0 -0
- data/assets/img/7.jpg +0 -0
- data/assets/img/8.jpg +0 -0
- data/assets/img/9.jpg +0 -0
- data/assets/img/book_covers/the_godfather.jpg +0 -0
- data/assets/img/brand/dosquartsdedocs-logo-cafe.svg +31 -0
- data/assets/img/brand/dosquartsdedocs-logo-footer-cafe.svg +31 -0
- data/assets/img/brand/dosquartsdedocs-logo-white.svg +31 -0
- data/assets/img/brand/dosquartsdedocs-logo.svg +31 -0
- data/assets/img/prof_pic.jpg +0 -0
- data/assets/img/prof_pic_color.png +0 -0
- data/assets/img/profile-placeholder.svg +8 -0
- data/assets/img/publication_preview/brownian-motion.gif +0 -0
- data/assets/img/publication_preview/goodchild1992-gis-cover.jpg +0 -0
- data/assets/img/publication_preview/gutierrez2020-jtrg-preview.jpg +0 -0
- data/assets/img/publication_preview/harvey1989-postmodernity-cover.jpg +0 -0
- data/assets/img/publication_preview/tobler1970-economic-geography-cover.jpg +0 -0
- data/assets/img/publication_preview/tuan1977-space-place-cover.jpg +0 -0
- data/assets/img/publication_preview/wave-mechanics.gif +0 -0
- data/assets/img/publication_preview/zaragozi2025-rooftop-pv-preview.jpg +0 -0
- data/assets/img/rhino.png +0 -0
- data/assets/img/team/aaron-gutierrez.png +0 -0
- data/assets/img/team/alba-font.png +0 -0
- data/assets/img/team/alba-viana.png +0 -0
- data/assets/img/team/alejandro-gonzalez.png +0 -0
- data/assets/img/team/ana-pastor.png +0 -0
- data/assets/img/team/antoni-domenech.png +0 -0
- data/assets/img/team/benito-zaragozi.png +0 -0
- data/assets/img/team/cinta-sanz.png +0 -0
- data/assets/img/team/daniel-miravet.png +0 -0
- data/assets/img/team/eugenia-altamirano.png +0 -0
- data/assets/img/team/fiammetta-brandajs.png +0 -0
- data/assets/img/team/francesc-gonzalez.png +0 -0
- data/assets/img/team/isabel-paulino.png +0 -0
- data/assets/img/team/joan-borras.png +0 -0
- data/assets/img/team/julie-wilson.png +0 -0
- data/assets/img/team/liliana-sole.png +0 -0
- data/assets/img/team/maria-casado.png +0 -0
- data/assets/img/team/maria-medina.png +0 -0
- data/assets/img/team/marta-nello.png +0 -0
- data/assets/img/team/mogrify +1 -0
- data/assets/img/team/oscar-saladie.png +0 -0
- data/assets/img/team/paolo-russo.png +0 -0
- data/assets/img/team/raquel-santos.png +0 -0
- data/assets/img/team/salvador-anton.png +0 -0
- data/assets/img/team/sunsi-huertas.png +0 -0
- data/assets/img/team/user-placeholder.png +0 -0
- data/assets/img/team/wilbert-den-hoed.png +0 -0
- data/assets/img/team/xavier-delclos.png +0 -0
- data/assets/img/template_error.png +0 -0
- data/assets/js/bibsearch.js +70 -0
- data/assets/js/bootstrap-toc.min.js +5 -0
- data/assets/js/bootstrap.bundle.min.js +7 -0
- data/assets/js/bootstrap.bundle.min.js.map +1 -0
- data/assets/js/callouts.js +115 -0
- data/assets/js/chartjs-setup.js +14 -0
- data/assets/js/common.js +117 -0
- data/assets/js/content-search-match.js +159 -0
- data/assets/js/content-search.js +441 -0
- data/assets/js/copy_code.js +60 -0
- data/assets/js/custom.js +6 -0
- data/assets/js/developer-mode.js +103 -0
- data/assets/js/diff2html-setup.js +20 -0
- data/assets/js/distillpub/overrides.js +27 -0
- data/assets/js/distillpub/template.v2.js +9616 -0
- data/assets/js/distillpub/template.v2.js.map +1 -0
- data/assets/js/distillpub/transforms.v2.js +14557 -0
- data/assets/js/distillpub/transforms.v2.js.map +1 -0
- data/assets/js/documentation.js +435 -0
- data/assets/js/echarts-setup.js +29 -0
- data/assets/js/highlight-search-term.js +110 -0
- data/assets/js/jupyter_new_tab.js +18 -0
- data/assets/js/leaflet-setup.js +22 -0
- data/assets/js/manual.js +308 -0
- data/assets/js/masonry.js +12 -0
- data/assets/js/mathjax-setup.js +46 -0
- data/assets/js/mermaid-setup.js +37 -0
- data/assets/js/newsletter.js +105 -0
- data/assets/js/no_defer.js +24 -0
- data/assets/js/plotly-setup.js +52 -0
- data/assets/js/profile-nav.js +92 -0
- data/assets/js/progress-bar.js +73 -0
- data/assets/js/pseudocode-setup.js +18 -0
- data/assets/js/search/base-styles.min.js +286 -0
- data/assets/js/search/command-score.min.js +7 -0
- data/assets/js/search/hotkeys-js/hotkeys.esm.min.js +18 -0
- data/assets/js/search/lit/decorators.min.js +7 -0
- data/assets/js/search/lit/directives/class-map.min.js +7 -0
- data/assets/js/search/lit/directives/join.min.js +7 -0
- data/assets/js/search/lit/directives/live.min.js +7 -0
- data/assets/js/search/lit/directives/ref.min.js +7 -0
- data/assets/js/search/lit/directives/repeat.min.js +7 -0
- data/assets/js/search/lit/directives/unsafe-html.min.js +7 -0
- data/assets/js/search/lit/index.min.js +7 -0
- data/assets/js/search/lit-element/lit-element.min.js +12 -0
- data/assets/js/search/lit-html/directives/class-map.min.js +22 -0
- data/assets/js/search/lit-html/directives/join.min.js +12 -0
- data/assets/js/search/lit-html/directives/live.min.js +28 -0
- data/assets/js/search/lit-html/directives/ref.min.js +32 -0
- data/assets/js/search/lit-html/directives/repeat.min.js +27 -0
- data/assets/js/search/lit-html/directives/unsafe-html.min.js +22 -0
- data/assets/js/search/lit-html/is-server.min.js +12 -0
- data/assets/js/search/lit-html/lit-html.min.js +12 -0
- data/assets/js/search/ninja-action.min.js +101 -0
- data/assets/js/search/ninja-footer.min.js +62 -0
- data/assets/js/search/ninja-header.min.js +78 -0
- data/assets/js/search/ninja-keys.min.js +39 -0
- data/assets/js/search/reactive-element/decorators/custom-element.min.js +12 -0
- data/assets/js/search/reactive-element/decorators/event-options.min.js +12 -0
- data/assets/js/search/reactive-element/decorators/property.min.js +22 -0
- data/assets/js/search/reactive-element/decorators/query-all.min.js +17 -0
- data/assets/js/search/reactive-element/decorators/query-assigned-elements.min.js +17 -0
- data/assets/js/search/reactive-element/decorators/query-assigned-nodes.min.js +17 -0
- data/assets/js/search/reactive-element/decorators/query-async.min.js +17 -0
- data/assets/js/search/reactive-element/decorators/query.min.js +17 -0
- data/assets/js/search/reactive-element/decorators/state.min.js +27 -0
- data/assets/js/search/reactive-element/reactive-element.min.js +17 -0
- data/assets/js/search-setup.js +17 -0
- data/assets/js/shortcut-key.js +11 -0
- data/assets/js/tabs.min.js +5 -0
- data/assets/js/theme.js +314 -0
- data/assets/js/tooltips-setup.js +3 -0
- data/assets/js/typograms.js +1341 -0
- data/assets/js/vanilla-back-to-top.min.js +1 -0
- data/assets/js/vega-setup.js +24 -0
- data/assets/js/venobox-setup.js +5 -0
- data/assets/js/wechat.js +14 -0
- data/assets/js/zoom.js +15 -0
- data/assets/json/resume.json +98 -0
- data/assets/json/table_data.json +128 -0
- data/assets/jupyter/blog.ipynb +48 -0
- data/assets/pdf/example_pdf.pdf +0 -0
- data/assets/plotly/demo.html +72 -0
- data/assets/video/pexels-engin-akyurt-6069112-960x540-30fps.mp4 +0 -0
- data/assets/webfonts/fa-brands-400.ttf +0 -0
- data/assets/webfonts/fa-brands-400.woff2 +0 -0
- data/assets/webfonts/fa-regular-400.ttf +0 -0
- data/assets/webfonts/fa-regular-400.woff2 +0 -0
- data/assets/webfonts/fa-solid-900.ttf +0 -0
- data/assets/webfonts/fa-solid-900.woff2 +0 -0
- data/assets/webfonts/fa-v4compatibility.ttf +0 -0
- data/assets/webfonts/fa-v4compatibility.woff2 +0 -0
- data/docs/Gemfile +5 -0
- data/docs/_config.yml +83 -0
- data/docs/_documentation/en/01-quick-start.md +115 -0
- data/docs/_documentation/en/02-tools.md +146 -0
- data/docs/_documentation/en/03-usage.md +107 -0
- data/docs/_documentation/en/04-jekyll-basics.md +51 -0
- data/docs/_documentation/en/05-pages-collections.md +68 -0
- data/docs/_documentation/en/06-github-web-editing.md +92 -0
- data/docs/_documentation/en/10-profiles.md +125 -0
- data/docs/_documentation/en/11-unaltreselfie.md +39 -0
- data/docs/_documentation/en/12-unaltreprojecte.md +38 -0
- data/docs/_documentation/en/13-unaltremanual.md +237 -0
- data/docs/_documentation/en/14-unaltredocs.md +92 -0
- data/docs/_documentation/en/15-profile-checklist.md +39 -0
- data/docs/_documentation/en/20-syntax.md +353 -0
- data/docs/_documentation/en/21-themes.md +103 -0
- data/docs/_documentation/en/22-feature-reference.md +56 -0
- data/docs/_documentation/en/23-front-matter.md +51 -0
- data/docs/_documentation/en/24-data-files.md +28 -0
- data/docs/_documentation/en/30-customization.md +448 -0
- data/docs/_documentation/en/31-template.md +55 -0
- data/docs/_documentation/en/32-development.md +108 -0
- data/docs/_documentation/en/33-reusable-workflows.md +136 -0
- data/docs/_documentation/en/40-distribution.md +164 -0
- data/docs/_documentation/en/41-bibliometrics.md +137 -0
- data/docs/_documentation/en/42-docker-image.md +185 -0
- data/docs/_documentation/en/50-faq.md +33 -0
- data/docs/_pages/ca/index.md +35 -0
- data/docs/_pages/en/index.md +35 -0
- data/docs/_pages/es/index.md +35 -0
- data/docs/agents/action-prompts/00-start-site-session.txt +10 -0
- data/docs/agents/action-prompts/05-create-new-web.txt +5 -0
- data/docs/agents/action-prompts/10-content-update.txt +9 -0
- data/docs/agents/action-prompts/15-edit-default-content.txt +8 -0
- data/docs/agents/action-prompts/20-manual-teaching-materials.txt +18 -0
- data/docs/agents/action-prompts/22-manual-style-audit.txt +14 -0
- data/docs/agents/action-prompts/23-manual-structure-audit.txt +17 -0
- data/docs/agents/action-prompts/25-translation-prepublish.txt +8 -0
- data/docs/agents/action-prompts/30-project-site-update.txt +7 -0
- data/docs/agents/action-prompts/40-documentation-update.txt +7 -0
- data/docs/agents/action-prompts/50-bibliography-entry.txt +7 -0
- data/docs/agents/action-prompts/60-bibliometrics-refresh.txt +7 -0
- data/docs/agents/action-prompts/70-build-and-review.txt +12 -0
- data/docs/agents/manual-authoring-components.md +255 -0
- data/docs/agents/mcp-contract.md +162 -0
- data/docs/agents/quarto-computation-adoption.md +102 -0
- data/docs/assets/img/unaltraweb-docs-hero.svg +36 -0
- data/lib/unaltraweb/version.rb +8 -0
- data/lib/unaltraweb.rb +31 -0
- data/requirements.txt +5 -0
- data/scripts/biblio/fetch_scimago_csv.sh +126 -0
- data/scripts/biblio/metrics_common.py +154 -0
- data/scripts/biblio/metrics_merge_meta.py +73 -0
- data/scripts/biblio/metrics_update.py +826 -0
- data/scripts/computations/python/Dockerfile +37 -0
- data/scripts/computations/r/Dockerfile +31 -0
- data/scripts/computations/render.py +1267 -0
- data/scripts/cv/render_pdf_preview.sh +36 -0
- data/scripts/deploy/publish_branch.sh +80 -0
- data/scripts/generate_logo_variants.py +47 -0
- data/scripts/manual/Dockerfile +31 -0
- data/scripts/manual/build_pdf.py +1780 -0
- data/scripts/manual/filters/bibliography.lua +164 -0
- data/scripts/manual/filters/code-blocks.lua +102 -0
- data/scripts/manual/filters/figure-captions.lua +35 -0
- data/scripts/manual/publish_release.sh +281 -0
- data/scripts/manual/templates/manual.tex +494 -0
- data/scripts/manual/verify_release_assets.py +650 -0
- data/scripts/test_gem_build.py +128 -0
- data/scripts/test_reproducible_jekyll_build.py +191 -0
- data/scripts/test_wheel_install.py +282 -0
- data/scripts/unaltraweb-docker-mount.sh +36 -0
- data/scripts/unaltraweb-mcp-bootstrap.sh +105 -0
- data/scripts/unaltraweb-mcp-cleanup.sh +73 -0
- data/scripts/unaltraweb-mcp-project-id.sh +34 -0
- data/scripts/validate_distribution.py +563 -0
- data/scripts/validate_workflows.py +1285 -0
- data/scripts/web_captures/Dockerfile +31 -0
- data/scripts/web_captures/capture.mjs +157 -0
- data/scripts/web_captures/package-lock.json +56 -0
- data/scripts/web_captures/package.json +9 -0
- data/scripts/web_captures/render.py +867 -0
- data/src/unaltraweb_mcp/component-contract.json +153 -0
- data/src/unaltraweb_mcp/component-contract.schema.json +87 -0
- data/src/unaltraweb_mcp/docker_mount.py +19 -0
- metadata +861 -0
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# MCP Contract
|
|
2
|
+
|
|
3
|
+
`unaltraweb` exposes one global, on-demand stdio MCP server for website workspaces. Every client session starts an independently named Dockerized MCP process and mounts its current consumer project at `/workspace` and at its canonical host path. Stable factory, role, and project labels preserve project-scoped lifecycle control without forcing concurrent sessions to share a deterministic container name. The image includes both FastMCP and the Jekyll runtime, so the host does not need Python, the optional `mcp` package, Ruby, or Bundler.
|
|
4
|
+
|
|
5
|
+
The client registration should launch:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
make --silent --no-print-directory -C ${factoryRoot} mcp-stdio
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
The opened workspace is the consumer website repository. The pending distribution contract selects `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.3.0` for the factory logic, without asserting that the remote image exists yet. The launcher reuses it locally or attempts to pull it; an unavailable pending release fails visibly rather than triggering a local fallback build. This is the canonical ContExt command from `mcp-factory.yml`; the transport also sets `MCP_CONSUMER_WORKSPACE=${workspaceFolder}` in the child environment:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
MCP_CONSUMER_WORKSPACE="$PWD" make --silent --no-print-directory -C /path/to/unaltraweb mcp-stdio
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Replace `/path/to/unaltraweb` with the checkout's absolute path. The bootstrap canonicalizes the inherited environment value after process launch; neither Make nor generated shell source evaluates consumer path text. The declared launcher remains `make`, which ContExt permits for a container runtime without a `runtime.allowed_host_launchers` exception. Restart clients such as OpenCode after changing their MCP registration.
|
|
18
|
+
|
|
19
|
+
ContExt dependency preparation builds the runtime and required companions but does not initialize consumer content. The transport passes `${workspaceFolder}` only through `MCP_CONSUMER_WORKSPACE`; it never sets `transport.cwd` or embeds the consumer path in a Make assignment. The factory command may therefore use `make -C` without changing or reparsing the selected consumer root. Companion-aware checks and smoke tests include both required providers, while provider updates remain explicit. The manifest does not advertise an `init` command, and both companion dependencies set `init: false`. Use `new_web` explicitly when a new consumer site should be created. Restart long-lived MCP clients after registration, rebuilds, or provider upgrades so their stdio processes use the selected releases.
|
|
20
|
+
|
|
21
|
+
For a new agent-driven workspace, create or select the empty Git repository first, open that directory in the IDE, register the factory, restart the client, and then call `new_web`. The tool remains confined to the configured project root and does not accept an arbitrary destination. Generated sites include user-owned `README.md` and `AGENTS.md`, profile-specific source directories, and a managed runtime baseline; `scaffold_sync` never rewrites those user-owned guidance or content files.
|
|
22
|
+
|
|
23
|
+
The Python wheel remains modular. Without a factory checkout it supports `version`, `new-web`, top-level `doctor`, the host-only `import-calibre` command, and the exact package-only MCP inventory declared in `component-contract.json`. The complementary exact inventory fails clearly with `UNALTRAWEB_FACTORY_DIR` remediation. The BOM selects the published `diavisuals v0.3.1` and `vegavisuals v0.3.1` releases. Its `consumer_integration` object is also the single source for the immutable core, workflow, PDF worker, and Vega revisions rendered into consumer scaffolds. Neither companion is bundled in the wheel or MCP server namespace.
|
|
24
|
+
|
|
25
|
+
Static Vega-Lite and Vega rendering remains owned by the required companion `vegavisuals` MCP dependency. Use its `visualization_status`, `visualization_check`, `render_visualizations`, and `vegavisuals://project/*` resources directly; `unaltraweb` exposes the authoring syntax but does not proxy those tools into the `web://` server.
|
|
26
|
+
|
|
27
|
+
When companion-owned sources exist, unaltraweb does not accept a caller-provided success boolean and does not proxy the provider implementation. The provider must write `.unaltraweb/receipts/vegavisuals.json` or `.unaltraweb/receipts/diavisuals.json`; `site_check`, `site_doctor`, and `build_site` independently verify the provider identity/version/release, current request hash, exact expected input and artifact inventories, and every input and artifact SHA-256. Missing, stale, malformed, non-finite, oversized, symlinked, inventory-mismatched, or hash-mismatched receipts block the required gate.
|
|
28
|
+
|
|
29
|
+
A version-1 receipt contains only the provider result contract: `schema_version`, `provider`, `provider_version`, `release`, `request_sha256`, `ok: true`, `inputs`, and `artifacts`. Each non-source input and each artifact has exactly `path` and lowercase `sha256`; unaltraweb independently derives the complete expected input set and rehashes every entry, so omission or modification of a dataset invalidates the receipt. For vegavisuals, expected inputs are the union of top-level manifest `inputs`, each `visualizations[].inputs`, and every project-relative static `data.url` recursively found in the strict JSON specifications. Recursion is bounded. Remote URLs, dynamic URL values or interpolation, query/fragment URLs, absolute paths, traversal, missing files, and symlinked paths are unverifiable errors. For diavisuals, expected inputs are exactly empty until a versioned local-include contract is defined.
|
|
30
|
+
|
|
31
|
+
The request digest is SHA-256 over `unaltraweb-companion-receipt-v1\0OWNER\0`, followed for each sorted provider source by its UTF-8 project-relative path and bytes, each prefixed by an unsigned eight-byte big-endian length. Vega sources are the manifest and all project Vega/Vega-Lite specifications; diagram sources are all supported Mermaid and PlantUML files. Receipt publication remains provider-owned, but a provider cannot expand or reduce the input inventory accepted by unaltraweb.
|
|
32
|
+
|
|
33
|
+
## Resources
|
|
34
|
+
|
|
35
|
+
| Resource | Description |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| `web://distribution` | Package-owned component BOM plus offline, feature-aware doctor findings for the current factory and project. |
|
|
38
|
+
| `web://site-context` | Site profile, feature flags, content inventory, bibliography, bibliometrics, and build state. |
|
|
39
|
+
| `web://site-doctor` | Read-only offline distribution, project-contract, freshness, scaffold-drift, and core-override findings. |
|
|
40
|
+
| `web://starter-templates` | Starter website templates available to initialize a new workspace. |
|
|
41
|
+
| `web://profile-contract` | Checks for `unaltreselfie`, `unaltreprojecte`, `unaltremanual`, and `unaltredocs`. |
|
|
42
|
+
| `web://profile-prune-plan` | Dry-run list of profile-specific content that can be removed from the active profile. |
|
|
43
|
+
| `web://content-inventory` | Local editable collections, `_data/`, and assets. |
|
|
44
|
+
| `web://language-policy` | Default language, configured languages, and editorial translation workflow settings. |
|
|
45
|
+
| `web://content-approval` | Local editorial approval status for default-language and translated content. |
|
|
46
|
+
| `web://translation-plan` | Missing translations for approved default-language content before publication. |
|
|
47
|
+
| `web://bibliography` | BibTeX files, entry counts, types, duplicate citekeys, and update dates. |
|
|
48
|
+
| `web://bibliometrics` | Static bibliometrics summary state and bibliography update dates. |
|
|
49
|
+
| `web://build-health` | Existing `_site` build artefacts without starting a server. |
|
|
50
|
+
| `web://prompts` | Registered prompt names, descriptions, arguments, source files, and availability. |
|
|
51
|
+
| `web://manual-writing-guidance` | Generic drafting and style-review prompts combined with the site's `context/writing-profile.md` when available. |
|
|
52
|
+
| `web://manual-authoring-components` | Supported prose structures and component syntax, including callouts, definition lists, figure layouts, tables, diagrams, citations, and web/PDF compatibility. |
|
|
53
|
+
| `web://manual-computations` | Executable manual sources, selected runtime images, generated outputs, and freshness state. |
|
|
54
|
+
| `web://web-captures` | Selector-based screenshot recipes, original PNGs, editable SVG layers, edited overrides, and freshness. |
|
|
55
|
+
| `web://new-web-scaffolds` | Package-owned scaffold availability and contract paths for every supported site profile. |
|
|
56
|
+
|
|
57
|
+
## Tools
|
|
58
|
+
|
|
59
|
+
| Tool | Notes |
|
|
60
|
+
| --- | --- |
|
|
61
|
+
| `distribution_doctor` | Return offline component/factory/project findings with stable codes and optional local Docker image inspection that never pulls. |
|
|
62
|
+
| `new_web` | Create a profile-specific site from package-owned assets after a complete collision, path, and symlink preflight. |
|
|
63
|
+
| `starter_templates` | List package-owned profile scaffolds under the legacy inventory name. |
|
|
64
|
+
| `initialize_site` | Compatibility alias for `new_web`; external templates and overwrite mode are rejected. |
|
|
65
|
+
| `detect_site` | Detect an unaltraweb consumer from `_config.yml` and `Gemfile`, and report whether its Makefile exposes the native build/serve contract. |
|
|
66
|
+
| `site_context` | Read the main local state for an agent session. |
|
|
67
|
+
| `site_doctor` | Combine distribution doctor with strict project config, identity/language, generated Make contract, scaffold drift, required generated-output/receipt status, existing HTML audit, companion actions, and core override inventory. Unknown required status is blocking. Read-only and offline. |
|
|
68
|
+
| `site_check` | Run profile, freshness, companion visualization/diagram receipt, bibliography, bibliometrics, and build-state checks without network. |
|
|
69
|
+
| `site_source_read` | Read one allowed UTF-8 site source and return its exact SHA-256. |
|
|
70
|
+
| `site_source_write` | Dry-run or atomically create/update one allowed source. Creates require `create_only`; updates require the exact SHA-256 returned by a read. |
|
|
71
|
+
| `site_source_delete` | Dry-run or delete one allowed source with exact SHA-256 and explicit confirmation. It never deletes `_config.yml` or directories. |
|
|
72
|
+
| `scaffold_sync` | Dry-run or transactionally synchronize the nine package-managed scaffold controls, including the collaboration contract, Dependabot policy, pull-request template, dependency pins, and deploy caller, against `.unaltraweb/scaffold.json`; edited files are conflicts, exact current package bytes may be adopted without a rewrite, every output is staged, adopted and unchanged files are rechecked before and after the last manifest write, and rollback covers partial apply. README prose remains site-owned. |
|
|
73
|
+
| `profile_check` | Check current profile and expected content/config paths. |
|
|
74
|
+
| `manual_source_quality_check` | For `unaltremanual`, check captioned tables and figures, resolve local visual sources, compare embedded SVG text with body text on web/PDF, and suggest support-specific dimensions. |
|
|
75
|
+
| `manual_editorial_quality_check` | Reject non-publishable metatext, user/agent instructions, workflow markers, drafting notes, and placeholders in manual bodies; return the editorial review checklist and local writing-profile path. |
|
|
76
|
+
| `manual_authoring_capabilities` | Return the paragraph-development model and structured component catalogue an MCP writing assistant must use. |
|
|
77
|
+
| `manual_computation_status` | Inspect executable manual sources, selected images, generated outputs, and freshness without executing code. |
|
|
78
|
+
| `manual_computation_check` | Reject missing, modified, orphaned, or stale generated Markdown and figures. |
|
|
79
|
+
| `manual_computation_render` | Execute trusted sources in network-disabled containers and atomically publish declared outputs. |
|
|
80
|
+
| `manual_computation_render_figures` | Render only stale figure-mode computation sources without touching fresh outputs. |
|
|
81
|
+
| `web_capture_status` | Inspect `.capture.yml` recipes, PNG/SVG artefacts, edited overrides, and freshness without starting Chromium. |
|
|
82
|
+
| `web_capture_check` | Reject missing, modified, stale, orphaned, or obsolete edited capture artefacts. |
|
|
83
|
+
| `web_capture_render` | Start Jekyll and Chromium on an ephemeral internal Docker network, then publish original PNG plus editable annotated SVG from declared CSS selectors. |
|
|
84
|
+
| `manual_pdf_status` | Inspect PDF configuration, sources, generated artefacts, published paths, selector, and freshness without Docker, network, or writes. |
|
|
85
|
+
| `manual_pdf_build` | Build one or all configured language PDFs and first-page cover previews under `tmp/`; `release_selector` defaults to `latest` and is part of the PDF fingerprint. |
|
|
86
|
+
| `manual_pdf_publish` | Copy built PDFs and covers to configured public assets. Defaults to dry-run; real publication requires explicit confirmation, and the selector must match the build. |
|
|
87
|
+
| `manual_release_status` | Offline, read-only inspection of selector-bound build receipt, HTML audit, PDF evidence, stable policy, and local candidate state. |
|
|
88
|
+
| `manual_release_check` | Fail unless the selector-bound candidate exactly matches the current verified source, site, PDF, cover, manifest, and checksums. |
|
|
89
|
+
| `manual_release_prepare` | Dry-run or prepare only `tmp/manual-release/<selector>/`. Apply requires explicit confirmation and never tags, pushes, deploys, or creates a GitHub release. Stable local paths are no-clobber evidence, not durable publication. |
|
|
90
|
+
| `profile_prune_plan` | List content files whose explicit `profiles:` front matter excludes the selected profile. |
|
|
91
|
+
| `profile_prune` | Remove those profile-specific files only after reviewing the plan and passing `confirm_prune=true`. Empty-directory cleanup is descriptor-relative and never follows collection-root or ancestor symlinks. Defaults to dry-run. |
|
|
92
|
+
| `content_inventory` | Inventory pages, posts, chapters, documentation, data, and assets. |
|
|
93
|
+
| `language_policy` | Inspect `lang`, `default_lang`, `languages`, and the default-language-first editorial workflow. |
|
|
94
|
+
| `content_approval_inventory` | Summarize `content_status` values, approved default-language sources, and pending default-language content. |
|
|
95
|
+
| `translation_plan` | List approved default-language sources, missing target-language files, existing translations, and blocked unapproved sources. |
|
|
96
|
+
| `content_freshness_check` | Detect stale bibliometrics and future-dated posts/news from local files. |
|
|
97
|
+
| `bibliography_inventory` | Inspect BibTeX files and duplicate citekeys. |
|
|
98
|
+
| `bibliography_add_entry` | Append a verified BibTeX entry under `_bibliography/`. |
|
|
99
|
+
| `bibliometrics_check` | Run the factory's offline metrics check against the consumer project. |
|
|
100
|
+
| `bibliometrics_fetch_scimago` | Fetch or validate Scimago input through the factory against the consumer project. |
|
|
101
|
+
| `bibliometrics_update` | Run the factory's metrics update against the consumer project. |
|
|
102
|
+
| `build_site` | Verify required companion provider receipts, snapshot manual sources, run `make build-native LOCAL_CORE=/opt/unaltraweb` inside the current MCP container, optionally with `SITE_PROFILE` and a release selector, require unchanged sources and `_site` around a clean `html_audit`, then write the selector-bound receipt for `unaltremanual`; this never launches a nested Jekyll container. |
|
|
103
|
+
| `build_health` | Inspect existing `_site` artefacts without running Jekyll. |
|
|
104
|
+
| `html_audit` | Audit `_site` internal links/assets/fragments, duplicate IDs, unresolved Liquid, image alt attributes, title, and HTML language; inventory external links without fetching them. |
|
|
105
|
+
| `preview_start` | Start the single labelled preview container for the current project and wait for HTTP readiness. Port `0` (the default) publishes internal port `4000` on a Docker-assigned loopback host port; a nonzero port requests a fixed host port. |
|
|
106
|
+
| `preview_status` | Inspect that project's preview, including browser and container-internal URLs and optional recent logs. |
|
|
107
|
+
| `preview_stop` | Remove only that project's labelled preview container. |
|
|
108
|
+
| `http_check` | Probe bounded safe paths on the current project's owned labelled preview. The origin is derived internally; redirects and arbitrary origins are rejected. |
|
|
109
|
+
|
|
110
|
+
Advanced computation, capture, PDF, and bibliometrics tools delegate to factory-owned Make targets against the consumer project. Fresh package scaffolds therefore do not need to copy those implementation targets into each website. A new `unaltremanual` does include a consumer-owned `.unaltraweb/computations.yml` that selects the release's R and Python workers; the first render reuses a local image or pulls that selected image automatically. The worker layers remain external distribution components and are not copied into the wheel or site. The tool names use `bibliometrics_*` even though factory Make targets retain `metrics-*` for backwards compatibility.
|
|
111
|
+
|
|
112
|
+
`distribution_doctor` findings always include `code`, `severity`, `expected`, `actual`, and `remediation`. Missing factory assets in a direct wheel install produce healthy limited wheel mode, not a false failure. When Docker checks are requested, doctor uses only `docker version` and `docker image inspect`; it does not pull, build, start, or remove anything.
|
|
113
|
+
|
|
114
|
+
Manual PDF publication is a local workspace operation: it copies reviewed artefacts from `tmp/manual-pdf/` to configured paths such as `assets/pdf/` and `assets/img/`. It never commits, pushes, creates releases, or writes outside the consumer workspace. Use one selector consistently across `manual_pdf_build`, `manual_pdf_publish`, `build_site`, and `manual_release_prepare`. `latest` is the default; stable selectors use `vYYYY.MM(.N)` and require the consumer repository root to be an exact clean Git checkout without nested repositories, submodules, or clean/smudge filters. Stable Jekyll builds run in an MCP image selected by immutable digest, derive `SOURCE_DATE_EPOCH` from the consumer commit, and record both identities in their version-2 candidate manifest. Run `manual_source_quality_check`, `manual_editorial_quality_check`, `manual_pdf_status`, `manual_pdf_build`, and a `manual_pdf_publish` dry-run before calling `manual_pdf_publish(dry_run=false, confirm_publish=true)`. A stable caller additionally submits the SHA-256 of its checked local `tmp/manual-release/<selector>/release-manifest.json`; only the GitHub workflow has tag and release authority.
|
|
115
|
+
|
|
116
|
+
## New Site Initialization
|
|
117
|
+
|
|
118
|
+
`new_web` is intended for empty or nearly-empty website repositories. It creates common runtime files, profile-specific configuration, localized home pages, the content paths required by the selected profile, and `.unaltraweb/scaffold.json`. All scaffold assets are shipped inside the `unaltraweb_mcp` Python package and MCP Docker image; environment variables, sibling checkouts, and arbitrary template paths are not consulted.
|
|
119
|
+
|
|
120
|
+
The `unaltremanual` scaffold also creates `context/writing-profile.md` with a usable default editorial policy and `.unaltraweb/computations.yml` with both BOM-selected workers, `_chapters` and `assets/quarto` source roots, and a generated-asset root. Customize the writing profile for the manual's audience, voice, terminology, evidence policy, language workflow, and review requirements. Computation configuration is consumer-owned rather than scaffold-managed so a project can add lockfiles, dependency paths, or extension Dockerfiles without creating scaffold-sync conflicts. PDF generation and Vega manifests remain opt-in project configuration.
|
|
121
|
+
|
|
122
|
+
It sets `lang`, `default_lang`, and `languages` so a new site has an explicit source language from the first commit. The default is a single English home page; every configured language gets a localized home-page source and route.
|
|
123
|
+
|
|
124
|
+
Before writing, it validates every managed path, rejects destination symlinks, and compares existing files with the complete rendered scaffold through bounded regular-file reads. Identical files make repeated calls idempotent. Any differing file or file/directory collision detected during preflight aborts the whole operation before website files are written. Descriptor-relative, no-clobber writes and a final descriptor-relative content check prevent raced paths from being followed or overwritten; overwrite mode is not available. The baseline manifest is created after every other scaffold file.
|
|
125
|
+
|
|
126
|
+
The baseline records exactly `.gitignore`, `.unaltraweb/docker-mount.sh`, `.github/CONTRIBUTING.md`, `.github/dependabot.yml`, `Makefile`, `Gemfile`, `Gemfile.lock`, `.github/pull_request_template.md`, and `.github/workflows/deploy.yml`. `scaffold_sync` updates one of these while its bytes still match the recorded baseline, adopts it when its bytes already equal the current package payload, creates a newly managed path only when it is absent, and reports other local edits or deletions as conflicts. Any conflict prevents every apply. It removes retired paths only from the baseline and never deletes their project files; it also never changes site-owned README/agent guidance, config, seed content, bibliography, data, or assets. A real synchronization requires `dry_run=false` and `confirm_sync=true`; adopted and unchanged files are included in the final rechecks around the manifest-last commit.
|
|
127
|
+
|
|
128
|
+
Each scaffold is already reduced to one profile, so `profile_prune_plan` is not part of new-site creation. The prune rule remains available for existing mixed-profile sites.
|
|
129
|
+
|
|
130
|
+
## Constrained Source Management
|
|
131
|
+
|
|
132
|
+
The source tools are not generic filesystem operations. Their complete write scope is `_config.yml`; Markdown/HTML under the known content collections; YAML, JSON, or CSV below `_data/`; and Markdown below `context/`. Workflows, Makefiles, Gemfiles, layouts, includes, plugins, Sass, bibliography, binary assets, generated paths, symlinks, directories, absolute paths, and traversal are outside this API.
|
|
133
|
+
|
|
134
|
+
All operations use project-confined descriptor-relative no-follow traversal. Nonblocking open rejects FIFOs/devices before reading, size is checked before allocation, and files/proposed content are limited to 1 MiB. Text must be UTF-8 without NUL bytes; YAML and JSON reject duplicate keys, and JSON rejects non-finite numbers. Reads return SHA-256. Existing writes require that exact digest; new writes require `create_only=true`. Apply takes an advisory parent lock, moves the expected object to a private backup, verifies content and identity before and after publication, and restores the backup when a final-window edit is detected. Deletes use the equivalent verified tombstone flow. `_config.yml` is never deletable.
|
|
135
|
+
|
|
136
|
+
## Language And Translation Discipline
|
|
137
|
+
|
|
138
|
+
Each website should have an explicit default language in `_config.yml`, using `default_lang` and usually `lang` as the HTML fallback. `languages` lists the language variants that are actively maintained.
|
|
139
|
+
|
|
140
|
+
The language selector is hidden when `languages` contains one language. With multiple languages, the current item is exposed through `aria-current` and the configured default is visibly identified. Visual assets use the same language policy: the unsuffixed source belongs to the default language, a translated source inserts `.<lang>` before its complete suffix, and a missing translated source falls back to the unsuffixed one on web and PDF.
|
|
141
|
+
|
|
142
|
+
Agents should draft and edit meaningful content in the default language first. Use `content_status: draft`, `content_status: review`, or `content_status: approved` to make editorial state visible. `translation_plan` treats only default-language files with the approved value as ready for translation, then reports missing target-language files by shared `ref`.
|
|
143
|
+
|
|
144
|
+
Translations are a pre-publication task. They should preserve `ref`, citations, bibliography keys, figures, links, code, data field names, and routing metadata. Existing translations should not be silently rewritten while the default-language source is still changing; mark or report them as stale instead.
|
|
145
|
+
|
|
146
|
+
## Docker Runtime And Preview
|
|
147
|
+
|
|
148
|
+
`make mcp-build` builds `ghcr.io/dosquartsdedocs/unaltraweb:0.3.0` and then `ghcr.io/dosquartsdedocs/unaltraweb-mcp:0.3.0` locally. `make mcp-smoke` runs a real MCP client/server stdio exchange, compiles a temporary minimal site, and exercises preview start/status/stop. `mcp-stdio` remains dormant until a client launches it; dependency preparation never invokes it.
|
|
149
|
+
|
|
150
|
+
Run `site_doctor` and `site_check`, then resolve any blocking validation result before compiling. `build_site` reuses the active MCP container and the consumer's `build-native` target, and runs the local HTML audit after a successful Jekyll process. The generated `test-native` target also runs `html_audit`. This is intentionally different from the consumer's normal host-side `make build`, which starts a Jekyll container and would create a nested runtime when called from MCP.
|
|
151
|
+
|
|
152
|
+
Make delegation and feasible Docker control calls use one bounded subprocess runner. Status and control commands have short deadlines, builds/renders have target-specific longer deadlines, timeout terminates the process group and returns code `124`, and retained stdout/stderr is capped with explicit truncation fields. Factory commands that promise JSON fail closed when output is empty, malformed, non-object, non-finite, or truncated. Every bind source and target is encoded as a quoted Docker CSV field, so commas or quotes in host paths cannot introduce duplicate mount fields; carriage-return and newline path characters are rejected before canonicalization or mount construction. Computation, capture, and PDF containers carry factory, worker-role, project, and invocation-token labels plus cidfiles; after timeout cleanup selects all four labels and cannot remove unrelated containers.
|
|
153
|
+
|
|
154
|
+
A preview must outlive one MCP tool invocation, so it runs in a separate container made from the same MCP/Jekyll image. Its deterministic name is derived from the canonical host project path and it carries the factory, role, and project labels. Its isolated container always listens on port `4000`; the default host port is allocated atomically by Docker on loopback and is reported as `preview_status.port`, preventing the old cross-project collision on host port `4000`. Starting an already-running preview probes it again instead of creating a duplicate. A preview created under the former fixed-port default is accepted as compatible with the new automatic default until it is stopped; its next start uses dynamic allocation. Changing an explicit requested port or profile still requires stopping first. Stdio session containers intentionally have Docker-generated names so independent clients can run simultaneously, but carry the same stable project ID and labels as previews and capture resources. Stop and cleanup operations select ownership labels before removing anything.
|
|
155
|
+
|
|
156
|
+
`preview_status.url` is the browser URL published on host loopback. `preview_status.internal_url` is informational; callers do not pass it to `http_check`. That tool verifies preview ownership, derives the exact container-internal HTTP origin, disables environment proxies, accepts at most 20 local paths within a bounded timeout and response-read budget, and rejects absolute URLs, protocol-relative forms, traversal, fragments, hostile characters, non-2xx results, and redirects. Preview readiness checks the configured home permalink and generated root candidates rather than guessing a language route. `MCP_CONSUMER_WORKSPACE=/canonical/consumer/path make mcp-down` removes containers and networks selected by both `io.context.mcp-factory=unaltraweb` and the stable project label. A simultaneous `MCP_PROJECT_ID` must match that canonical live path. If the original path has moved or disappeared, pass the absent path with the retained ID or explicitly clear `MCP_CONSUMER_WORKSPACE`; only then is the retained ID accepted without path access. `make mcp-down-all` is the explicit maintainer cleanup for every resource carrying the factory label. Neither target deletes images or touches unlabelled resources.
|
|
157
|
+
|
|
158
|
+
The Docker socket gives the MCP container authority equivalent to the host Docker user. Enable this factory only for trusted unaltraweb repositories and trusted local images; project Makefiles execute as part of explicit build and authoring tools.
|
|
159
|
+
|
|
160
|
+
## Bibliography And Bibliometrics Discipline
|
|
161
|
+
|
|
162
|
+
Agents must not invent BibTeX metadata, DOIs, citekeys, journal details, or bibliometric values. Add entries only from author-provided or verified metadata. Static bibliometrics updates belong in versionable outputs such as `_bibliography/*.bib` and `_data/metrics.yml`; local Scimago caches and diagnostics stay unversioned.
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Quarto computation adoption note
|
|
2
|
+
|
|
3
|
+
This note records the TIGIT ColorBrewer figure incident from 13 August 2026 and turns it into follow-up work for `unaltraweb`.
|
|
4
|
+
|
|
5
|
+
## What happened
|
|
6
|
+
|
|
7
|
+
While editing `/home/benizar/git/tigit`, a Quarto source was added for a manual figure:
|
|
8
|
+
|
|
9
|
+
- `assets/quarto/color-cartography/palette-reference-swatches.qmd`
|
|
10
|
+
- intended output: `assets/img/color-cartography/palette-reference-swatches.svg`
|
|
11
|
+
|
|
12
|
+
The source was rendered directly with the host `quarto render` command, outside the `unaltraweb` computation workflow. That failed for three independent environment reasons:
|
|
13
|
+
|
|
14
|
+
1. Quarto tried to create a Jupyter transport directory below `/run/user/1000/jt`, which was read-only in the agent sandbox.
|
|
15
|
+
2. The host Python environment did not include `nbclient`; installing it locally also required network access.
|
|
16
|
+
3. After redirecting Quarto state to project-local `tmp/` directories, Jupyter still needed to open a local socket, which the sandbox denied.
|
|
17
|
+
|
|
18
|
+
The immediate failure is therefore not a Jekyll or Markdown rendering bug. It is a workflow problem: executable manual sources should not be run ad hoc on the host environment.
|
|
19
|
+
|
|
20
|
+
## What is already implemented in unaltraweb
|
|
21
|
+
|
|
22
|
+
The core already has the right architectural direction:
|
|
23
|
+
|
|
24
|
+
- `manual-compute-status`, `manual-compute-check`, `manual-compute-render`
|
|
25
|
+
- `.unaltraweb/computations.yml`
|
|
26
|
+
- `.unaltraweb/computations.lock.json`
|
|
27
|
+
- Python and R computation images under `scripts/computations/`
|
|
28
|
+
- project-specific image extension through `engines.<engine>.dockerfile`, `context`, `base_image`, `local_image`, `lockfiles`, and `fingerprint_paths`
|
|
29
|
+
- explicit image resolution through `COMPUTE_PYTHON_IMAGE`, `COMPUTE_R_IMAGE`, `COMPUTE_ENV`, and configured `environments`
|
|
30
|
+
|
|
31
|
+
That means the TIGIT issue is not a request to invent repo-specific Docker support from scratch. The support exists in the factory/core, but the child repo workflow must adopt it and the failure mode should be documented more clearly.
|
|
32
|
+
|
|
33
|
+
## General versus project-specific
|
|
34
|
+
|
|
35
|
+
There are two layers.
|
|
36
|
+
|
|
37
|
+
The general `unaltraweb` layer should provide the common computation contract: Quarto, Jupyter, Python, R, rendering, provenance locks, stale-output checks, no-network execution, and safe publication of generated Markdown and figures.
|
|
38
|
+
|
|
39
|
+
Each child repo should declare non-general dependencies in its own computation image. For TIGIT this may include packages or system libraries used for cartographic figures, geospatial transformations, QGIS-adjacent processing, color science utilities, or any dataset-specific helper library that does not belong in the shared image.
|
|
40
|
+
|
|
41
|
+
So the ColorBrewer example is a specific TIGIT use case, but the missing adoption path is a general unaltraweb/template concern.
|
|
42
|
+
|
|
43
|
+
## Desired child-repo pattern
|
|
44
|
+
|
|
45
|
+
A child repo should be able to keep a small, project-owned Dockerfile:
|
|
46
|
+
|
|
47
|
+
```yaml
|
|
48
|
+
# .unaltraweb/computations.yml
|
|
49
|
+
version: 1
|
|
50
|
+
enabled: true
|
|
51
|
+
source_roots:
|
|
52
|
+
- _chapters
|
|
53
|
+
- assets/quarto
|
|
54
|
+
generated_assets_root: assets/img/generated
|
|
55
|
+
engines:
|
|
56
|
+
python:
|
|
57
|
+
dockerfile: Dockerfile.compute-python
|
|
58
|
+
context: .
|
|
59
|
+
base_image: ghcr.io/dosquartsdedocs/unaltraweb-compute-python@sha256:18cb269811bd4005800382da25a480ec2bca7eac8d0501ad1ef36bad1c0f8cd9
|
|
60
|
+
local_image: tigit-compute-python:local
|
|
61
|
+
lockfiles:
|
|
62
|
+
- requirements-compute.txt
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
```dockerfile
|
|
66
|
+
ARG BASE_IMAGE
|
|
67
|
+
FROM ${BASE_IMAGE}
|
|
68
|
+
|
|
69
|
+
COPY requirements-compute.txt /tmp/requirements-compute.txt
|
|
70
|
+
RUN python3 -m pip install --no-cache-dir -r /tmp/requirements-compute.txt
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Then the authoring workflow should be:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
MCP_CONSUMER_WORKSPACE="$PWD" make -C ../unaltraweb manual-compute-image-python
|
|
77
|
+
MCP_CONSUMER_WORKSPACE="$PWD" make -C ../unaltraweb manual-compute-render COMPUTE_SOURCE=assets/quarto/color-cartography/palette-reference-swatches.qmd
|
|
78
|
+
make build
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
or through equivalent child-repo `make` targets once the template Makefile exposes the computation commands directly.
|
|
82
|
+
|
|
83
|
+
## Follow-up work
|
|
84
|
+
|
|
85
|
+
1. Verify the synced `../unaltraweb-template` `manual-compute-*` targets in a real child repository after template updates are pulled into that repository. The template now exposes direct targets and uses a local `unaltraweb` checkout for routine status/check when available.
|
|
86
|
+
2. Keep the troubleshooting note visible in child docs: if direct host `quarto render` fails with missing Jupyter packages, read-only runtime directories, or socket errors, use `manual-compute-render` instead.
|
|
87
|
+
3. Confirm the `mode: figure` path in a real child repository for computation sources outside `_chapters`, such as `assets/quarto/**.qmd`, when listed in `source_roots`.
|
|
88
|
+
4. Use figure-only Quarto sources as first-class compute records when the source exists to generate reusable SVG/PNG assets embedded in ordinary Markdown chapters.
|
|
89
|
+
5. Add a compact example to the manual documentation showing a project-specific Python computation image with `requirements-compute.txt`.
|
|
90
|
+
6. Add or verify a doctor/check message that detects executable sources in a child repo without `.unaltraweb/computations.yml`, or sources that are being kept outside configured roots.
|
|
91
|
+
|
|
92
|
+
## Figure mode now first class
|
|
93
|
+
|
|
94
|
+
Items 3 and 4 are closed in TIGIT and the factory:
|
|
95
|
+
|
|
96
|
+
- `computation_figure_images` (Jekyll plugin) rewrites Markdown/HTML image references that point at a compute source (`.qmd`, `.rmd`, `.r`, `.py`, `.ipynb`) into the source's declared `mode: figure` output. An author-owned `*.edited.svg` override wins and is never overwritten. Builds fail when the referenced source is missing or its output has not been rendered.
|
|
97
|
+
- `render.py` supports `--mode figure --stale-only` (wired as `manual_computation_render_figures` through MCP and `manual-compute-render-figures` in the factory Makefile). Rendering is explicit, and package-scaffolded build/test runs reject stale outputs. Present-but-unrecorded outputs are treated as author-managed and are never auto-replaced.
|
|
98
|
+
- TIGIT chapter `03` demonstrates the workflow with R `mode: figure` sources under `assets/quarto/data-visualization/`.
|
|
99
|
+
|
|
100
|
+
## Open design question
|
|
101
|
+
|
|
102
|
+
The computation renderer now supports an explicit `unaltraweb_compute.mode: figure` contract. It records source, inputs, declared output assets and provenance without manufacturing a chapter Markdown file.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="760" height="420" viewBox="0 0 760 420" role="img" aria-labelledby="title desc">
|
|
2
|
+
<title id="title">unaltraweb documentation workflow</title>
|
|
3
|
+
<desc id="desc">Diagram showing content, profile, gem, Docker and GitHub Actions feeding a published website.</desc>
|
|
4
|
+
<defs>
|
|
5
|
+
<linearGradient id="bg" x1="0" x2="1" y1="0" y2="1">
|
|
6
|
+
<stop offset="0" stop-color="#e8f7f3"/>
|
|
7
|
+
<stop offset="1" stop-color="#f7efe1"/>
|
|
8
|
+
</linearGradient>
|
|
9
|
+
<linearGradient id="panel" x1="0" x2="1" y1="0" y2="1">
|
|
10
|
+
<stop offset="0" stop-color="#0d4f57"/>
|
|
11
|
+
<stop offset="1" stop-color="#172033"/>
|
|
12
|
+
</linearGradient>
|
|
13
|
+
</defs>
|
|
14
|
+
<rect width="760" height="420" rx="34" fill="url(#bg)"/>
|
|
15
|
+
<rect x="70" y="62" width="620" height="296" rx="26" fill="white" stroke="#9db8c3" stroke-width="3"/>
|
|
16
|
+
<rect x="104" y="98" width="230" height="224" rx="18" fill="#f8fbfc" stroke="#b6c9d0" stroke-width="2"/>
|
|
17
|
+
<rect x="128" y="126" width="138" height="14" rx="7" fill="#9bb5bf"/>
|
|
18
|
+
<rect x="128" y="158" width="172" height="12" rx="6" fill="#c3d3d8"/>
|
|
19
|
+
<rect x="128" y="184" width="145" height="12" rx="6" fill="#c3d3d8"/>
|
|
20
|
+
<rect x="128" y="210" width="162" height="12" rx="6" fill="#c3d3d8"/>
|
|
21
|
+
<rect x="128" y="254" width="72" height="30" rx="8" fill="#2a9d8f"/>
|
|
22
|
+
<rect x="214" y="254" width="78" height="30" rx="8" fill="#517ec1"/>
|
|
23
|
+
<path d="M360 210h72" stroke="#268ca0" stroke-width="8" stroke-linecap="round"/>
|
|
24
|
+
<path d="m422 182 34 28-34 28" fill="none" stroke="#268ca0" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/>
|
|
25
|
+
<rect x="474" y="98" width="182" height="224" rx="18" fill="url(#panel)"/>
|
|
26
|
+
<text x="505" y="144" fill="#ffffff" font-family="Arial, sans-serif" font-size="24" font-weight="700">unaltraweb</text>
|
|
27
|
+
<text x="505" y="182" fill="#b8e5dc" font-family="Arial, sans-serif" font-size="15">gem + theme</text>
|
|
28
|
+
<text x="505" y="210" fill="#b8e5dc" font-family="Arial, sans-serif" font-size="15">Docker runtime</text>
|
|
29
|
+
<text x="505" y="238" fill="#b8e5dc" font-family="Arial, sans-serif" font-size="15">Actions deploy</text>
|
|
30
|
+
<rect x="505" y="268" width="118" height="28" rx="8" fill="#2aa5bf"/>
|
|
31
|
+
<text x="525" y="287" fill="#07212a" font-family="Arial, sans-serif" font-size="13" font-weight="700">publish site</text>
|
|
32
|
+
<circle cx="112" cy="350" r="5" fill="#2aa5bf" opacity="0.5"/>
|
|
33
|
+
<circle cx="134" cy="350" r="5" fill="#2aa5bf" opacity="0.5"/>
|
|
34
|
+
<circle cx="156" cy="350" r="5" fill="#2aa5bf" opacity="0.5"/>
|
|
35
|
+
<circle cx="604" cy="68" r="44" fill="#2aa5bf" opacity="0.18"/>
|
|
36
|
+
</svg>
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "json"
|
|
4
|
+
|
|
5
|
+
module Unaltraweb
|
|
6
|
+
contract_path = File.expand_path("../../src/unaltraweb_mcp/component-contract.json", __dir__)
|
|
7
|
+
VERSION = JSON.parse(File.read(contract_path)).fetch("release").fetch("version") unless const_defined?(:VERSION, false)
|
|
8
|
+
end
|
data/lib/unaltraweb.rb
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "unaltraweb/version"
|
|
4
|
+
|
|
5
|
+
%w[
|
|
6
|
+
bibliography_profiles
|
|
7
|
+
localized_visual_sources
|
|
8
|
+
callouts
|
|
9
|
+
code_blocks
|
|
10
|
+
computation_figure_images
|
|
11
|
+
vega_visual_images
|
|
12
|
+
computation_sources
|
|
13
|
+
details
|
|
14
|
+
external-posts
|
|
15
|
+
file-exists
|
|
16
|
+
figure_captions
|
|
17
|
+
google-scholar-citations
|
|
18
|
+
hide-custom-bibtex
|
|
19
|
+
inspirehep-citations
|
|
20
|
+
mermaid_mmd_images
|
|
21
|
+
content_search_index
|
|
22
|
+
manual_release_metadata
|
|
23
|
+
reproducible_build_time
|
|
24
|
+
profile-pages
|
|
25
|
+
remove-accents
|
|
26
|
+
search-data
|
|
27
|
+
theme-cache-bust
|
|
28
|
+
web_capture_images
|
|
29
|
+
].each do |plugin|
|
|
30
|
+
require File.expand_path("../_plugins/#{plugin}", __dir__)
|
|
31
|
+
end
|
data/requirements.txt
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
OUT_PATH=".cache/scimago/scimagojr.csv"
|
|
6
|
+
URL="https://raw.githubusercontent.com/ikashnitsky/sjrdata/master/data/sjr_journals.rda"
|
|
7
|
+
INPUT_PATH=""
|
|
8
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
9
|
+
|
|
10
|
+
while [[ $# -gt 0 ]]; do
|
|
11
|
+
case "$1" in
|
|
12
|
+
--out)
|
|
13
|
+
OUT_PATH="$2"
|
|
14
|
+
shift 2
|
|
15
|
+
;;
|
|
16
|
+
--url)
|
|
17
|
+
URL="$2"
|
|
18
|
+
shift 2
|
|
19
|
+
;;
|
|
20
|
+
--input)
|
|
21
|
+
INPUT_PATH="$2"
|
|
22
|
+
shift 2
|
|
23
|
+
;;
|
|
24
|
+
*)
|
|
25
|
+
echo "Unknown option: $1" >&2
|
|
26
|
+
echo "Usage: $0 [--input local.(csv|rda)] [--url SOURCE_URL] [--out .cache/scimago/scimagojr.csv]" >&2
|
|
27
|
+
exit 1
|
|
28
|
+
;;
|
|
29
|
+
esac
|
|
30
|
+
done
|
|
31
|
+
|
|
32
|
+
mkdir -p "$(dirname "$OUT_PATH")"
|
|
33
|
+
|
|
34
|
+
convert_rda_to_csv() {
|
|
35
|
+
local src_rda="$1"
|
|
36
|
+
local dst_csv="$2"
|
|
37
|
+
|
|
38
|
+
python3 - "$src_rda" "$dst_csv" <<'PY'
|
|
39
|
+
import sys
|
|
40
|
+
from pathlib import Path
|
|
41
|
+
|
|
42
|
+
src = Path(sys.argv[1])
|
|
43
|
+
dst = Path(sys.argv[2])
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
import rdata
|
|
47
|
+
except ImportError as exc:
|
|
48
|
+
print("Missing Python dependency 'rdata'. Install dependencies first:", file=sys.stderr)
|
|
49
|
+
print(" python3 -m pip install --break-system-packages -r requirements.txt", file=sys.stderr)
|
|
50
|
+
print(f"Details: {exc}", file=sys.stderr)
|
|
51
|
+
raise SystemExit(1)
|
|
52
|
+
|
|
53
|
+
obj = rdata.read_rda(str(src))
|
|
54
|
+
if not obj:
|
|
55
|
+
print(f"No objects found in RDA file: {src}", file=sys.stderr)
|
|
56
|
+
raise SystemExit(1)
|
|
57
|
+
|
|
58
|
+
_, payload = next(iter(obj.items()))
|
|
59
|
+
|
|
60
|
+
if not hasattr(payload, "columns"):
|
|
61
|
+
print("Unsupported RDA payload: expected a data.frame/tibble", file=sys.stderr)
|
|
62
|
+
raise SystemExit(1)
|
|
63
|
+
|
|
64
|
+
required = ["year", "issn", "sjr", "sjr_best_quartile", "categories"]
|
|
65
|
+
missing = [c for c in required if c not in payload.columns]
|
|
66
|
+
if missing:
|
|
67
|
+
print(f"RDA dataset missing expected columns: {missing}", file=sys.stderr)
|
|
68
|
+
raise SystemExit(1)
|
|
69
|
+
|
|
70
|
+
df = payload.copy()
|
|
71
|
+
df.to_csv(dst, index=False)
|
|
72
|
+
print(f"Converted RDA -> CSV ({len(df)} rows): {dst}")
|
|
73
|
+
PY
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if [[ -n "$INPUT_PATH" ]]; then
|
|
77
|
+
if [[ ! -f "$INPUT_PATH" ]]; then
|
|
78
|
+
echo "Input file not found: $INPUT_PATH" >&2
|
|
79
|
+
exit 1
|
|
80
|
+
fi
|
|
81
|
+
case "${INPUT_PATH,,}" in
|
|
82
|
+
*.rda)
|
|
83
|
+
convert_rda_to_csv "$INPUT_PATH" "$OUT_PATH"
|
|
84
|
+
;;
|
|
85
|
+
*.csv)
|
|
86
|
+
cp "$INPUT_PATH" "$OUT_PATH"
|
|
87
|
+
echo "Copied local Scimago CSV to $OUT_PATH"
|
|
88
|
+
;;
|
|
89
|
+
*)
|
|
90
|
+
echo "Unsupported input format: $INPUT_PATH" >&2
|
|
91
|
+
echo "Expected .csv or .rda" >&2
|
|
92
|
+
exit 1
|
|
93
|
+
;;
|
|
94
|
+
esac
|
|
95
|
+
else
|
|
96
|
+
TMP_FILE="${OUT_PATH}.download"
|
|
97
|
+
TMP_EXT="${URL##*.}"
|
|
98
|
+
echo "Downloading Scimago source from: $URL"
|
|
99
|
+
if ! curl -fsSL "$URL" -A "Mozilla/5.0" -o "$TMP_FILE"; then
|
|
100
|
+
echo "Scimago download failed from: $URL" >&2
|
|
101
|
+
echo "The upstream URL may be unavailable, moved, or blocked by the network." >&2
|
|
102
|
+
echo "Retry later or use --input path/to/local.(csv|rda)." >&2
|
|
103
|
+
rm -f "$TMP_FILE"
|
|
104
|
+
exit 1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
case "${TMP_EXT,,}" in
|
|
108
|
+
rda)
|
|
109
|
+
convert_rda_to_csv "$TMP_FILE" "$OUT_PATH"
|
|
110
|
+
rm -f "$TMP_FILE"
|
|
111
|
+
;;
|
|
112
|
+
csv)
|
|
113
|
+
mv "$TMP_FILE" "$OUT_PATH"
|
|
114
|
+
echo "Downloaded CSV to $OUT_PATH"
|
|
115
|
+
;;
|
|
116
|
+
*)
|
|
117
|
+
echo "Unknown source extension from URL: .$TMP_EXT" >&2
|
|
118
|
+
echo "Use --url ending in .csv or .rda, or pass --input." >&2
|
|
119
|
+
rm -f "$TMP_FILE"
|
|
120
|
+
exit 1
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
fi
|
|
124
|
+
|
|
125
|
+
python3 "$SCRIPT_DIR/metrics_update.py" --validate-scimago "$OUT_PATH"
|
|
126
|
+
echo "Scimago CSV ready: $OUT_PATH"
|