functualize 0.2.0__tar.gz → 0.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (380) hide show
  1. {functualize-0.2.0 → functualize-0.2.2}/CHANGELOG.md +112 -1
  2. {functualize-0.2.0 → functualize-0.2.2}/PKG-INFO +6 -1
  3. {functualize-0.2.0 → functualize-0.2.2}/README.md +5 -0
  4. {functualize-0.2.0 → functualize-0.2.2}/pyproject.toml +1 -1
  5. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/SKILL.md +1 -1
  6. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize-app/SKILL.md +1 -1
  7. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize-cli/SKILL.md +1 -1
  8. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize-skill/SKILL.md +1 -1
  9. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/__init__.py +1 -1
  10. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/main.py +6 -1
  11. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/manifest.py +19 -1
  12. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/package_ops.py +77 -18
  13. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/plugin_cmd.py +4 -1
  14. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/runtime.py +45 -2
  15. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/self_cmd.py +102 -6
  16. functualize-0.2.2/src/functualize/_cli/self_update.py +365 -0
  17. {functualize-0.2.0 → functualize-0.2.2}/.gitignore +0 -0
  18. {functualize-0.2.0 → functualize-0.2.2}/LICENSE +0 -0
  19. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/capabilities.md +0 -0
  20. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/config-and-secrets.md +0 -0
  21. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/discovery.md +0 -0
  22. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/environment.md +0 -0
  23. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/idiomatic.md +0 -0
  24. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/testing.md +0 -0
  25. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize/references/workflows.md +0 -0
  26. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize-app/references/standalone-scripts.md +0 -0
  27. {functualize-0.2.0 → functualize-0.2.2}/skills/functualize-skill/references/distribution.md +0 -0
  28. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/__main__.py +0 -0
  29. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/__init__.py +0 -0
  30. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/boot.py +0 -0
  31. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/decorators.py +0 -0
  32. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/environment.py +0 -0
  33. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/impl.py +0 -0
  34. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/models.py +0 -0
  35. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_app/state.py +0 -0
  36. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/__init__.py +0 -0
  37. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/annotation_utils.py +0 -0
  38. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/builtins.py +0 -0
  39. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/__init__.py +0 -0
  40. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/cursor_context.py +0 -0
  41. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/data.py +0 -0
  42. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/engine.py +0 -0
  43. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/flag_filtering.py +0 -0
  44. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/provenance.py +0 -0
  45. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/quote_handling.py +0 -0
  46. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/completions/shell_init.py +0 -0
  47. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/config.py +0 -0
  48. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/__init__.py +0 -0
  49. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/argument_history.py +0 -0
  50. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/config_snapshot_store.py +0 -0
  51. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/config_target.py +0 -0
  52. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/func_settings.py +0 -0
  53. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/invocation_preset.py +0 -0
  54. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/override_applicator.py +0 -0
  55. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/path_suggestion.py +0 -0
  56. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/pending_execution.py +0 -0
  57. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/resolved_value_compat.py +0 -0
  58. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/settings_schema.py +0 -0
  59. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/shortcut_generator.py +0 -0
  60. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/data/toml_writer.py +0 -0
  61. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/dispatch.py +0 -0
  62. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/info.py +0 -0
  63. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/inline_tui.py +0 -0
  64. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/introspect.py +0 -0
  65. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/orchestrator.py +0 -0
  66. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/parallel_output.py +0 -0
  67. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/pep723.py +0 -0
  68. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/__init__.py +0 -0
  69. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/cli.py +0 -0
  70. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/context.py +0 -0
  71. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/generator.py +0 -0
  72. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/registry.py +0 -0
  73. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/__init__.py +0 -0
  74. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/_shared/config.base.toml.j2 +0 -0
  75. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/_shared/config.dev.toml.j2 +0 -0
  76. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/_shared/config.prod.toml.j2 +0 -0
  77. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/_shared/gitignore.j2 +0 -0
  78. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/config.base.ini.j2 +0 -0
  79. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/config.base.toml.j2 +0 -0
  80. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-plugin/__init__.py.j2 +0 -0
  81. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-plugin/_plugin.py.j2 +0 -0
  82. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-plugin/pyproject.toml.j2 +0 -0
  83. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-plugin/test_plugin.py.j2 +0 -0
  84. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/__init__.py.j2 +0 -0
  85. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/_errors.py.j2 +0 -0
  86. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/_events.py.j2 +0 -0
  87. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/_metadata.py.j2 +0 -0
  88. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/_protocols.py.j2 +0 -0
  89. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/_types.py.j2 +0 -0
  90. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/pyproject.toml.j2 +0 -0
  91. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/domain-sdk/testing/__init__.py.j2 +0 -0
  92. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/file_plugin.py.j2 +0 -0
  93. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/README.md.j2 +0 -0
  94. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/__init__.py.j2 +0 -0
  95. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/events_job.py.j2 +0 -0
  96. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/interactive_job.py.j2 +0 -0
  97. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/main.py.j2 +0 -0
  98. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/pyproject.toml.j2 +0 -0
  99. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/full-interactivity/workflow_job.py.j2 +0 -0
  100. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/job-folder/README.md.j2 +0 -0
  101. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/job-folder/config.base.toml.j2 +0 -0
  102. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/job-folder/file_plugin.py.j2 +0 -0
  103. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/job-folder/sample_job.py.j2 +0 -0
  104. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/job.py.j2 +0 -0
  105. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/main.py.j2 +0 -0
  106. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin-project/README.md.j2 +0 -0
  107. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin-project/__init__.py.j2 +0 -0
  108. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin-project/plugin.py.j2 +0 -0
  109. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin-project/provider.py.j2 +0 -0
  110. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin-project/pyproject.toml.j2 +0 -0
  111. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin-project/renderer.py.j2 +0 -0
  112. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/plugin.py.j2 +0 -0
  113. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/pyproject.toml.j2 +0 -0
  114. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/screen.py.j2 +0 -0
  115. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/screen.tcss.j2 +0 -0
  116. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/simple/README.md.j2 +0 -0
  117. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/simple/__init__.py.j2 +0 -0
  118. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/simple/main.py.j2 +0 -0
  119. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/simple/pyproject.toml.j2 +0 -0
  120. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/simple/sample_job.py.j2 +0 -0
  121. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/scaffold/templates/standalone_job.py.j2 +0 -0
  122. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/skills.py +0 -0
  123. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/stdin_reader.py +0 -0
  124. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/__init__.py +0 -0
  125. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/app.py +0 -0
  126. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/bar.py +0 -0
  127. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/bar_items.py +0 -0
  128. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/breadcrumb_header_widget.py +0 -0
  129. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/chain_resolution.py +0 -0
  130. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/cli_arg_parser.py +0 -0
  131. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/config_diff.py +0 -0
  132. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/config_target_discovery.py +0 -0
  133. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/descriptor_fields.py +0 -0
  134. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/diff_view_widget.py +0 -0
  135. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/display_affinity.py +0 -0
  136. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/display_chrome.py +0 -0
  137. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/display_provider_discovery.py +0 -0
  138. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/display_slot.py +0 -0
  139. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/dynamic_footer.py +0 -0
  140. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/dynamic_footer_widget.py +0 -0
  141. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/dynamic_input_bar.py +0 -0
  142. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/editable_table.py +0 -0
  143. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/field_priority.py +0 -0
  144. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/focus.py +0 -0
  145. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/functualize_autocomplete.py +0 -0
  146. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/insert_mode.py +0 -0
  147. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/integration.py +0 -0
  148. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/job_execution.py +0 -0
  149. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/job_listing.py +0 -0
  150. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/key_handler.py +0 -0
  151. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/live_panel_widget.py +0 -0
  152. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/missing_args.py +0 -0
  153. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/modals/__init__.py +0 -0
  154. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/models/__init__.py +0 -0
  155. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/models/focus_state.py +0 -0
  156. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/models/panel_ring_controller.py +0 -0
  157. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/models/ring_models.py +0 -0
  158. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/models/source_chain.py +0 -0
  159. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/new_file_picker.py +0 -0
  160. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panel_host.py +0 -0
  161. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panel_live_zone.py +0 -0
  162. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panels/__init__.py +0 -0
  163. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panels/config_files.py +0 -0
  164. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panels/config_table.py +0 -0
  165. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panels/job_browser.py +0 -0
  166. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panels/ring.py +0 -0
  167. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/panels/settings_files.py +0 -0
  168. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/path_field_editor.py +0 -0
  169. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/path_suggestion_scanner.py +0 -0
  170. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/preflight_summary.py +0 -0
  171. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/settings_panel.py +0 -0
  172. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/settings_validator.py +0 -0
  173. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/shell_mode.py +0 -0
  174. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/shortcut_save_modal.py +0 -0
  175. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/smart_bar_autocomplete.py +0 -0
  176. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/source_chain_detail.py +0 -0
  177. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/source_chain_providers.py +0 -0
  178. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/sync.py +0 -0
  179. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/theme_manager.py +0 -0
  180. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/thread_marshal.py +0 -0
  181. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_cli/tui/type_hint_formatter.py +0 -0
  182. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/__init__.py +0 -0
  183. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/_emit.py +0 -0
  184. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/chain.py +0 -0
  185. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/cli_adapter.py +0 -0
  186. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/errors.py +0 -0
  187. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/interactivity.py +0 -0
  188. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/job_config.py +0 -0
  189. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/manifest.py +0 -0
  190. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/merge.py +0 -0
  191. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/protocols.py +0 -0
  192. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/providers/__init__.py +0 -0
  193. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/providers/ini.py +0 -0
  194. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/providers/toml.py +0 -0
  195. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/registry.py +0 -0
  196. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/resolved_field.py +0 -0
  197. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/roles.py +0 -0
  198. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_config/sources.py +0 -0
  199. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/__init__.py +0 -0
  200. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/ast_extractor.py +0 -0
  201. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/cached_provider.py +0 -0
  202. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/filter_factory.py +0 -0
  203. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/group_options_extractor.py +0 -0
  204. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/hierarchy.py +0 -0
  205. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/hierarchy_errors.py +0 -0
  206. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/hierarchy_validator.py +0 -0
  207. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/lazy_wrapper.py +0 -0
  208. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/naming.py +0 -0
  209. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/pipeline.py +0 -0
  210. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/pre_filter.py +0 -0
  211. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/providers.py +0 -0
  212. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/registry.py +0 -0
  213. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/schema_extractor.py +0 -0
  214. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/sync.py +0 -0
  215. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/transforms.py +0 -0
  216. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_discovery/version_resolver.py +0 -0
  217. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/__init__.py +0 -0
  218. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/ambient.py +0 -0
  219. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/__init__.py +0 -0
  220. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/invoke.py +0 -0
  221. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/job_context.py +0 -0
  222. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/live.py +0 -0
  223. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/log.py +0 -0
  224. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/perf.py +0 -0
  225. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/prompt.py +0 -0
  226. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/protocols.py +0 -0
  227. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/registry.py +0 -0
  228. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/runcontext.py +0 -0
  229. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/shell.py +0 -0
  230. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/sources.py +0 -0
  231. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/spec.py +0 -0
  232. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/state.py +0 -0
  233. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/state_store.py +0 -0
  234. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/stdin_collector.py +0 -0
  235. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/stdout.py +0 -0
  236. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/tty.py +0 -0
  237. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/workflow.py +0 -0
  238. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/capabilities/workflow_scope.py +0 -0
  239. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/context.py +0 -0
  240. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/errors.py +0 -0
  241. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/exec_policy.py +0 -0
  242. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/executor.py +0 -0
  243. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/explain.py +0 -0
  244. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/frontier.py +0 -0
  245. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/guards.py +0 -0
  246. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/job_graph.py +0 -0
  247. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/job_middleware.py +0 -0
  248. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/middleware.py +0 -0
  249. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/missing_value.py +0 -0
  250. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/preflight.py +0 -0
  251. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/resolution.py +0 -0
  252. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/result.py +0 -0
  253. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/scheduler.py +0 -0
  254. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/surface_routing.py +0 -0
  255. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/validation.py +0 -0
  256. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/workflow_runner.py +0 -0
  257. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/workflow_validation.py +0 -0
  258. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_engine/workflow_walker.py +0 -0
  259. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/__init__.py +0 -0
  260. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/_catalog_entries.py +0 -0
  261. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/_obs_types.py +0 -0
  262. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/_pattern_matcher.py +0 -0
  263. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/adapter.py +0 -0
  264. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/bus.py +0 -0
  265. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/catalog.py +0 -0
  266. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/hooks.py +0 -0
  267. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/instrumentation.py +0 -0
  268. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/introspection.py +0 -0
  269. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/middleware_stack.py +0 -0
  270. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/perf.py +0 -0
  271. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_events/tracing.py +0 -0
  272. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_gate/__init__.py +0 -0
  273. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_gate/_context.py +0 -0
  274. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_gate/_registry.py +0 -0
  275. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_gate/_resolver.py +0 -0
  276. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_gate/_strategy.py +0 -0
  277. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_gate/prompt_strategy.py +0 -0
  278. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_plugins/__init__.py +0 -0
  279. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_plugins/config.py +0 -0
  280. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_plugins/domain_metadata.py +0 -0
  281. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_plugins/domain_registry.py +0 -0
  282. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_plugins/loader.py +0 -0
  283. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/__init__.py +0 -0
  284. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/agent_epilog.py +0 -0
  285. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/cache_format.py +0 -0
  286. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/capability_names.py +0 -0
  287. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/config_class_detection.py +0 -0
  288. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/di.py +0 -0
  289. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/display_detection.py +0 -0
  290. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/entry_points.py +0 -0
  291. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/fingerprint.py +0 -0
  292. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/graph.py +0 -0
  293. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/group_options_detection.py +0 -0
  294. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/job_filter.py +0 -0
  295. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/job_schema.py +0 -0
  296. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/lazy.py +0 -0
  297. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/locator.py +0 -0
  298. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/middleware.py +0 -0
  299. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/modules.py +0 -0
  300. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/pre_filter.py +0 -0
  301. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/resilient.py +0 -0
  302. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/resolution.py +0 -0
  303. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/state_format.py +0 -0
  304. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/state_store.py +0 -0
  305. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_primitives/stdout_emitter.py +0 -0
  306. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/__init__.py +0 -0
  307. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/annotations.py +0 -0
  308. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/commands.py +0 -0
  309. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/descriptors.py +0 -0
  310. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/enums.py +0 -0
  311. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/errors.py +0 -0
  312. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/exit_codes.py +0 -0
  313. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/from_job.py +0 -0
  314. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/group_options.py +0 -0
  315. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/input_modes.py +0 -0
  316. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/interactivity.py +0 -0
  317. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/job_declaration.py +0 -0
  318. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/naming.py +0 -0
  319. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/protocols.py +0 -0
  320. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/redaction.py +0 -0
  321. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/settings.py +0 -0
  322. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/shell.py +0 -0
  323. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/stdout.py +0 -0
  324. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/_types/workflow.py +0 -0
  325. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/__init__.py +0 -0
  326. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/_workflow_resume.py +0 -0
  327. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/__init__.py +0 -0
  328. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/_validation.py +0 -0
  329. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/cli.py +0 -0
  330. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/click_params.py +0 -0
  331. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/lazy_command.py +0 -0
  332. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/surface_gate.py +0 -0
  333. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/adapters/tui.py +0 -0
  334. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/commands.py +0 -0
  335. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/config.py +0 -0
  336. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/core.py +0 -0
  337. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/fallback.py +0 -0
  338. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/presets.py +0 -0
  339. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/app/utils.py +0 -0
  340. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/__init__.py +0 -0
  341. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_invoke.py +0 -0
  342. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_job_context.py +0 -0
  343. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_log.py +0 -0
  344. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_middleware.py +0 -0
  345. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_perf.py +0 -0
  346. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_prompt.py +0 -0
  347. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_protocols.py +0 -0
  348. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_runcontext.py +0 -0
  349. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_shell.py +0 -0
  350. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_sources.py +0 -0
  351. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_state.py +0 -0
  352. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_state_store.py +0 -0
  353. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_stdout.py +0 -0
  354. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/_workflow_scope.py +0 -0
  355. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/capabilities.py +0 -0
  356. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/context.py +0 -0
  357. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/decorators.py +0 -0
  358. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/job/markers.py +0 -0
  359. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/plugin/__init__.py +0 -0
  360. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/plugin/protocols.py +0 -0
  361. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/py.typed +0 -0
  362. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/testing/__init__.py +0 -0
  363. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/testing/builder.py +0 -0
  364. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/testing/doubles.py +0 -0
  365. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/testing/shell.py +0 -0
  366. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/testing/stdout.py +0 -0
  367. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/types/__init__.py +0 -0
  368. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/__init__.py +0 -0
  369. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/_prompt_modal.py +0 -0
  370. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/display.py +0 -0
  371. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/fullscreen/__init__.py +0 -0
  372. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/fullscreen/app.py +0 -0
  373. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/fullscreen/screens.py +0 -0
  374. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/fullscreen/widgets.py +0 -0
  375. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/py.typed +0 -0
  376. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/stdout_surface.py +0 -0
  377. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/ui/textual_app.py +0 -0
  378. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/workflow/__init__.py +0 -0
  379. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/workflow/_decorator.py +0 -0
  380. {functualize-0.2.0 → functualize-0.2.2}/src/functualize/workflow/_validation.py +0 -0
