pipulate 2.0__tar.gz → 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 (90) hide show
  1. pipulate-2.2/MANIFEST.in +5 -0
  2. {pipulate-2.0 → pipulate-2.2}/PKG-INFO +25 -18
  3. {pipulate-2.0 → pipulate-2.2}/README.md +21 -14
  4. {pipulate-2.0 → pipulate-2.2}/__init__.py +9 -5
  5. {pipulate-2.0 → pipulate-2.2}/apps/010_introduction.py +33 -12
  6. {pipulate-2.0 → pipulate-2.2}/apps/015_config.py +34 -25
  7. {pipulate-2.0 → pipulate-2.2}/apps/020_profiles.py +3 -11
  8. {pipulate-2.0 → pipulate-2.2}/apps/030_roles.py +17 -268
  9. {pipulate-2.0 → pipulate-2.2}/apps/040_hello_workflow.py +8 -14
  10. {pipulate-2.0 → pipulate-2.2}/apps/060_tasks.py +2 -5
  11. {pipulate-2.0 → pipulate-2.2}/apps/070_history.py +4 -4
  12. {pipulate-2.0 → pipulate-2.2}/apps/110_parameter_buster.py +20 -20
  13. {pipulate-2.0 → pipulate-2.2}/apps/120_link_graph.py +22 -22
  14. {pipulate-2.0 → pipulate-2.2}/apps/130_gap_analysis.py +8 -9
  15. {pipulate-2.0 → pipulate-2.2}/apps/200_workflow_genesis.py +5 -6
  16. {pipulate-2.0 → pipulate-2.2}/apps/210_widget_examples.py +35 -35
  17. {pipulate-2.0 → pipulate-2.2}/apps/230_dev_assistant.py +12 -13
  18. {pipulate-2.0 → pipulate-2.2}/apps/240_simon_mcp.py +6 -7
  19. {pipulate-2.0 → pipulate-2.2}/apps/300_blank_placeholder.py +4 -11
  20. {pipulate-2.0 → pipulate-2.2}/apps/400_botify_trifecta.py +13 -13
  21. {pipulate-2.0 → pipulate-2.2}/apps/440_browser_automation.py +67 -24
  22. {pipulate-2.0 → pipulate-2.2}/apps/450_stream_simulator.py +1 -1
  23. {pipulate-2.0 → pipulate-2.2}/apps/510_text_field.py +5 -5
  24. {pipulate-2.0 → pipulate-2.2}/apps/520_text_area.py +5 -5
  25. {pipulate-2.0 → pipulate-2.2}/apps/530_dropdown.py +6 -6
  26. {pipulate-2.0 → pipulate-2.2}/apps/540_checkboxes.py +6 -6
  27. {pipulate-2.0 → pipulate-2.2}/apps/550_radios.py +6 -6
  28. {pipulate-2.0 → pipulate-2.2}/apps/560_range.py +6 -6
  29. {pipulate-2.0 → pipulate-2.2}/apps/570_switch.py +4 -4
  30. {pipulate-2.0 → pipulate-2.2}/apps/580_upload.py +9 -9
  31. {pipulate-2.0 → pipulate-2.2}/apps/610_markdown.py +4 -4
  32. {pipulate-2.0 → pipulate-2.2}/apps/620_mermaid.py +4 -4
  33. {pipulate-2.0 → pipulate-2.2}/apps/630_prism.py +4 -4
  34. {pipulate-2.0 → pipulate-2.2}/apps/640_javascript.py +5 -5
  35. {pipulate-2.0 → pipulate-2.2}/apps/710_pandas.py +5 -5
  36. {pipulate-2.0 → pipulate-2.2}/apps/720_rich.py +5 -5
  37. {pipulate-2.0 → pipulate-2.2}/apps/730_matplotlib.py +5 -5
  38. {pipulate-2.0 → pipulate-2.2}/apps/810_webbrowser.py +4 -4
  39. {pipulate-2.0 → pipulate-2.2}/apps/820_selenium.py +4 -4
  40. {pipulate-2.0 → pipulate-2.2}/apps/830_pico_slider.py +6 -7
  41. {pipulate-2.0 → pipulate-2.2}/cli.py +17 -14
  42. {pipulate-2.0 → pipulate-2.2}/config.py +0 -38
  43. {pipulate-2.0 → pipulate-2.2}/imports/ascii_displays.py +953 -111
  44. {pipulate-2.0 → pipulate-2.2}/imports/botify/true_schema_discoverer.py +1 -1
  45. {pipulate-2.0 → pipulate-2.2}/imports/crud.py +7 -428
  46. pipulate-2.2/imports/durable_backup_system.py +215 -0
  47. {pipulate-2.0 → pipulate-2.2}/imports/stream_orchestrator.py +37 -0
  48. {pipulate-2.0 → pipulate-2.2}/imports/voice_synthesis.py +21 -2
  49. {pipulate-2.0 → pipulate-2.2}/pipulate/core.py +3 -2
  50. {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/PKG-INFO +25 -18
  51. {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/SOURCES.txt +2 -2
  52. {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/top_level.txt +0 -2
  53. {pipulate-2.0 → pipulate-2.2}/pyproject.toml +6 -6
  54. {pipulate-2.0 → pipulate-2.2}/server.py +17 -24
  55. pipulate-2.2/tests/test_mck_rep2.py +157 -0
  56. {pipulate-2.0 → pipulate-2.2}/tools/dom_tools.py +35 -23
  57. {pipulate-2.0 → pipulate-2.2}/tools/llm_optics.py +50 -2
  58. {pipulate-2.0 → pipulate-2.2}/tools/scraper_tools.py +429 -18
  59. pipulate-2.0/apps/xx_link_graph_v2.py +0 -6243
  60. pipulate-2.0/imports/durable_backup_system.py +0 -600
  61. pipulate-2.0/pipulate/pipulate.py +0 -62
  62. {pipulate-2.0 → pipulate-2.2}/LICENSE +0 -0
  63. {pipulate-2.0 → pipulate-2.2}/apps/025_aspect.py +0 -0
  64. {pipulate-2.0 → pipulate-2.2}/apps/050_documentation.py +0 -0
  65. {pipulate-2.0 → pipulate-2.2}/apps/220_roadmap.py +0 -0
  66. {pipulate-2.0 → pipulate-2.2}/imports/__init__.py +0 -0
  67. {pipulate-2.0 → pipulate-2.2}/imports/ai_dictdb.py +0 -0
  68. {pipulate-2.0 → pipulate-2.2}/imports/ai_tool_discovery_simple_parser.py +0 -0
  69. {pipulate-2.0 → pipulate-2.2}/imports/append_only_conversation.py +0 -0
  70. {pipulate-2.0 → pipulate-2.2}/imports/botify/__init__.py +0 -0
  71. {pipulate-2.0 → pipulate-2.2}/imports/botify/code_generators.py +0 -0
  72. {pipulate-2.0 → pipulate-2.2}/imports/botify_code_generation.py +0 -0
  73. {pipulate-2.0 → pipulate-2.2}/imports/database_safety_wrapper.py +0 -0
  74. {pipulate-2.0 → pipulate-2.2}/imports/dom_processing/__init__.py +0 -0
  75. {pipulate-2.0 → pipulate-2.2}/imports/dom_processing/ai_dom_beautifier.py +0 -0
  76. {pipulate-2.0 → pipulate-2.2}/imports/dom_processing/enhanced_dom_processor.py +0 -0
  77. {pipulate-2.0 → pipulate-2.2}/imports/mcp_orchestrator.py +0 -0
  78. {pipulate-2.0 → pipulate-2.2}/imports/server_logging.py +0 -0
  79. {pipulate-2.0 → pipulate-2.2}/pipulate/__init__.py +0 -0
  80. {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/dependency_links.txt +0 -0
  81. {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/entry_points.txt +0 -0
  82. {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/requires.txt +0 -0
  83. {pipulate-2.0 → pipulate-2.2}/setup.cfg +0 -0
  84. {pipulate-2.0 → pipulate-2.2}/tools/__init__.py +0 -0
  85. {pipulate-2.0 → pipulate-2.2}/tools/advanced_automation_tools.py +0 -0
  86. {pipulate-2.0 → pipulate-2.2}/tools/botify_tools.py +0 -0
  87. {pipulate-2.0 → pipulate-2.2}/tools/conversation_tools.py +0 -0
  88. {pipulate-2.0 → pipulate-2.2}/tools/keychain_tools.py +0 -0
  89. {pipulate-2.0 → pipulate-2.2}/tools/mcp_tools.py +0 -0
  90. {pipulate-2.0 → pipulate-2.2}/tools/system_tools.py +0 -0
@@ -0,0 +1,5 @@
1
+ # Work-in-progress plugin sketches (xx_ prefix) never ship to PyPI.
2
+ # Runtime plugin discovery already skips xx_ files; this keeps the sdist
3
+ # — and the wheel `python -m build` derives from it — in agreement with
4
+ # that convention. Fix protects v2.1 forward; the 2.0 artifact is history.
5
+ exclude apps/xx_*.py
@@ -1,13 +1,13 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.0
4
- Summary: A Notebook-inspired Hand-cranked Agentic Framework
3
+ Version: 2.2
4
+ Summary: AI-readiness for the agentic web — local-first, Nix-reproducible workflows. The successor to AI SEO software.
5
5
  Author-email: Mike Levin <pipulate@gmail.com>
6
- License-Expression: MIT
6
+ License-Expression: AGPL-3.0-or-later
7
7
  Project-URL: Homepage, https://github.com/miklevin/pipulate/
8
8
  Project-URL: Bug Tracker, https://github.com/miklevin/pipulate/issues
9
9
  Project-URL: Documentation, https://pipulate.com/guide/
10
- Keywords: seo,ai,llm,automation,local-first,nix,fasthtml,htmx,agentic
10
+ Keywords: ai-readiness,agentic-web,seo,ai,llm,automation,local-first,nix,fasthtml,htmx,agentic,content-negotiation
11
11
  Classifier: Development Status :: 5 - Production/Stable
12
12
  Classifier: Programming Language :: Python :: 3
13
13
  Classifier: Operating System :: OS Independent
@@ -59,7 +59,7 @@ Dynamic: license-file
59
59
  2. Hi, Auditor! What you're looking for is AUDIT.md ( https://pipulate.com/AUDIT.md )
60
60
  3. And context AI_CONTEXT.md ( https://pipulate.com/AI_CONTEXT.md )
61
61
 
62
- # Pipulate: Local First AI SEO Software
62
+ # Pipulate: Local First AI-Readiness for the Agentic Web
63
63
 
64
64
  ```text
65
65
  ( Like a canary you say? )
@@ -112,10 +112,10 @@ nix develop
112
112
 
113
113
  **Real examples of what people create with Pipulate:**
114
114
 
115
- ### 🔍 SEO Workflows
115
+ ### 🔍 Agentic-Web Readiness Workflows (the craft formerly known as SEO)
116
116
  - **Keyword Research Pipeline**: Input seed keywords → AI expansion → competition analysis → export spreadsheet
117
117
  - **Content Gap Analysis**: Compare your site vs competitors → identify missing topics → prioritized content calendar
118
- - **Technical SEO Audits**: Crawl site → check Core Web Vitals → generate action items → track fixes
118
+ - **Technical Readiness Audits**: Crawl site → check what agents actually see → generate action items → track fixes
119
119
 
120
120
  ### 📊 Data Processing Workflows
121
121
  - **CSV Data Cleaning**: Upload messy data → standardize formats → remove duplicates → validate results
@@ -123,7 +123,7 @@ nix develop
123
123
  - **Report Generation**: Combine multiple data sources → apply business rules → create branded reports
124
124
 
125
125
  ### 🤖 AI-Assisted Workflows
126
- - **Content Creation Pipeline**: Research topics → generate outlines → write drafts → optimize for SEO
126
+ - **Content Creation Pipeline**: Research topics → generate outlines → write drafts → optimize for machine readers
127
127
  - **Data Analysis Helper**: Upload spreadsheet → AI suggests insights → create visualizations → export findings
128
128
 
129
129
  **Key advantage:** Each workflow is a guided, step-by-step process that non-technical users can run repeatedly, while developers can customize the Python code behind each step.
@@ -132,7 +132,7 @@ nix develop
132
132
 
133
133
  ```text
134
134
  ╔═════════════════════════════════════════════════════════════════════════╗ Chip O'What?
135
- ║ 🎭 PIPULATE: LOCAL-FIRST AI SEO SOFTWARE & DIGITAL WORKSHOP ║ , O
135
+ ║ 🎭 PIPULATE: LOCAL-FIRST AI-READINESS & DIGITAL WORKSHOP ║ , O
136
136
  ║ ────────────────────────────────────────────────────────────────────── ║ \\ . O
137
137
  ║ ║ |\\/| o
138
138
  ║ 💬 Chip O'Theseus: "Welcome to your sovereign computing environment!" ║ / " '\
@@ -177,7 +177,7 @@ Think of it as putting guardrails on AI assistance. Instead of asking an AI to "
177
177
  3. 📓 **Transforms Jupyter Notebooks** into production-ready, step-by-step workflows
178
178
  4. 🤖 **Integrated AI assistance** using your own local models with complete privacy
179
179
  5. 🔧 **Reproducible environments** with Nix that work identically across all platforms
180
- 6. 🎯 **Perfect for SEO practitioners** who want to turn technical expertise into guided, reusable workflows
180
+ 6. 🎯 **Perfect for practitioners readying clients for the agentic web** who want to turn technical expertise into guided, reusable workflows
181
181
 
182
182
  --------------------------------------------------------------------------------
183
183
 
@@ -277,7 +277,7 @@ If you are not an Empire builder and prefer craftsmanship over the rat race and
277
277
 
278
278
  ### Primary Goals
279
279
 
280
- 1. **Empower End-Users (e.g., SEO Practitioners):** Enable non-programmers to run powerful, AI-guided workflows (often ported from Jupyter Notebooks) without needing to interact with Python code directly.
280
+ 1. **Empower End-Users (e.g., Agentic-Web Practitioners):** Enable non-programmers to run powerful, AI-guided workflows (often ported from Jupyter Notebooks) without needing to interact with Python code directly.
281
281
  2. **Serve Developers:** Provide a simple, reproducible environment for building these workflows, leveraging integrated tooling like Jupyter, local LLMs, and a streamlined web framework.
282
282
 
283
283
  --------------------------------------------------------------------------------
@@ -576,6 +576,13 @@ nix develop
576
576
 
577
577
  Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
578
578
 
579
+ **Automating past the boot menu:** `nix develop` ends at a two-door prompt — start Pipulate, or drop to the shell. It fails open, so anything without a terminal (CI, a provisioning script, an SSH session with no tty) starts the app and never sees the prompt. Two environment variables cover the cases that do:
580
+
581
+ | Variable | Effect |
582
+ |----------|--------|
583
+ | `PIPULATE_BOOT_MENU=0` | Skip the prompt entirely and start the app, e.g. `PIPULATE_BOOT_MENU=0 nix develop`. For an unattended terminal that would otherwise wait for a keypress that never comes. |
584
+ | `PIPULATE_BOOT_MENU_TIMEOUT=10` | Keep the prompt, but start the app after N seconds if nobody chooses. Off by default — with a human provably present, the prompt waits indefinitely rather than starting something they didn't ask for. |
585
+
579
586
  ### 🚨 Installation Troubleshooting
580
587
 
581
588
  **Common Issues & Solutions:**
@@ -695,7 +702,7 @@ Are you a Developer or an End User? Chef or Customer? Understanding your audienc
695
702
  ### 👨‍🍳 The Chef (Developer/Technical User)
696
703
  * **🔧 Workflow Creators:** Build and customize AI-assisted workflows
697
704
  * **📓 Jupyter Porters:** Convert notebook experiments into guided applications
698
- * **🔍 Technical SEOs:** Create sophisticated, reusable SEO processes
705
+ * **🔍 Technical SEOs (and their successors):** Create sophisticated, reusable readiness processes
699
706
  * **⚙️ System Administrators:** Deploy consistent environments across teams
700
707
 
701
708
  **What Chefs Get:**
@@ -705,10 +712,10 @@ Are you a Developer or an End User? Chef or Customer? Understanding your audienc
705
712
  - 🧰 Integrated tooling (Jupyter, local LLM, SQLite)
706
713
 
707
714
  ### 🍽️ The Customer (End User/Non-Technical)
708
- * **📈 SEO Practitioners:** Run powerful workflows without coding
709
- * **✍️ Content Creators:** Use AI-assisted processes for optimization
710
- * **📊 Marketing Teams:** Execute consistent SEO strategies
711
- * **🏢 Business Owners:** Access enterprise-level SEO capabilities
715
+ * **📈 Agentic-Web Practitioners:** Run powerful workflows without coding
716
+ * **✍️ Content Creators:** Use AI-assisted processes for machine legibility
717
+ * **📊 Marketing Teams:** Execute consistent agentic-web strategies
718
+ * **🏢 Business Owners:** Access enterprise-level readiness capabilities
712
719
 
713
720
  **What Customers Get:**
714
721
  - 🚶‍♂️ Guided, step-by-step workflow experiences
@@ -743,7 +750,7 @@ Just as a chef talks about knife techniques while a diner just wants amazing pas
743
750
 
744
751
  ### Your Next Steps Depend on Who You Are
745
752
 
746
- **🔍 If you're an SEO practitioner:**
753
+ **🔍 If you're readying clients for the agentic web:**
747
754
  - Click "Introduction" in the left menu for a guided tour
748
755
  - Try the built-in workflows to see the step-by-step pattern
749
756
  - Use the AI chat to ask "How do I create a keyword research workflow?"
@@ -1707,6 +1714,6 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
1707
1714
 
1708
1715
  **Background Articles:** <a href="https://mikelev.in/">Mike Levin, AI SEO in NYC</a>
1709
1716
 
1710
- **Enhanced Documentation:** <a href="https://pipulate.com/">Pipulate AI SEO Software</a>
1717
+ **Enhanced Documentation:** <a href="https://pipulate.com/">Pipulate: AI-Readiness for the Agentic Web</a>
1711
1718
 
1712
1719
  **On GitHub:** <a href="https://github.com/miklevin/pipulate">Pipulate on GitHub</a>
@@ -2,7 +2,7 @@
2
2
  2. Hi, Auditor! What you're looking for is AUDIT.md ( https://pipulate.com/AUDIT.md )
3
3
  3. And context AI_CONTEXT.md ( https://pipulate.com/AI_CONTEXT.md )
4
4
 
5
- # Pipulate: Local First AI SEO Software
5
+ # Pipulate: Local First AI-Readiness for the Agentic Web
6
6
 
7
7
  ```text
8
8
  ( Like a canary you say? )
@@ -55,10 +55,10 @@ nix develop
55
55
 
56
56
  **Real examples of what people create with Pipulate:**
57
57
 
58
- ### 🔍 SEO Workflows
58
+ ### 🔍 Agentic-Web Readiness Workflows (the craft formerly known as SEO)
59
59
  - **Keyword Research Pipeline**: Input seed keywords → AI expansion → competition analysis → export spreadsheet
60
60
  - **Content Gap Analysis**: Compare your site vs competitors → identify missing topics → prioritized content calendar
61
- - **Technical SEO Audits**: Crawl site → check Core Web Vitals → generate action items → track fixes
61
+ - **Technical Readiness Audits**: Crawl site → check what agents actually see → generate action items → track fixes
62
62
 
63
63
  ### 📊 Data Processing Workflows
64
64
  - **CSV Data Cleaning**: Upload messy data → standardize formats → remove duplicates → validate results
@@ -66,7 +66,7 @@ nix develop
66
66
  - **Report Generation**: Combine multiple data sources → apply business rules → create branded reports
67
67
 
68
68
  ### 🤖 AI-Assisted Workflows
69
- - **Content Creation Pipeline**: Research topics → generate outlines → write drafts → optimize for SEO
69
+ - **Content Creation Pipeline**: Research topics → generate outlines → write drafts → optimize for machine readers
70
70
  - **Data Analysis Helper**: Upload spreadsheet → AI suggests insights → create visualizations → export findings
71
71
 
72
72
  **Key advantage:** Each workflow is a guided, step-by-step process that non-technical users can run repeatedly, while developers can customize the Python code behind each step.
@@ -75,7 +75,7 @@ nix develop
75
75
 
76
76
  ```text
77
77
  ╔═════════════════════════════════════════════════════════════════════════╗ Chip O'What?
78
- ║ 🎭 PIPULATE: LOCAL-FIRST AI SEO SOFTWARE & DIGITAL WORKSHOP ║ , O
78
+ ║ 🎭 PIPULATE: LOCAL-FIRST AI-READINESS & DIGITAL WORKSHOP ║ , O
79
79
  ║ ────────────────────────────────────────────────────────────────────── ║ \\ . O
80
80
  ║ ║ |\\/| o
81
81
  ║ 💬 Chip O'Theseus: "Welcome to your sovereign computing environment!" ║ / " '\
@@ -120,7 +120,7 @@ Think of it as putting guardrails on AI assistance. Instead of asking an AI to "
120
120
  3. 📓 **Transforms Jupyter Notebooks** into production-ready, step-by-step workflows
121
121
  4. 🤖 **Integrated AI assistance** using your own local models with complete privacy
122
122
  5. 🔧 **Reproducible environments** with Nix that work identically across all platforms
123
- 6. 🎯 **Perfect for SEO practitioners** who want to turn technical expertise into guided, reusable workflows
123
+ 6. 🎯 **Perfect for practitioners readying clients for the agentic web** who want to turn technical expertise into guided, reusable workflows
124
124
 
125
125
  --------------------------------------------------------------------------------
126
126
 
@@ -220,7 +220,7 @@ If you are not an Empire builder and prefer craftsmanship over the rat race and
220
220
 
221
221
  ### Primary Goals
222
222
 
223
- 1. **Empower End-Users (e.g., SEO Practitioners):** Enable non-programmers to run powerful, AI-guided workflows (often ported from Jupyter Notebooks) without needing to interact with Python code directly.
223
+ 1. **Empower End-Users (e.g., Agentic-Web Practitioners):** Enable non-programmers to run powerful, AI-guided workflows (often ported from Jupyter Notebooks) without needing to interact with Python code directly.
224
224
  2. **Serve Developers:** Provide a simple, reproducible environment for building these workflows, leveraging integrated tooling like Jupyter, local LLMs, and a streamlined web framework.
225
225
 
226
226
  --------------------------------------------------------------------------------
@@ -519,6 +519,13 @@ nix develop
519
519
 
520
520
  Wait for the JupyterLab tab to auto-open, then run the Onboarding notebook to unlock the Pipulate app.
521
521
 
522
+ **Automating past the boot menu:** `nix develop` ends at a two-door prompt — start Pipulate, or drop to the shell. It fails open, so anything without a terminal (CI, a provisioning script, an SSH session with no tty) starts the app and never sees the prompt. Two environment variables cover the cases that do:
523
+
524
+ | Variable | Effect |
525
+ |----------|--------|
526
+ | `PIPULATE_BOOT_MENU=0` | Skip the prompt entirely and start the app, e.g. `PIPULATE_BOOT_MENU=0 nix develop`. For an unattended terminal that would otherwise wait for a keypress that never comes. |
527
+ | `PIPULATE_BOOT_MENU_TIMEOUT=10` | Keep the prompt, but start the app after N seconds if nobody chooses. Off by default — with a human provably present, the prompt waits indefinitely rather than starting something they didn't ask for. |
528
+
522
529
  ### 🚨 Installation Troubleshooting
523
530
 
524
531
  **Common Issues & Solutions:**
@@ -638,7 +645,7 @@ Are you a Developer or an End User? Chef or Customer? Understanding your audienc
638
645
  ### 👨‍🍳 The Chef (Developer/Technical User)
639
646
  * **🔧 Workflow Creators:** Build and customize AI-assisted workflows
640
647
  * **📓 Jupyter Porters:** Convert notebook experiments into guided applications
641
- * **🔍 Technical SEOs:** Create sophisticated, reusable SEO processes
648
+ * **🔍 Technical SEOs (and their successors):** Create sophisticated, reusable readiness processes
642
649
  * **⚙️ System Administrators:** Deploy consistent environments across teams
643
650
 
644
651
  **What Chefs Get:**
@@ -648,10 +655,10 @@ Are you a Developer or an End User? Chef or Customer? Understanding your audienc
648
655
  - 🧰 Integrated tooling (Jupyter, local LLM, SQLite)
649
656
 
650
657
  ### 🍽️ The Customer (End User/Non-Technical)
651
- * **📈 SEO Practitioners:** Run powerful workflows without coding
652
- * **✍️ Content Creators:** Use AI-assisted processes for optimization
653
- * **📊 Marketing Teams:** Execute consistent SEO strategies
654
- * **🏢 Business Owners:** Access enterprise-level SEO capabilities
658
+ * **📈 Agentic-Web Practitioners:** Run powerful workflows without coding
659
+ * **✍️ Content Creators:** Use AI-assisted processes for machine legibility
660
+ * **📊 Marketing Teams:** Execute consistent agentic-web strategies
661
+ * **🏢 Business Owners:** Access enterprise-level readiness capabilities
655
662
 
656
663
  **What Customers Get:**
657
664
  - 🚶‍♂️ Guided, step-by-step workflow experiences
@@ -686,7 +693,7 @@ Just as a chef talks about knife techniques while a diner just wants amazing pas
686
693
 
687
694
  ### Your Next Steps Depend on Who You Are
688
695
 
689
- **🔍 If you're an SEO practitioner:**
696
+ **🔍 If you're readying clients for the agentic web:**
690
697
  - Click "Introduction" in the left menu for a guided tour
691
698
  - Try the built-in workflows to see the step-by-step pattern
692
699
  - Use the AI chat to ask "How do I create a keyword research workflow?"
@@ -1650,6 +1657,6 @@ This project is licensed under the MIT License. See the [LICENSE](https://github
1650
1657
 
1651
1658
  **Background Articles:** <a href="https://mikelev.in/">Mike Levin, AI SEO in NYC</a>
1652
1659
 
1653
- **Enhanced Documentation:** <a href="https://pipulate.com/">Pipulate AI SEO Software</a>
1660
+ **Enhanced Documentation:** <a href="https://pipulate.com/">Pipulate: AI-Readiness for the Agentic Web</a>
1654
1661
 
1655
1662
  **On GitHub:** <a href="https://github.com/miklevin/pipulate">Pipulate on GitHub</a>
@@ -1,5 +1,5 @@
1
1
  """
2
- Pipulate: A hand-cranked, local-first AI SEO software framework.
2
+ Pipulate: A hand-cranked, local-first AI-readiness software framework — the successor to AI SEO software.
3
3
 
4
4
  Your data. Your AI. Your machine. Your control.
5
5
 
@@ -12,12 +12,16 @@ Usage:
12
12
  pipulate
13
13
  """
14
14
 
15
- __version__ = "2.00"
16
- __version_description__ = "Good Install Instructions"
15
+ __version__ = "2.02"
16
+ __version_description__ = "Operation Stick Bug"
17
+ # SPDX expression, single source of truth, synced into pyproject.toml by
18
+ # scripts/release/version_sync.py. "-or-later" (not bare AGPL-3.0, which is
19
+ # deprecated SPDX) because the header below grants "any later version".
20
+ __license__ = "AGPL-3.0-or-later"
17
21
  __email__ = "pipulate@gmail.com"
18
- __description__ = "A Notebook-inspired Hand-cranked Agentic Framework"
22
+ __description__ = "AI-readiness for the agentic web — local-first, Nix-reproducible workflows. The successor to AI SEO software."
19
23
 
20
- # Pipulate: A hand-cranked, local-first AI SEO software framework.
24
+ # Pipulate: A hand-cranked, local-first AI-readiness software framework — the successor to AI SEO software.
21
25
  # Copyright (C) 2026 Michael Jay Levin
22
26
  #
23
27
  # This program is free software: you can redistribute it and/or modify
@@ -84,30 +84,47 @@ class IntroductionPlugin:
84
84
  except Exception as e:
85
85
  logger.warning(f"Could not verify config pipeline state: {e}")
86
86
  dynamic_app_name = self.wand.get_config().APP_NAME
87
- active_model = self.wand.db.get('active_local_model', 'an external provider')
87
+ active_model = self.wand.db.get('active_local_model', 'not yet selected')
88
88
 
89
89
  if not operator_name:
90
- # STATE 1: The Bouncer Persona (Airlock has not fired)
90
+ # STATE 1: The Doorway (Airlock has not fired)
91
+ # ATTRIBUTED VOICE: the first thing a stranger hears must say
92
+ # what is producing it. This is Piper reading a fixed string,
93
+ # so it says so, and it does not borrow a persona to do it.
91
94
  msg = (
92
- "Halt. I am Chip O'Theseus. My speech is generated entirely on your machine, "
93
- "but you are trying to sneak into the VIP lounge through the kitchen. "
94
- "You have discovered port 5001, but the doors to the Control Room remain sealed until you complete the initiation rite. "
95
- "Return to your JupyterLab tab, execute the Golden Path, and drop the sentinel file."
95
+ "One disclosure before anything else. This voice is Piper, a speech "
96
+ "synthesizer running entirely on your machine, reading a fixed script. "
97
+ "There is no language model behind it yet. You have found port 5001 early, "
98
+ "which is fine, but this room fills in as you finish onboarding. "
99
+ "Head back to your JupyterLab tab, run the Onboarding notebook top to bottom, "
100
+ "and this page will have something worth saying."
96
101
  )
97
- return "Access Denied 🛑", msg, None
102
+ return "Onboarding Not Finished 🔒", msg, None
98
103
 
99
104
  elif not has_configured:
100
- # STATE 2: The Guide Persona (Airlock fired, but Configuration is pending)
105
+ # STATE 2: The Guide (Airlock fired, but Configuration is pending)
106
+ # The demo is a scripted scenario driven by player-piano.js, so
107
+ # it is a tour OF THE SYSTEM, never of anyone's "capabilities."
101
108
  msg = (
102
- f"Welcome to {dynamic_app_name}, {operator_name}. I am Chip O'Theseus. To see a demonstration of my capabilities, press ",
109
+ f"Welcome to {dynamic_app_name}, {operator_name}. This voice is still a local "
110
+ "speech synthesizer reading a script, not a language model. The thinking "
111
+ "engines, local and cloud, get wired up next in Configuration. For a scripted "
112
+ "tour of the interface, press ",
103
113
  Strong("Ctrl+Alt+D", cls="platform-shortcut"),
104
114
  " right now. Otherwise, we will proceed to finalize your configuration."
105
115
  )
106
116
  return "Welcome", msg, 'finalize'
107
117
 
108
118
  else:
109
- # STATE 3: The Veteran Persona (Config workflow is finalized)
110
- msg = f"Welcome back to {dynamic_app_name}, {operator_name}. All systems are online and ready."
119
+ # STATE 3: The Veteran (Config workflow is finalized)
120
+ # Even here the narration stays scripted. The model name is a
121
+ # receipt, so it is spoken only about the engine, never as a
122
+ # claim about the speaker.
123
+ msg = (
124
+ f"Welcome back to {dynamic_app_name}, {operator_name}. This narration is still "
125
+ f"scripted speech, not inference. Your local engine is {active_model}. Anything "
126
+ "a model actually generates from here on will name the model that generated it."
127
+ )
111
128
  return "Dashboard Ready ✅", msg, None
112
129
 
113
130
  elif step_id == 'finalize':
@@ -132,6 +149,10 @@ class IntroductionPlugin:
132
149
  else:
133
150
  text = str(content)
134
151
 
152
+ # Markup stripping lives at the voice boundary (the ACOUSTIC SANITIZER
153
+ # in imports/voice_synthesis.py), which every caller crosses. Duplicated
154
+ # here it was harmless but drift-prone, so it moved rather than doubled.
155
+
135
156
 
136
157
  from imports.voice_synthesis import chip_voice_system
137
158
  if chip_voice_system and chip_voice_system.voice_ready:
@@ -206,7 +227,7 @@ class IntroductionPlugin:
206
227
 
207
228
  return Div(
208
229
  onload_trigger,
209
- Card(
230
+ Article(
210
231
  Div(
211
232
  H2(title, style="display: inline-block; margin-bottom: 0;"),
212
233
  encore_btn,
@@ -45,7 +45,7 @@ The chain is maintained by including a Div with hx_trigger="load" in the respons
45
45
  ```python
46
46
  # Explicit method:
47
47
  return Div(
48
- Card(...), # Current step content
48
+ Article(...), # Current step content
49
49
  Div(id=next_step_id, hx_get=f"/{app_name}/{next_step_id}", hx_trigger="load"),
50
50
  id=step_id
51
51
  )
@@ -408,7 +408,7 @@ You're here to make the workflow concepts accessible and help users understand t
408
408
  finalize_data = wand.get_step_data(pipeline_id, finalize_step.id, {})
409
409
  if request.method == 'GET':
410
410
  if finalize_step.done in finalize_data:
411
- return Card(
411
+ return Article(
412
412
  H3(f'{self.ui["EMOJIS"]["LOCKED"]} Workflow is locked.'),
413
413
  P('Each step can do ANYTHING. With this you can change the world — or at least show how to in a workflow.', cls='text-muted'),
414
414
  Form(
@@ -436,7 +436,7 @@ You're here to make the workflow concepts accessible and help users understand t
436
436
  else:
437
437
  all_steps_complete = all((wand.get_step_data(pipeline_id, step.id, {}).get(step.done) for step in steps[:-1]))
438
438
  if all_steps_complete:
439
- return Card(
439
+ return Article(
440
440
  H3(f'{self.ui["EMOJIS"]["SUCCESS"]} All steps complete. Finalize?'),
441
441
  P('At the end they get locked. Or you can go back.', cls='text-muted'),
442
442
  Form(
@@ -469,7 +469,11 @@ You're here to make the workflow concepts accessible and help users understand t
469
469
  else:
470
470
  await wand.finalize_workflow(pipeline_id)
471
471
  await self.message_queue.add(wand, self.step_messages['finalize']['complete'], verbatim=True)
472
- self.wand.speak("The workflow is now locked and finalized. You can pull it up again any time with the same key. All features requiring AI or an API-key are active. Enjoy the workflows!", wait=False)
472
+ # ATTRIBUTED VOICE: the old line claimed "all features requiring AI
473
+ # or an API-key are active" unconditionally, including for someone
474
+ # who pressed Skip on both cloud and Botify. Nothing here inspects
475
+ # which steps were filled in, so the narration must not assert it.
476
+ self.wand.speak("The workflow is now locked and finalized. You can pull it up again any time with the same key. Whatever you configured is now in effect, and anything you skipped can be added later by unlocking this workflow.", wait=False)
473
477
  return wand.run_all_cells(app_name, steps)
474
478
 
475
479
  async def unfinalize(self, request):
@@ -543,7 +547,7 @@ You're here to make the workflow concepts accessible and help users understand t
543
547
  locked_msg = f'{self.ui["EMOJIS"]["LOCKED"]} Your name is set to: {user_val}'
544
548
  await self.message_queue.add(wand, locked_msg, verbatim=True)
545
549
  return Div(
546
- Card(
550
+ Article(
547
551
  H3(f'{self.ui["EMOJIS"]["LOCKED"]} {step.show}: {user_val}'),
548
552
  role='region',
549
553
  aria_label=f'Locked step 1: {step.show}',
@@ -593,7 +597,7 @@ You're here to make the workflow concepts accessible and help users understand t
593
597
  self.wand.speak("Workflows are Notebooks without having to look at the code. Please enter your name to begin.", wait=False)
594
598
 
595
599
  return Div(
596
- Card(
600
+ Article(
597
601
  H3(f'{self.ui["EMOJIS"]["USER_INPUT"]} {self.wand.fmt(step.id)}: Enter {step.show}'),
598
602
  P(explanation, cls='text-muted'),
599
603
  Label(
@@ -679,12 +683,16 @@ You're here to make the workflow concepts accessible and help users understand t
679
683
  except ImportError as e:
680
684
  logger.warning(f"⚠️ Could not access AI Keychain to store operator identity: {e}")
681
685
 
682
- # Progressive feedback with emoji
683
- success_msg = f'{self.ui["EMOJIS"]["SUCCESS"]} Operator Identity secured: {user_val}'
686
+ # Progressive feedback with emoji.
687
+ # NO VERB OF VERIFICATION WITHOUT A VERIFICATION. Three writes happened
688
+ # above (pipeline state, server cookie, keychain) and zero checks. The
689
+ # honest verb is "recorded"; "confirmed" and "secured" both describe an
690
+ # act no code in this method performs.
691
+ success_msg = f'{self.ui["EMOJIS"]["SUCCESS"]} Name recorded: {user_val}'
684
692
  await self.message_queue.add(self.wand, success_msg, verbatim=True)
685
693
 
686
694
  # Speak it into existence!
687
- self.wand.speak(f"Identity confirmed. Hello {user_val}. Proceed to the next step.", wait=True)
695
+ self.wand.speak(f"Name recorded. Hello {user_val}. Proceed to the next step.", wait=True)
688
696
 
689
697
  # Update LLM context
690
698
  self.wand.append_to_history(f"[SYSTEM STATE] Operator identity established globally as:\n{user_val}")
@@ -712,7 +720,7 @@ You're here to make the workflow concepts accessible and help users understand t
712
720
  locked_msg = f'{self.ui["EMOJIS"]["LOCKED"]} Greeting is locked: {user_val}'
713
721
  await self.message_queue.add(wand, locked_msg, verbatim=True)
714
722
  return Div(
715
- Card(
723
+ Article(
716
724
  H3(f'{self.ui["EMOJIS"]["LOCKED"]} {step.show}: {user_val}'),
717
725
  role='region',
718
726
  aria_label=f'Locked step 2: {step.show}',
@@ -787,7 +795,7 @@ You're here to make the workflow concepts accessible and help users understand t
787
795
  # --- END DYNAMIC OLLAMA FETCH ---
788
796
 
789
797
  return Div(
790
- Card(
798
+ Article(
791
799
  H3(f'🧠 {self.wand.fmt(step.id)}: {step.show}'),
792
800
  P(explanation, cls='text-muted'),
793
801
  Label(
@@ -892,7 +900,7 @@ You're here to make the workflow concepts accessible and help users understand t
892
900
  display_text = f"Model: {current_value.get('model')}\nToken: {current_value.get('api_key')}"
893
901
  pip.append_to_history(f"[WIDGET CONTENT] {step.show} (Finalized):\n{display_text}")
894
902
  return Div(
895
- Card(H3(f"🔒 {step.show}: Completed"), Pre(display_text, cls="code-block-container")),
903
+ Article(H3(f"🔒 {step.show}: Completed"), Pre(display_text, cls="code-block-container")),
896
904
  Div(id=next_step_id, hx_get=f"/{app_name}/{next_step_id}", hx_trigger="load"),
897
905
  id=step_id
898
906
  )
@@ -975,7 +983,7 @@ You're here to make the workflow concepts accessible and help users understand t
975
983
  )
976
984
 
977
985
  return Div(
978
- Card(
986
+ Article(
979
987
  H3(f"☁️ {step.show}"),
980
988
  P("Select your preferred Cloud AI engine and provide the corresponding API key. This unlocks advanced capabilities and provides a fallback when the local engine needs help. ",
981
989
  A("Get your Google AI Studio key here ↗", href="https://aistudio.google.com/", target="_blank"),
@@ -1068,9 +1076,12 @@ You're here to make the workflow concepts accessible and help users understand t
1068
1076
  pip.append_to_history(f"[WIDGET CONTENT] {step.show}:\n{display_text}")
1069
1077
  pip.append_to_history(f"[WIDGET STATE] {step.show}: Step completed")
1070
1078
 
1071
- success_msg = f'{pip.get_ui_constants()["EMOJIS"]["SUCCESS"]} Cloud Engine secured: {selected_model}'
1079
+ # "Connection secured" described a connection that was never opened:
1080
+ # the key was written to .env and the llm keychain and not used once.
1081
+ # Say what happened, and name the first moment it gets tested.
1082
+ success_msg = f'{pip.get_ui_constants()["EMOJIS"]["SUCCESS"]} Cloud engine saved: {selected_model}'
1072
1083
  await self.message_queue.add(pip, success_msg, verbatim=True)
1073
- pip.speak(f"Cloud cognitive engine selected. Connection secured. If you are a Botify employee or Customer, please enter your Botify API key.", wait=False)
1084
+ pip.speak(f"Cloud engine set to {selected_model} and the key is saved. It has not been tested yet; the first real request will confirm it. If you are a Botify employee or customer, please enter your Botify API key next.", wait=False)
1074
1085
 
1075
1086
  if pip.check_finalize_needed(step_index, steps):
1076
1087
  await self.message_queue.add(pip, self.step_messages['finalize']['ready'], verbatim=True)
@@ -1102,7 +1113,7 @@ You're here to make the workflow concepts accessible and help users understand t
1102
1113
  display_text = f"Status: {current_value.get('status')}\nToken: {current_value.get('api_key')}"
1103
1114
  pip.append_to_history(f"[WIDGET CONTENT] {step.show} (Finalized):\n{display_text}")
1104
1115
  return Div(
1105
- Card(H3(f"🔒 {step.show}: Completed"), Pre(display_text, cls="code-block-container")),
1116
+ Article(H3(f"🔒 {step.show}: Completed"), Pre(display_text, cls="code-block-container")),
1106
1117
  Div(id=next_step_id, hx_get=f"/{app_name}/{next_step_id}", hx_trigger="load"),
1107
1118
  id=step_id
1108
1119
  )
@@ -1148,7 +1159,7 @@ You're here to make the workflow concepts accessible and help users understand t
1148
1159
  )
1149
1160
 
1150
1161
  return Div(
1151
- Card(
1162
+ Article(
1152
1163
  H3(f"🕷️ {step.show}"),
1153
1164
  P("If you are a Botify customer or employee, you can connect your account. ",
1154
1165
  A("Get your API key here ↗", href="https://app.botify.com/account/", target="_blank"),
@@ -1201,22 +1212,20 @@ You're here to make the workflow concepts accessible and help users understand t
1201
1212
  }
1202
1213
  display_text = f"Status: Configured\nToken: {masked_key}"
1203
1214
 
1204
- success_msg = f'{pip.get_ui_constants()["EMOJIS"]["SUCCESS"]} Botify token secured.'
1215
+ # Stored, not validated. Same correction as the cloud step.
1216
+ success_msg = f'{pip.get_ui_constants()["EMOJIS"]["SUCCESS"]} Botify token saved (not yet tested).'
1205
1217
  await self.message_queue.add(pip, success_msg, verbatim=True)
1206
- pip.speak("Botify API token secured.", wait=True)
1218
+ pip.speak("Botify API token saved. It has not been tested yet.", wait=True)
1207
1219
 
1208
- pip.speak("Congratulations, everything is configured! Click finalize.", wait=False)
1220
+ # Fires on every path through step_04, including double-Skip, so it
1221
+ # cannot claim everything is configured. It CAN claim the steps are done.
1222
+ pip.speak("Configuration steps complete. Click finalize to lock it in.", wait=False)
1209
1223
 
1210
1224
  await pip.set_step_data(pipeline_id, step_id, payload, steps)
1211
1225
 
1212
1226
  pip.append_to_history(f"[WIDGET CONTENT] {step.show}:\n{display_text}")
1213
1227
  pip.append_to_history(f"[WIDGET STATE] {step.show}: Step completed")
1214
1228
 
1215
- if pip.check_finalize_needed(step_index, steps):
1216
- await self.message_queue.add(pip, self.step_messages['finalize']['ready'], verbatim=True)
1217
-
1218
- widget = Pre(display_text, cls="code-block-container")
1219
-
1220
1229
  if pip.check_finalize_needed(step_index, steps):
1221
1230
  await self.message_queue.add(pip, self.step_messages['finalize']['ready'], verbatim=True)
1222
1231
 
@@ -1,18 +1,11 @@
1
- import json
2
- import os
3
- import re
4
- import sys
5
- from typing import Any, Dict, List, Optional
6
-
7
1
  import fastlite
8
- from fasthtml.common import (H2, H3, H4, A, Button, Card, Container, Details,
9
- Div, Form, Grid, Group, Hr, HTMLResponse,
2
+ from fasthtml.common import (H2, H3, H4, A, Button, Details,
3
+ Div, Fieldset, Form, Hr, HTMLResponse,
10
4
  HTTPException, Input, Label, Li, Link, Meta,
11
5
  Option, P, Script, Select, Span, Summary,
12
6
  Textarea, Title, Ul, to_xml)
13
7
  from server import get_db_filename
14
8
  from imports.crud import BaseCrud
15
- from server import db as server_db
16
9
 
17
10
  # Lazy imports to avoid circular dependencies - imported when needed
18
11
  def get_server_functions():
@@ -117,7 +110,6 @@ class ProfilesPlugin(ProfilesPluginIdentity):
117
110
  except Exception as e:
118
111
  logger.warning(f"Error counting tasks for profile {profile_id}: {e}")
119
112
  return 0
120
- logger.debug(f'{display_name_for_init} ProfileCrudOperations instance created.')
121
113
 
122
114
  def register_routes(self, rt_decorator):
123
115
  self.crud_handler.register_routes(rt_decorator)
@@ -210,7 +202,7 @@ class ProfilesPlugin(ProfilesPluginIdentity):
210
202
  if profile_lock_enabled:
211
203
  add_profile_form = Div() # Empty div when locked
212
204
  else:
213
- add_profile_form = Form(Group(Input(placeholder='Nickname', name='profile_name', id='profile-name-input-add', autofocus=True), Input(placeholder='Real Name (Optional)', name='profile_real_name', id='profile-real-name-input-add'), Input(placeholder=PLACEHOLDER_ADDRESS, name='profile_address', id='profile-address-input-add'), Input(placeholder=PLACEHOLDER_CODE, name='profile_code', id='profile-code-input-add'), Button('Add', type='submit', id='add-profile-button')), hx_post=f'/{self.name}', hx_target='#profile-list-ul', hx_swap='beforeend', hx_on_htmx_after_request="this.reset(); this.querySelector('input[name=profile_name]').focus();")
205
+ add_profile_form = Form(Fieldset(Input(placeholder='Nickname', name='profile_name', id='profile-name-input-add', autofocus=True), Input(placeholder='Real Name (Optional)', name='profile_real_name', id='profile-real-name-input-add'), Input(placeholder=PLACEHOLDER_ADDRESS, name='profile_address', id='profile-address-input-add'), Input(placeholder=PLACEHOLDER_CODE, name='profile_code', id='profile-code-input-add'), Button('Add', type='submit', id='add-profile-button'), role='group'), hx_post=f'/{self.name}', hx_target='#profile-list-ul', hx_swap='beforeend', hx_on_htmx_after_request="this.reset(); this.querySelector('input[name=profile_name]').focus();")
214
206
 
215
207
  container = Div(H2('Profiles'), add_profile_form, profile_list, cls='container-centered')
216
208
  return container