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.
- pipulate-2.2/MANIFEST.in +5 -0
- {pipulate-2.0 → pipulate-2.2}/PKG-INFO +25 -18
- {pipulate-2.0 → pipulate-2.2}/README.md +21 -14
- {pipulate-2.0 → pipulate-2.2}/__init__.py +9 -5
- {pipulate-2.0 → pipulate-2.2}/apps/010_introduction.py +33 -12
- {pipulate-2.0 → pipulate-2.2}/apps/015_config.py +34 -25
- {pipulate-2.0 → pipulate-2.2}/apps/020_profiles.py +3 -11
- {pipulate-2.0 → pipulate-2.2}/apps/030_roles.py +17 -268
- {pipulate-2.0 → pipulate-2.2}/apps/040_hello_workflow.py +8 -14
- {pipulate-2.0 → pipulate-2.2}/apps/060_tasks.py +2 -5
- {pipulate-2.0 → pipulate-2.2}/apps/070_history.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/110_parameter_buster.py +20 -20
- {pipulate-2.0 → pipulate-2.2}/apps/120_link_graph.py +22 -22
- {pipulate-2.0 → pipulate-2.2}/apps/130_gap_analysis.py +8 -9
- {pipulate-2.0 → pipulate-2.2}/apps/200_workflow_genesis.py +5 -6
- {pipulate-2.0 → pipulate-2.2}/apps/210_widget_examples.py +35 -35
- {pipulate-2.0 → pipulate-2.2}/apps/230_dev_assistant.py +12 -13
- {pipulate-2.0 → pipulate-2.2}/apps/240_simon_mcp.py +6 -7
- {pipulate-2.0 → pipulate-2.2}/apps/300_blank_placeholder.py +4 -11
- {pipulate-2.0 → pipulate-2.2}/apps/400_botify_trifecta.py +13 -13
- {pipulate-2.0 → pipulate-2.2}/apps/440_browser_automation.py +67 -24
- {pipulate-2.0 → pipulate-2.2}/apps/450_stream_simulator.py +1 -1
- {pipulate-2.0 → pipulate-2.2}/apps/510_text_field.py +5 -5
- {pipulate-2.0 → pipulate-2.2}/apps/520_text_area.py +5 -5
- {pipulate-2.0 → pipulate-2.2}/apps/530_dropdown.py +6 -6
- {pipulate-2.0 → pipulate-2.2}/apps/540_checkboxes.py +6 -6
- {pipulate-2.0 → pipulate-2.2}/apps/550_radios.py +6 -6
- {pipulate-2.0 → pipulate-2.2}/apps/560_range.py +6 -6
- {pipulate-2.0 → pipulate-2.2}/apps/570_switch.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/580_upload.py +9 -9
- {pipulate-2.0 → pipulate-2.2}/apps/610_markdown.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/620_mermaid.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/630_prism.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/640_javascript.py +5 -5
- {pipulate-2.0 → pipulate-2.2}/apps/710_pandas.py +5 -5
- {pipulate-2.0 → pipulate-2.2}/apps/720_rich.py +5 -5
- {pipulate-2.0 → pipulate-2.2}/apps/730_matplotlib.py +5 -5
- {pipulate-2.0 → pipulate-2.2}/apps/810_webbrowser.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/820_selenium.py +4 -4
- {pipulate-2.0 → pipulate-2.2}/apps/830_pico_slider.py +6 -7
- {pipulate-2.0 → pipulate-2.2}/cli.py +17 -14
- {pipulate-2.0 → pipulate-2.2}/config.py +0 -38
- {pipulate-2.0 → pipulate-2.2}/imports/ascii_displays.py +953 -111
- {pipulate-2.0 → pipulate-2.2}/imports/botify/true_schema_discoverer.py +1 -1
- {pipulate-2.0 → pipulate-2.2}/imports/crud.py +7 -428
- pipulate-2.2/imports/durable_backup_system.py +215 -0
- {pipulate-2.0 → pipulate-2.2}/imports/stream_orchestrator.py +37 -0
- {pipulate-2.0 → pipulate-2.2}/imports/voice_synthesis.py +21 -2
- {pipulate-2.0 → pipulate-2.2}/pipulate/core.py +3 -2
- {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/PKG-INFO +25 -18
- {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/SOURCES.txt +2 -2
- {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/top_level.txt +0 -2
- {pipulate-2.0 → pipulate-2.2}/pyproject.toml +6 -6
- {pipulate-2.0 → pipulate-2.2}/server.py +17 -24
- pipulate-2.2/tests/test_mck_rep2.py +157 -0
- {pipulate-2.0 → pipulate-2.2}/tools/dom_tools.py +35 -23
- {pipulate-2.0 → pipulate-2.2}/tools/llm_optics.py +50 -2
- {pipulate-2.0 → pipulate-2.2}/tools/scraper_tools.py +429 -18
- pipulate-2.0/apps/xx_link_graph_v2.py +0 -6243
- pipulate-2.0/imports/durable_backup_system.py +0 -600
- pipulate-2.0/pipulate/pipulate.py +0 -62
- {pipulate-2.0 → pipulate-2.2}/LICENSE +0 -0
- {pipulate-2.0 → pipulate-2.2}/apps/025_aspect.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/apps/050_documentation.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/apps/220_roadmap.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/__init__.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/ai_dictdb.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/ai_tool_discovery_simple_parser.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/append_only_conversation.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/botify/__init__.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/botify/code_generators.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/botify_code_generation.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/database_safety_wrapper.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/dom_processing/__init__.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/dom_processing/ai_dom_beautifier.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/dom_processing/enhanced_dom_processor.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/mcp_orchestrator.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/imports/server_logging.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/pipulate/__init__.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/dependency_links.txt +0 -0
- {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/entry_points.txt +0 -0
- {pipulate-2.0 → pipulate-2.2}/pipulate.egg-info/requires.txt +0 -0
- {pipulate-2.0 → pipulate-2.2}/setup.cfg +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/__init__.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/advanced_automation_tools.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/botify_tools.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/conversation_tools.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/keychain_tools.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/mcp_tools.py +0 -0
- {pipulate-2.0 → pipulate-2.2}/tools/system_tools.py +0 -0
pipulate-2.2/MANIFEST.in
ADDED
|
@@ -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.
|
|
4
|
-
Summary:
|
|
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:
|
|
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
|
|
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
|
-
### 🔍
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.,
|
|
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
|
|
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
|
-
* **📈
|
|
709
|
-
* **✍️ Content Creators:** Use AI-assisted processes for
|
|
710
|
-
* **📊 Marketing Teams:** Execute consistent
|
|
711
|
-
* **🏢 Business Owners:** Access enterprise-level
|
|
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
|
|
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
|
|
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
|
|
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
|
-
### 🔍
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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.,
|
|
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
|
|
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
|
-
* **📈
|
|
652
|
-
* **✍️ Content Creators:** Use AI-assisted processes for
|
|
653
|
-
* **📊 Marketing Teams:** Execute consistent
|
|
654
|
-
* **🏢 Business Owners:** Access enterprise-level
|
|
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
|
|
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
|
|
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
|
|
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.
|
|
16
|
-
__version_description__ = "
|
|
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__ = "
|
|
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
|
|
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', '
|
|
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
|
|
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
|
-
"
|
|
93
|
-
"
|
|
94
|
-
"
|
|
95
|
-
"
|
|
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 "
|
|
102
|
+
return "Onboarding Not Finished 🔒", msg, None
|
|
98
103
|
|
|
99
104
|
elif not has_configured:
|
|
100
|
-
# STATE 2: The Guide
|
|
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}.
|
|
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
|
|
110
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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"
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1218
|
+
pip.speak("Botify API token saved. It has not been tested yet.", wait=True)
|
|
1207
1219
|
|
|
1208
|
-
|
|
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,
|
|
9
|
-
Div,
|
|
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(
|
|
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
|