@@ -7,6 +7,115 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.2] - 2026-09-04
11
+
12
+ ### Added — the standalone binary manages itself
13
+
14
+ `func builtin self update` on a standalone install now **replaces the binary**.
15
+ It reads the release source baked into the distribution, fetches that release,
16
+ verifies the archive against its `SHA256SUMS` *before unpacking*, and swaps the
17
+ file through a staged `os.replace` in the target's own directory — atomic, so
18
+ an interrupted update leaves one working binary or the other, never a truncated
19
+ file. An archive the checksums file does not mention is refused exactly like one
20
+ that fails to match.
21
+
22
+ `self install`, `self uninstall`, `plugin install` and `plugin uninstall` work
23
+ on a standalone install too, through the bundled interpreter's own pip. A binary
24
+ is the install method for a machine with no Python toolchain, so requiring `uv`
25
+ on `PATH` to add a dependency would defeat it — and the baked distribution ships
26
+ no uv. `self python -- ...` remains the escape hatch; `self uv` is the one
27
+ command a standalone install does not have.
28
+
29
+ The release source is baked, never hard-coded. An application that builds its
30
+ own PyApp binary over functualize writes its own `standalone-release.json` and
31
+ points its users at its own releases; its absence refuses cleanly rather than
32
+ offering functualize's artifact to somebody else's users.
33
+
34
+ ### Fixed — the binaries 0.2.1 announced and could not build
35
+
36
+ **All seven `binaries` jobs failed on 0.2.1.** The reported error was PyApp
37
+ panicking without `PYAPP_DISTRIBUTION_PYTHON_PATH`, but fixing that alone was
38
+ not enough: building a binary locally showed the baked artifact was unusable in
39
+ a second, quieter way.
40
+
41
+ - **A virtual environment is not a distribution.** The bake produced
42
+ `uv venv --relocatable`, whose `bin/python` is a symlink to the interpreter it
43
+ was created from and whose standard library lives in that interpreter's
44
+ prefix. Unpacked on another machine it contains no Python at all, and the
45
+ binary died on launch with `project execution failed / No such file or
46
+ directory (os error 2)`. The bake now copies the python-build-standalone
47
+ installation itself.
48
+
49
+ - **uv installs a workspace root as editable by default**, writing a `.pth`
50
+ that points at the build machine and leaving no package in site-packages — a
51
+ binary that starts and then imports nothing. The bake passes `--no-editable`
52
+ and asserts no editable `.pth` survives.
53
+
54
+ - **`uv python install` offers only the host libc's distributions**, so a musl
55
+ target baked on a glibc runner would embed a glibc interpreter. The musl bakes
56
+ run inside Alpine, with `build-base` installed because aarch64-musl has no
57
+ published wheel for one dependency.
58
+
59
+ - **The size-measurement step failed six working builds.** `ls a b` exits
60
+ non-zero when one of the two names is absent, and under `set -o pipefail`
61
+ that failed the job *after* a successful ten-minute build.
62
+
63
+ - **A `python.exe` at the distribution root**, not under `Scripts/`, is the
64
+ Windows layout for a standalone install.
65
+
66
+ ### Fixed — a standalone install could not be identified
67
+
68
+ PyApp launches the application as `python -c "..."`, so `argv[0]` is the literal
69
+ string `-c`. Self-management reverse-maps that to a distribution, found nothing,
70
+ and read a healthy binary as a degraded install: every mutating command refused
71
+ with exit `3`, `self doctor` reported "self-management is unavailable here", and
72
+ the installation registry recorded `<prefix>/bin/-c` — a path that never existed
73
+ — which it then correctly reported as stale on the run that created it.
74
+
75
+ Identity now comes from PyApp itself, which supplies the executable's absolute
76
+ path in `PYAPP` when built with `PYAPP_PASS_LOCATION=1`. A standalone install is
77
+ no longer degraded for want of an owning distribution: it has none by
78
+ construction, because it is a file rather than a package.
79
+
80
+ `self update` previously delegated to `func pyapp update`. That command does not
81
+ work on a pre-baked binary: PyApp hides it unless `PYAPP_EXPOSE_UPDATE=1`,
82
+ refuses it outright under `PYAPP_SKIP_INSTALL=1`, and would `pip install
83
+ --upgrade` from an index if it ran — dissolving the offline-complete
84
+ distribution the binary exists to be.
85
+
86
+ `install.sh` and `install.ps1` work from **this** release onward; 0.2.1's note
87
+ to that effect was premature, as it shipped no binaries.
88
+
89
+ ## [0.2.1] - 2026-09-04
90
+
91
+ ### Fixed — the release pipeline that could not deliver 0.2.0's binaries
92
+
93
+ **No library changes.** This release exists to ship the standalone binaries that
94
+ 0.2.0 announced and could not produce. If you install from PyPI, 0.2.1 and 0.2.0
95
+ are the same code.
96
+
97
+ The binary pipeline shipped in 0.2.0 and ran for the first time on its tag —
98
+ these stages cannot execute before a tag exists, so the tag *was* the first test.
99
+ Three defects surfaced:
100
+
101
+ - **The `macos-13` runner label was retired on 2025-12-04.** A workflow naming a
102
+ dead label does not fail — it queues forever. The release sat "in progress"
103
+ for an hour with no runner ever assigned. Now `macos-15-intel`, which is the
104
+ last x86_64 macOS image GitHub will offer.
105
+
106
+ - **The binary and checksum stages required their entire upstream matrix to
107
+ succeed.** `needs.<job>.result` is the aggregate across a matrix, so one
108
+ target failing skipped all seven — `fail-fast: false` buys nothing across a
109
+ stage boundary. A broken platform now costs its own binary and nothing else.
110
+
111
+ - **The GitHub Release depended on the binaries.** A failed binary build
112
+ therefore skipped the release entirely, leaving PyPI with the version and
113
+ GitHub with a bare tag. It is now gated on the PyPI publish alone; the wheel
114
+ is the primary artifact and the binaries ride along.
115
+
116
+ `install.sh` and `install.ps1` work from this release onward. 0.2.0 remains a
117
+ PyPI-only release.
118
+
10
119
  ## [0.2.0] - 2026-09-04
11
120
 
12
121
  ### Added — a standalone binary, and installations that manage themselves
@@ -1065,7 +1174,9 @@ function knowing which.
1065
1174
  through the injected `Log` is deferred to a later release.
1066
1175
  *(Fixed in 0.1.1.)*
1067
1176
 
1068
- [Unreleased]: https://github.com/raicing-ai/functualize/compare/v0.2.0...HEAD
1177
+ [Unreleased]: https://github.com/raicing-ai/functualize/compare/v0.2.2...HEAD
1178
+ [0.2.2]: https://github.com/raicing-ai/functualize/compare/v0.2.1...v0.2.2
1179
+ [0.2.1]: https://github.com/raicing-ai/functualize/compare/v0.2.0...v0.2.1
1069
1180
  [0.2.0]: https://github.com/raicing-ai/functualize/compare/v0.1.3...v0.2.0
1070
1181
  [0.1.3]: https://github.com/raicing-ai/functualize/compare/v0.1.2...v0.1.3
1071
1182
  [0.1.2]: https://github.com/raicing-ai/functualize/compare/v0.1.1...v0.1.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: functualize
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Summary: A reusable Python CLI framework with auto-discovery, structured run context, layered configuration, and TUI integration
5
5
  Project-URL: Homepage, https://github.com/raicing-ai/functualize
6
6
  Project-URL: Repository, https://github.com/raicing-ai/functualize
@@ -156,6 +156,11 @@ self update` then restores anything you had added — including packages install
156
156
  > binary, a `uv tool` install, a `pipx` install, or a project checkout. A bare `pip install`
157
157
  > into a system interpreter is not self-managing: the command prints guidance, changes
158
158
  > nothing, and exits `3`. `func builtin self doctor` tells you which kind you have.
159
+ >
160
+ > The standalone binary updates by **replacing itself**: it fetches the release for its own
161
+ > platform, verifies it against the release's `SHA256SUMS` before unpacking, and swaps the
162
+ > file. Packages you had added are reinstalled into the new distribution afterwards — a new
163
+ > binary unpacks a new environment, so they do not carry over on their own.
159
164
 
160
165
  ### How `func` finds jobs
161
166
 
@@ -107,6 +107,11 @@ self update` then restores anything you had added — including packages install
107
107
  > binary, a `uv tool` install, a `pipx` install, or a project checkout. A bare `pip install`
108
108
  > into a system interpreter is not self-managing: the command prints guidance, changes
109
109
  > nothing, and exits `3`. `func builtin self doctor` tells you which kind you have.
110
+ >
111
+ > The standalone binary updates by **replacing itself**: it fetches the release for its own
112
+ > platform, verifies it against the release's `SHA256SUMS` before unpacking, and swaps the
113
+ > file. Packages you had added are reinstalled into the new distribution afterwards — a new
114
+ > binary unpacks a new environment, so they do not carry over on their own.
110
115
 
111
116
  ### How `func` finds jobs
112
117
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "functualize"
3
- version = "0.2.0"
3
+ version = "0.2.2"
4
4
  description = "A reusable Python CLI framework with auto-discovery, structured run context, layered configuration, and TUI integration"
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -12,7 +12,7 @@ description: >
12
12
  upgrading, or configuring the `func` tool itself — use functualize-cli.
13
13
  license: MIT
14
14
  metadata:
15
- version: "0.2.0"
15
+ version: "0.2.2"
16
16
  project: functualize
17
17
  ---
18
18
 
@@ -11,7 +11,7 @@ description: >
11
11
  ready" or well tested, or when packaging a functualize CLI for distribution.
12
12
  license: MIT
13
13
  metadata:
14
- version: "0.2.0"
14
+ version: "0.2.2"
15
15
  project: functualize
16
16
  ---
17
17
 
@@ -14,7 +14,7 @@ description: >
14
14
  skill.
15
15
  license: MIT
16
16
  metadata:
17
- version: "0.2.0"
17
+ version: "0.2.2"
18
18
  project: functualize
19
19
  ---
20
20
 
@@ -10,7 +10,7 @@ description: >
10
10
  real work, or when asked how to share or publish a skill.
11
11
  license: MIT
12
12
  metadata:
13
- version: "0.2.0"
13
+ version: "0.2.2"
14
14
  project: functualize
15
15
  ---
16
16
 
@@ -1,6 +1,6 @@
1
1
  """Functualize - A reusable Python CLI framework."""
2
2
 
3
- __version__ = "0.2.0"
3
+ __version__ = "0.2.2"
4
4
 
5
5
  from functualize._config.job_config import JobConfigView
6
6
  from functualize._gate import GateContext, GateResolver, GateStrategy
@@ -1758,7 +1758,12 @@ def _register_this_installation() -> None:
1758
1758
  # than imported so the warm path stays free of that module's dataclass
1759
1759
  # codegen. `tests/_cli/test_manifest.py` asserts the two agree.
1760
1760
  argv0 = sys.argv[0] if sys.argv else ""
1761
- if not argv0:
1761
+ pyapp = os.environ.get("PYAPP", "")
1762
+ if pyapp and pyapp != "1":
1763
+ # A standalone binary: `argv0` is `-c` and useless. See
1764
+ # `resolve_binary_path`.
1765
+ binary_path = str(Path(pyapp).resolve())
1766
+ elif not argv0:
1762
1767
  binary_path = ""
1763
1768
  elif "/" in argv0 or "\\" in argv0:
1764
1769
  binary_path = str(Path(argv0).resolve())
@@ -164,9 +164,18 @@ class Manifest:
164
164
  }
165
165
 
166
166
 
167
- def resolve_binary_path(argv0: str, executable: str) -> str:
167
+ def resolve_binary_path(
168
+ argv0: str, executable: str, standalone_binary: str | None = None
169
+ ) -> str:
168
170
  """A stable identity for "this installation", whatever `argv[0]` looks like.
169
171
 
172
+ `standalone_binary` wins outright when supplied. PyApp launches a standalone
173
+ binary as `python -c "..."`, so `argv[0]` is the literal string `-c` and
174
+ every derivation below produces `<prefix>/bin/-c` -- a path that has never
175
+ existed, which the registry then correctly reports as stale on the very run
176
+ that recorded it. PyApp hands over the real executable path in `PYAPP`
177
+ (built with `PYAPP_PASS_LOCATION=1`), and that is the only honest answer.
178
+
170
179
  `sys.argv[0]` is not stable across invocation styles: a direct call gives an
171
180
  absolute path, while `uv run func` gives the bare name `func`. Recording it
172
181
  raw registers one installation twice, and then reports the bare-name copy as
@@ -181,6 +190,15 @@ def resolve_binary_path(argv0: str, executable: str) -> str:
181
190
  avoid importing this module; `tests/_cli/test_manifest.py` asserts the two
182
191
  agree.
183
192
  """
193
+ if standalone_binary:
194
+ # Substituted for `argv0` rather than returned directly, so it goes
195
+ # through the same normalization. PyApp's `PYAPP` is always absolute and
196
+ # falls out of the separator branch unchanged; a bare name -- which is
197
+ # what the `FUNCTUALIZE_RUNTIME=standalone` override sees -- must still
198
+ # resolve against the interpreter's directory and not the working one,
199
+ # or this disagrees with `main.py`'s warm path and the registry records
200
+ # the same installation twice.
201
+ argv0 = standalone_binary
184
202
  if not argv0:
185
203
  return ""
186
204
  if "/" in argv0 or "\\" in argv0:
@@ -38,11 +38,12 @@ from functualize._cli.runtime import Detection, InstallMode
38
38
  from functualize.app.utils import ExitCode
39
39
 
40
40
  if TYPE_CHECKING:
41
- from collections.abc import Callable, Iterable, Sequence
41
+ from collections.abc import Callable, Iterable, Mapping, Sequence
42
42
 
43
43
  __all__ = [
44
44
  "LossyReceiptError",
45
45
  "MissingToolError",
46
+ "StandaloneUpdateError",
46
47
  "Requirement",
47
48
  "announce",
48
49
  "capture_environment",
@@ -510,6 +511,24 @@ def owned_python() -> str:
510
511
  # ---------------------------------------------------------------------------
511
512
 
512
513
 
514
+ class StandaloneUpdateError(Exception):
515
+ """``self update`` on a standalone install is not a subprocess.
516
+
517
+ Every other mode delegates to a package manager, so its update is a command
518
+ tuple. A standalone binary is a single file, and updating it means
519
+ downloading a release and replacing that file -- work that happens
520
+ in-process, in :mod:`functualize._cli.self_update`. Raised rather than
521
+ returned so a caller that forgets to handle it fails loudly instead of
522
+ running an empty command list and reporting success.
523
+
524
+ PyApp's own updater is not an option: it is hidden unless
525
+ ``PYAPP_EXPOSE_UPDATE=1``, refuses outright under ``PYAPP_SKIP_INSTALL=1``
526
+ (`"Cannot update as installation is disabled"`), and would ``pip install
527
+ --upgrade`` from an index if it ran -- replacing the offline-complete
528
+ environment the binary exists to be.
529
+ """
530
+
531
+
513
532
  def update_commands(
514
533
  detection: Detection, binary_path: str
515
534
  ) -> tuple[tuple[str, ...], ...]:
@@ -526,15 +545,23 @@ def update_commands(
526
545
  backstop that keeps a refusal from silently becoming a command.
527
546
  MissingToolError: the mode's manager is not installed.
528
547
  """
548
+ # Standalone is checked *before* the owner guard. It has no owning
549
+ # distribution by construction -- it is a file, not a package -- so the
550
+ # guard that protects every other mode from acting on a name it could not
551
+ # resolve would refuse every healthy binary.
552
+ if detection.mode is InstallMode.STANDALONE:
553
+ if detection.standalone_binary is None:
554
+ raise ValueError(
555
+ "standalone installations cannot be updated without knowing "
556
+ "their own path"
557
+ )
558
+ raise StandaloneUpdateError(detection.standalone_binary)
559
+
529
560
  distribution = detection.owning_distribution
530
561
  if detection.degraded or distribution is None:
531
562
  raise ValueError(f"{detection.mode.value} installations are not self-managing")
532
563
 
533
564
  match detection.mode:
534
- case InstallMode.STANDALONE:
535
- # PyApp's own updater, reachable at the renamed self-command
536
- # (`PYAPP_SELF_COMMAND=pyapp`). It replaces the binary in place.
537
- return ((binary_path, "pyapp", "update"),)
538
565
  case InstallMode.TOOL_UV:
539
566
  return ((resolve_uv(), "tool", "upgrade", distribution),)
540
567
  case InstallMode.TOOL_PIPX:
@@ -562,18 +589,14 @@ def install_commands(detection: Detection, package: str) -> tuple[tuple[str, ...
562
589
  MissingToolError: the mode's manager is not installed.
563
590
  LossyReceiptError: a uv receipt could not be reproduced faithfully.
564
591
  """
592
+ if detection.mode is InstallMode.STANDALONE:
593
+ return ((*_bundled_pip(), "install", package),)
594
+
565
595
  distribution = detection.owning_distribution
566
596
  if detection.degraded or distribution is None:
567
597
  raise ValueError(f"{detection.mode.value} installations are not self-managing")
568
598
 
569
599
  match detection.mode:
570
- case InstallMode.STANDALONE:
571
- # `--python` targets the bundled interpreter explicitly. Without it
572
- # uv picks a Python by its own discovery rules, which in a shell
573
- # with an activated venv is somebody else's environment.
574
- return (
575
- (resolve_uv(), "pip", "install", "--python", owned_python(), package),
576
- )
577
600
  case InstallMode.TOOL_UV:
578
601
  uv = resolve_uv()
579
602
  receipt = read_receipt(Path(sys.prefix))
@@ -597,15 +620,14 @@ def uninstall_commands(
597
620
  MissingToolError: the mode's manager is not installed.
598
621
  LossyReceiptError: a uv receipt could not be reproduced faithfully.
599
622
  """
623
+ if detection.mode is InstallMode.STANDALONE:
624
+ return ((*_bundled_pip(), "uninstall", "-y", package),)
625
+
600
626
  distribution = detection.owning_distribution
601
627
  if detection.degraded or distribution is None:
602
628
  raise ValueError(f"{detection.mode.value} installations are not self-managing")
603
629
 
604
630
  match detection.mode:
605
- case InstallMode.STANDALONE:
606
- return (
607
- (resolve_uv(), "pip", "uninstall", "--python", owned_python(), package),
608
- )
609
631
  case InstallMode.TOOL_UV:
610
632
  uv = resolve_uv()
611
633
  receipt = read_receipt(Path(sys.prefix))
@@ -638,16 +660,53 @@ def render(argv: Sequence[str]) -> str:
638
660
  return " ".join(parts)
639
661
 
640
662
 
641
- def script_name() -> str:
663
+ def _bundled_pip() -> tuple[str, ...]:
664
+ """The bundled interpreter's own pip, as a command prefix.
665
+
666
+ Not uv. A standalone binary is the install method for a machine with no
667
+ Python toolchain, so requiring ``uv`` on ``PATH`` before a package can be
668
+ added defeats the reason it exists -- and PyApp's baked distribution ships
669
+ no uv, so ``resolve_uv()`` would raise on the very install method that
670
+ cannot fix it. pip is present in the distribution and the bake asserts so.
671
+
672
+ ``-m pip`` rather than the ``pip`` script: the script's shebang is written
673
+ at build time and points at the *build machine's* path, which is not where
674
+ the distribution unpacks.
675
+ """
676
+ return (owned_python(), "-m", "pip")
677
+
678
+
679
+ def script_name(environ: Mapping[str, str] | None = None) -> str:
642
680
  """The console script the user actually typed, for use in guidance.
643
681
 
644
682
  Never a hard-coded ``func``: in a consumer application this text has to
645
683
  name *that* application's script or it tells the user to run a command they
646
684
  do not have.
685
+
686
+ And never ``-c``. PyApp launches a standalone binary as ``python -c "..."``,
687
+ so ``argv[0]`` is that literal string and guidance built from it reads
688
+ ``Run `-c builtin self doctor` `` -- a command nobody can type. PyApp
689
+ supplies the real name in ``PYAPP_COMMAND_NAME``; falling back to the
690
+ binary's own basename covers a build that did not expose one.
647
691
  """
692
+ env = os.environ if environ is None else environ
693
+
694
+ exposed = env.get("PYAPP_COMMAND_NAME", "")
695
+ if exposed:
696
+ return exposed
697
+
648
698
  argv0 = sys.argv[0] if sys.argv else ""
649
699
  name = argv0.replace("\\", "/").rsplit("/", 1)[-1]
650
- return name or "func"
700
+ if name and name != "-c":
701
+ return name
702
+
703
+ binary = env.get("PYAPP", "")
704
+ if binary and binary != "1":
705
+ basename = binary.replace("\\", "/").rsplit("/", 1)[-1]
706
+ if basename:
707
+ return basename
708
+
709
+ return "func"
651
710
 
652
711
 
653
712
  def refuse(detection: Detection, what: str) -> NoReturn:
@@ -174,10 +174,13 @@ def _binary_and_config() -> tuple[str, Path]:
174
174
  import sys
175
175
 
176
176
  from functualize._cli import manifest
177
+ from functualize._cli.runtime import detect_from_process
177
178
  from functualize.app.utils import resolve_user_config_dir
178
179
 
179
180
  binary = manifest.resolve_binary_path(
180
- sys.argv[0] if sys.argv else "", sys.executable
181
+ sys.argv[0] if sys.argv else "",
182
+ sys.executable,
183
+ detect_from_process().standalone_binary,
181
184
  )
182
185
  return binary, resolve_user_config_dir()
183
186
 
@@ -90,10 +90,23 @@ class Detection:
90
90
  #: wrong-owner failure this module exists to prevent, so ``None`` forces
91
91
  #: the refusal path.
92
92
  owning_distribution: str | None
93
+ #: The standalone binary's own absolute path, as PyApp reports it in
94
+ #: ``PYAPP`` when built with ``PYAPP_PASS_LOCATION=1``. ``None`` for every
95
+ #: other mode, and for a standalone binary built without that flag or whose
96
+ #: ``current_exe()`` lookup failed.
97
+ standalone_binary: str | None = None
93
98
 
94
99
  @property
95
100
  def degraded(self) -> bool:
96
101
  """Either the environment or the owner is unusable for management."""
102
+ if self.mode is InstallMode.STANDALONE:
103
+ # A standalone binary has no owning distribution *by construction*
104
+ # -- it is a file, not a package -- so the absence that degrades
105
+ # every other mode says nothing here. What a mutating command needs
106
+ # is the executable to act on, and PyApp launches the application
107
+ # through `python -c`, so `argv[0]` is the literal string `-c` and
108
+ # can never supply it. The binary's own path is the discriminator.
109
+ return self.standalone_binary is None
97
110
  return self.mode.degraded or self.owning_distribution is None
98
111
 
99
112
 
@@ -233,9 +246,33 @@ def detect(
233
246
  return Detection(
234
247
  mode=_detect_mode(prefix, base_prefix, environ, cwd),
235
248
  owning_distribution=_owning_distribution(argv0),
249
+ standalone_binary=_standalone_binary(environ, argv0),
236
250
  )
237
251
 
238
252
 
253
+ def _standalone_binary(environ: Mapping[str, str], argv0: str) -> str | None:
254
+ """The running executable's path, for a standalone installation.
255
+
256
+ ``PYAPP`` holds the absolute path only when the binary was built with
257
+ ``PYAPP_PASS_LOCATION=1``; older builds set the literal ``"1"`` and a build
258
+ whose ``current_exe()`` failed sets the empty string. Both are "unknown",
259
+ not a path — returning ``"1"`` here would have every mutating command
260
+ operate on a file named ``1`` in the working directory.
261
+
262
+ ``argv0`` is the second rung, and it is not a fallback guess. Inside a real
263
+ PyApp binary it is the literal string ``-c`` and this rung cannot fire. It
264
+ fires only when the mode was pinned with ``FUNCTUALIZE_RUNTIME=standalone``
265
+ — where ``argv0`` genuinely *is* the command that ran — which is what keeps
266
+ that override useful rather than a way to manufacture a degraded install.
267
+ """
268
+ value = environ.get("PYAPP", "")
269
+ if value and value != "1":
270
+ return value
271
+ if argv0 and argv0 != "-c":
272
+ return argv0
273
+ return None
274
+
275
+
239
276
  def _detect_mode(
240
277
  prefix: str,
241
278
  base_prefix: str,
@@ -254,8 +291,14 @@ def _detect_mode(
254
291
  f"Expected one of: {', '.join(m.value for m in InstallMode)}"
255
292
  ) from None
256
293
 
257
- # 2. The binary announces itself: PyApp injects PYAPP=1 into its runtime.
258
- if environ.get("PYAPP") or environ.get("PYAPP_COMMAND_NAME"):
294
+ # 2. The binary announces itself: PyApp injects PYAPP into its runtime --
295
+ # the executable's own path under `PYAPP_PASS_LOCATION=1`, otherwise
296
+ # "1". Membership, not truthiness: PyApp sets it to the *empty string*
297
+ # when `current_exe()` fails (`distribution.rs:56`), and a binary that
298
+ # cannot name itself is still a standalone binary. Reading that as
299
+ # "not standalone" would send it down the project or unknown rungs and
300
+ # describe it as something it is not.
301
+ if "PYAPP" in environ or environ.get("PYAPP_COMMAND_NAME"):
259
302
  return InstallMode.STANDALONE
260
303
 
261
304
  prefix_path = Path(prefix)
@@ -39,7 +39,7 @@ from pathlib import Path
39
39
 
40
40
  import click
41
41
 
42
- from functualize._cli.runtime import Detection, detect_from_process
42
+ from functualize._cli.runtime import Detection, InstallMode, detect_from_process
43
43
  from functualize.app.utils import ExitCode
44
44
 
45
45
  # `manifest` and `package_ops` are imported *inside* the commands that need
@@ -154,7 +154,37 @@ def _check_install(detection: Detection) -> list[Check]:
154
154
  checks = [
155
155
  Check("install-mode", CheckStatus.INFO, detection.mode.value),
156
156
  ]
157
- if detection.owning_distribution is None:
157
+ if detection.mode is InstallMode.STANDALONE:
158
+ # Not a warning, and not "unknown". A standalone binary has no owning
159
+ # distribution *by construction* -- it is a file, not a package -- so
160
+ # reporting the absence as a fault would tell a healthy installation
161
+ # that self-management is unavailable, which it is not. What identifies
162
+ # it is the executable, and that is worth showing.
163
+ checks.append(
164
+ Check(
165
+ "owning-distribution",
166
+ CheckStatus.INFO,
167
+ "not applicable — a standalone binary manages itself",
168
+ )
169
+ )
170
+ if detection.standalone_binary is None:
171
+ checks.append(
172
+ Check(
173
+ "binary-path",
174
+ CheckStatus.WARNING,
175
+ "this binary cannot determine its own location",
176
+ remedy=(
177
+ "It was built without PYAPP_PASS_LOCATION=1, so there is "
178
+ "nothing for `self update` to replace. Reinstall from a "
179
+ "current release."
180
+ ),
181
+ )
182
+ )
183
+ else:
184
+ checks.append(
185
+ Check("binary-path", CheckStatus.OK, detection.standalone_binary)
186
+ )
187
+ elif detection.owning_distribution is None:
158
188
  checks.append(
159
189
  Check(
160
190
  "owning-distribution",
@@ -470,7 +500,11 @@ def doctor(output_format: str) -> None:
470
500
  def _this_binary() -> str:
471
501
  from functualize._cli import manifest
472
502
 
473
- return manifest.resolve_binary_path(sys.argv[0] if sys.argv else "", sys.executable)
503
+ return manifest.resolve_binary_path(
504
+ sys.argv[0] if sys.argv else "",
505
+ sys.executable,
506
+ detect_from_process().standalone_binary,
507
+ )
474
508
 
475
509
 
476
510
  def _config_dir() -> Path:
@@ -496,9 +530,18 @@ def update(assume_yes: bool) -> None:
496
530
  package_ops.refuse(detection, "update")
497
531
 
498
532
  binary = _this_binary()
499
- commands = package_ops.plan_or_exit(
500
- lambda: package_ops.update_commands(detection, binary)
501
- )
533
+ try:
534
+ commands = package_ops.plan_or_exit(
535
+ lambda: package_ops.update_commands(detection, binary)
536
+ )
537
+ except package_ops.StandaloneUpdateError:
538
+ # A standalone binary has no package manager to delegate to: updating
539
+ # it means fetching a release and replacing one file, which happens
540
+ # in-process. Raised rather than returned as an empty command list, so
541
+ # forgetting this branch fails loudly instead of reporting success
542
+ # having done nothing.
543
+ raise SystemExit(_update_standalone(binary, assume_yes)) from None
544
+
502
545
  package_ops.announce(commands, assume_yes)
503
546
 
504
547
  config_dir = _config_dir()
@@ -529,6 +572,59 @@ def update(assume_yes: bool) -> None:
529
572
  _reconcile(detection, config_dir, binary, before)
530
573
 
531
574
 
575
+ def _update_standalone(binary: str, assume_yes: bool) -> int:
576
+ """`self update` for a standalone binary. Returns the process exit code.
577
+
578
+ Reconciliation is deliberately *not* the in-process kind the other modes
579
+ use. Replacing the executable means the next launch unpacks a different
580
+ distribution at a different path, so packages added to the old one are not
581
+ "removed by an upgrade" -- they are in a directory nothing will look at
582
+ again. The new binary is therefore asked to install them into its own
583
+ distribution, which it can only do after it has unpacked one.
584
+ """
585
+ import functualize
586
+ from functualize._cli import manifest, self_update
587
+
588
+ config_dir = _config_dir()
589
+ # Captured before the replacement, for the same reason every other mode
590
+ # persists first: an update interrupted between the two loses the record of
591
+ # what to put back.
592
+ keep = sorted(set(manifest.recorded_additions(config_dir, binary)))
593
+
594
+ code = self_update.perform(
595
+ binary=Path(binary),
596
+ prefix=Path(sys.prefix),
597
+ current_version=functualize.__version__,
598
+ assume_yes=assume_yes,
599
+ echo=click.echo,
600
+ confirm=click.confirm,
601
+ )
602
+ if code != int(ExitCode.OK) or not keep:
603
+ return code
604
+
605
+ click.echo(f"Reinstalling {len(keep)} package(s) into the new distribution.")
606
+ failed: list[str] = []
607
+ for package in keep:
608
+ result = subprocess.run( # noqa: S603 - argv list, no shell
609
+ [binary, "builtin", "self", "install", package, "--yes"],
610
+ check=False,
611
+ )
612
+ if result.returncode != 0:
613
+ failed.append(package)
614
+
615
+ for package in failed:
616
+ click.echo(f" could not reinstall {package}", err=True)
617
+ if failed:
618
+ # Reported, not fatal: the update itself succeeded, and turning one
619
+ # unreachable package into a failed update would misdescribe the state
620
+ # of the installation.
621
+ click.echo(
622
+ f"{len(failed)} package(s) need reinstalling by hand.",
623
+ err=True,
624
+ )
625
+ return int(ExitCode.OK)
626
+
627
+
532
628
  def _reconcile(
533
629
  detection: Detection,
534
630
  config_dir: Path,