pipulate 2.80__tar.gz → 2.81__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.80 → pipulate-2.81}/.agents/skills/journal/references/index.md +12 -7
- {pipulate-2.80 → pipulate-2.81}/PKG-INFO +1 -1
- {pipulate-2.80 → pipulate-2.81}/__init__.py +2 -2
- {pipulate-2.80 → pipulate-2.81}/pipulate/core.py +5 -90
- {pipulate-2.80 → pipulate-2.81}/pipulate.egg-info/PKG-INFO +1 -1
- {pipulate-2.80 → pipulate-2.81}/pipulate.egg-info/SOURCES.txt +0 -8
- {pipulate-2.80 → pipulate-2.81}/pyproject.toml +1 -1
- {pipulate-2.80 → pipulate-2.81}/server.py +283 -22
- {pipulate-2.80 → pipulate-2.81}/tools/__init__.py +2 -21
- {pipulate-2.80 → pipulate-2.81}/tools/mcp_tools.py +17 -665
- pipulate-2.80/apps/110_parameter_buster.py +0 -4158
- pipulate-2.80/apps/120_link_graph.py +0 -5319
- pipulate-2.80/apps/400_botify_trifecta.py +0 -5164
- pipulate-2.80/imports/botify/code_generators.py +0 -601
- pipulate-2.80/imports/botify/true_schema_discoverer.py +0 -432
- pipulate-2.80/imports/botify_code_generation.py +0 -424
- pipulate-2.80/imports/dom_processing/__init__.py +0 -0
- pipulate-2.80/tools/botify_tools.py +0 -475
- {pipulate-2.80 → pipulate-2.81}/.agents/skills/gsc-readonly/SKILL.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/.agents/skills/hello-workflow/SKILL.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/.agents/skills/journal/SKILL.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/.agents/skills/pipulate/SKILL.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/.agents/skills/roles/SKILL.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/.agents/skills/sheets-readonly/SKILL.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/AGENTS.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/AUDIT.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/LICENSE +0 -0
- {pipulate-2.80 → pipulate-2.81}/MANIFEST.in +0 -0
- {pipulate-2.80 → pipulate-2.81}/README.md +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/010_introduction.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/015_config.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/020_profiles.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/025_aspect.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/030_roles.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/040_hello_workflow.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/050_documentation.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/060_tasks.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/070_history.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/130_gap_analysis.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/200_workflow_genesis.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/210_widget_examples.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/220_roadmap.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/230_dev_assistant.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/240_simon_mcp.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/300_blank_placeholder.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/440_browser_automation.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/450_stream_simulator.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/510_text_field.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/520_text_area.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/530_dropdown.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/540_checkboxes.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/550_radios.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/560_range.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/570_switch.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/580_upload.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/610_markdown.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/620_mermaid.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/630_prism.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/640_javascript.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/710_pandas.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/720_rich.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/730_matplotlib.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/810_webbrowser.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/820_selenium.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/apps/830_pico_slider.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/cli.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/config.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/__init__.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/ai_dictdb.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/ai_tool_discovery_simple_parser.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/append_only_conversation.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/ascii_displays.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/crud.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/database_safety_wrapper.py +0 -0
- {pipulate-2.80/imports/botify → pipulate-2.81/imports/dom_processing}/__init__.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/dom_processing/ai_dom_beautifier.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/dom_processing/enhanced_dom_processor.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/durable_backup_system.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/mcp_orchestrator.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/server_logging.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/stream_orchestrator.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/imports/voice_synthesis.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/pipulate/__init__.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/pipulate.egg-info/dependency_links.txt +0 -0
- {pipulate-2.80 → pipulate-2.81}/pipulate.egg-info/entry_points.txt +0 -0
- {pipulate-2.80 → pipulate-2.81}/pipulate.egg-info/requires.txt +0 -0
- {pipulate-2.80 → pipulate-2.81}/pipulate.egg-info/top_level.txt +0 -0
- {pipulate-2.80 → pipulate-2.81}/setup.cfg +0 -0
- {pipulate-2.80 → pipulate-2.81}/tests/test_apply_airlock.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tests/test_mck_rep2.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/advanced_automation_tools.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/connector_tools.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/conversation_tools.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/dom_tools.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/keychain_tools.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/llm_optics.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/scraper_tools.py +0 -0
- {pipulate-2.80 → pipulate-2.81}/tools/system_tools.py +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# The Pipulate journal, indexed
|
|
2
2
|
|
|
3
|
-
> Auto-generated on 2026-10-
|
|
3
|
+
> Auto-generated on 2026-10-10 by `scripts/articles/generate_ai_context.py` and
|
|
4
4
|
> rewritten from scratch on every release. If this date looks stale, assume
|
|
5
|
-
> the rest of the repo is newer than this map.
|
|
5
|
+
> the rest of the repo is newer than this map. 1528 entries indexed.
|
|
6
6
|
|
|
7
7
|
This repository holds the *machinery*. The *reasoning*, the running journal
|
|
8
8
|
that explains why every piece exists, lives on a separate website and not in
|
|
@@ -27,6 +27,11 @@ The first 20 entries include full `index.md` URLs to establish
|
|
|
27
27
|
the link pattern. All remaining entries are bare slugs. Reconstruct any full
|
|
28
28
|
URL as: `https://mikelev.in/futureproof/{slug}/index.md`
|
|
29
29
|
|
|
30
|
+
- [2026-10-10] [Making a Diff: The Minimal Instrument](https://mikelev.in/futureproof/making-a-diff-the-minimal-instrument/index.md)
|
|
31
|
+
- [2026-10-09] [The Heat and the Delete: Landauer's Limit and Verifiable Codebase Pruning](https://mikelev.in/futureproof/the-heat-and-the-delete-verifiable-pruning/index.md)
|
|
32
|
+
- [2026-10-09] [The Inverted Cave: AI Quality Assurance and the Jevons Paradox of Code](https://mikelev.in/futureproof/inverted-cave-ai-quality-assurance-jevons-paradox/index.md)
|
|
33
|
+
- [2026-10-08] [The Anti-Crichton Pipeline: Intentional Friction and Replayable Receipts](https://mikelev.in/futureproof/anti-crichton-pipeline-intentional-friction/index.md)
|
|
34
|
+
- [2026-10-08] [The Randi Test: Grounding Agent-Readiness in Verifiable Receipts](https://mikelev.in/futureproof/the-randi-test-for-agent-readiness/index.md)
|
|
30
35
|
- [2026-10-08] [Jekyll Satellites: Decoupling Shared Tooling from Independent Sites with Nix](https://mikelev.in/futureproof/jekyll-satellites-shared-nix-kernel/index.md)
|
|
31
36
|
- [2026-10-07] [Removing the Conversion Event: When Paradigms Become Invisible Infrastructure](https://mikelev.in/futureproof/removing-the-conversion-event/index.md)
|
|
32
37
|
- [2026-10-07] [Public Installers, Private Entitlements, and Watching the Infrastructure Read](https://mikelev.in/futureproof/public-installers-private-entitlements-wire-telemetry/index.md)
|
|
@@ -42,16 +47,16 @@ URL as: `https://mikelev.in/futureproof/{slug}/index.md`
|
|
|
42
47
|
- [2026-10-02] [The Enter Fence and the Bell Cue: Building Replayable Browser Walks in the Age of AI](https://mikelev.in/futureproof/the-enter-fence-and-the-bell-cue/index.md)
|
|
43
48
|
- [2026-10-02] [The Clear Cups Protocol: Auditing MCP Calls with Replayable Receipts](https://mikelev.in/futureproof/clear-cups-protocol-auditing-mcp-calls/index.md)
|
|
44
49
|
- [2026-10-02] [The Door Table and the Moving Twig: Replayable Installs and the Stick Bug Secret](https://mikelev.in/futureproof/the-door-table-and-the-moving-twig/index.md)
|
|
45
|
-
- [2026-10-01] [The Scrollback Rule: Engineering Full-Width Terminal Dividers for Replayable Logs](https://mikelev.in/futureproof/scrollback-rule-clear-x-receipts/index.md)
|
|
46
|
-
- [2026-10-01] [The Prefix Ladder and the Unalias Guard: Replayable Shell Ergonomics in the Age of AI](https://mikelev.in/futureproof/prefix-ladders-and-the-unalias-guard/index.md)
|
|
47
|
-
- [2026-09-30] [Forever-Commands and the Talk Toggle: Replayable CLI Habits in the Age of AI](https://mikelev.in/futureproof/forever-commands-and-the-talk-toggle/index.md)
|
|
48
|
-
- [2026-09-29] [Layered Reality and Operator Overloading: Reading the Physics of CPython in the Age of AI](https://mikelev.in/futureproof/layered-reality-operator-overloading-cpython/index.md)
|
|
49
|
-
- [2026-09-29] [The Three-Door Installer: Named Prompts, Split DNS, and Verifiable Releases](https://mikelev.in/futureproof/the-three-door-installer-and-named-prompts/index.md)
|
|
50
50
|
|
|
51
51
|
## Compact slug index -- pattern: https://mikelev.in/futureproof/{slug}/index.md
|
|
52
52
|
|
|
53
53
|
Format: `[date] [size] slug` -- fetch any entry as `https://mikelev.in/futureproof/{slug}/index.md`
|
|
54
54
|
|
|
55
|
+
- [2026-10-01] [22k] scrollback-rule-clear-x-receipts
|
|
56
|
+
- [2026-10-01] [51k] prefix-ladders-and-the-unalias-guard
|
|
57
|
+
- [2026-09-30] [103k] forever-commands-and-the-talk-toggle
|
|
58
|
+
- [2026-09-29] [35k] layered-reality-operator-overloading-cpython
|
|
59
|
+
- [2026-09-29] [245k] the-three-door-installer-and-named-prompts
|
|
55
60
|
- [2026-09-29] [234k] the-deed-is-named-for-the-door-qa-archive-zip
|
|
56
61
|
- [2026-09-29] [200k] qamy-door-cellular-witness-replayable-receipts
|
|
57
62
|
- [2026-09-28] [368k] the-forward-slash-test-claude-skills-bridge
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipulate
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.81
|
|
4
4
|
Summary: The context compiler that helps you prompt well -- local-first, Nix-reproducible, replayable. The successor to AI SEO software.
|
|
5
5
|
Author-email: Mike Levin <pipulate@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -12,7 +12,7 @@ Usage:
|
|
|
12
12
|
pipulate
|
|
13
13
|
"""
|
|
14
14
|
|
|
15
|
-
__version__ = "2.
|
|
15
|
+
__version__ = "2.81"
|
|
16
16
|
# APOSTROPHES RESTORED (2026-08-04). They were stripped as a workaround for
|
|
17
17
|
# flake.nix's descMatch regex, whose character class excluded ' from the
|
|
18
18
|
# CAPTURE and truncated the banner to "(So)". That regex was fixed in the same
|
|
@@ -23,7 +23,7 @@ __version__ = "2.80"
|
|
|
23
23
|
# is not a property of the system; the regex is. Blast radius is one banner:
|
|
24
24
|
# nothing but flake.nix reads this name -- version_sync.py syncs __version__
|
|
25
25
|
# and __description__, never this. So'wI' chu' -- "engage the cloaking device."
|
|
26
|
-
__version_description__ = "
|
|
26
|
+
__version_description__ = "Less Messaging"
|
|
27
27
|
# SPDX expression, single source of truth, synced into pyproject.toml by
|
|
28
28
|
# scripts/release/version_sync.py. "-or-later" (not bare AGPL-3.0, which is
|
|
29
29
|
# deprecated SPDX) because the header below grants "any later version".
|
|
@@ -17,7 +17,6 @@ from loguru import logger
|
|
|
17
17
|
import imports.server_logging as slog
|
|
18
18
|
import config as CFG
|
|
19
19
|
from config import COLOR_MAP
|
|
20
|
-
from imports import botify_code_generation
|
|
21
20
|
from imports.stream_orchestrator import stream_orchestrator
|
|
22
21
|
from typing import AsyncGenerator, Optional
|
|
23
22
|
import imports.server_logging as slog
|
|
@@ -902,7 +901,6 @@ class Pipulate:
|
|
|
902
901
|
|
|
903
902
|
full_log_message = '\n'.join(log_entry_parts)
|
|
904
903
|
logger.info(f'\n🚀 === API CALL TRANSPARENCY ===\n{full_log_message}\n🚀 === END API TRANSPARENCY ===')
|
|
905
|
-
is_bql = 'bql' in (call_description or '').lower() or 'botify query language' in (call_description or '').lower()
|
|
906
904
|
|
|
907
905
|
def _is_discovery_endpoint(self, url: str) -> bool:
|
|
908
906
|
"""Detect if this is a key discovery endpoint that should have full response logging.
|
|
@@ -1165,45 +1163,7 @@ class Pipulate:
|
|
|
1165
1163
|
|
|
1166
1164
|
return '\n'.join(lines)
|
|
1167
1165
|
|
|
1168
|
-
# ========================================
|
|
1169
|
-
# REUSABLE BOTIFY PYTHON CODE GENERATION
|
|
1170
|
-
# ========================================
|
|
1171
1166
|
|
|
1172
|
-
def generate_botify_code_header(self, display_name: str, step_name: str, username: str, project_name: str,
|
|
1173
|
-
template_info: dict = None, qualifier_config: dict = None) -> list:
|
|
1174
|
-
"""Generate standardized header for Botify Python debugging code.
|
|
1175
|
-
|
|
1176
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1177
|
-
"""
|
|
1178
|
-
return botify_code_generation.generate_botify_code_header(
|
|
1179
|
-
display_name=display_name,
|
|
1180
|
-
step_name=step_name,
|
|
1181
|
-
username=username,
|
|
1182
|
-
project_name=project_name,
|
|
1183
|
-
template_info=template_info,
|
|
1184
|
-
qualifier_config=qualifier_config
|
|
1185
|
-
)
|
|
1186
|
-
|
|
1187
|
-
def generate_botify_token_loader(self) -> str:
|
|
1188
|
-
"""Generate the standard Botify token loading function.
|
|
1189
|
-
|
|
1190
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1191
|
-
"""
|
|
1192
|
-
return botify_code_generation.generate_botify_token_loader()
|
|
1193
|
-
|
|
1194
|
-
def generate_botify_http_client(self, client_name: str, description: str) -> str:
|
|
1195
|
-
"""Generate the standard HTTP client function for Botify APIs.
|
|
1196
|
-
|
|
1197
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1198
|
-
"""
|
|
1199
|
-
return botify_code_generation.generate_botify_http_client(client_name, description)
|
|
1200
|
-
|
|
1201
|
-
def generate_botify_main_executor(self, client_function_name: str, api_description: str) -> str:
|
|
1202
|
-
"""Generate the main execution function for Botify APIs.
|
|
1203
|
-
|
|
1204
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1205
|
-
"""
|
|
1206
|
-
return botify_code_generation.generate_botify_main_executor(client_function_name, api_description)
|
|
1207
1167
|
|
|
1208
1168
|
def create_folder_button(self, folder_path: str, icon: str = "📁", text: str = "Open Folder",
|
|
1209
1169
|
title_prefix: str = "Open folder") -> object:
|
|
@@ -1226,50 +1186,7 @@ class Pipulate:
|
|
|
1226
1186
|
cls="button-link"
|
|
1227
1187
|
)
|
|
1228
1188
|
|
|
1229
|
-
# ========================================
|
|
1230
|
-
# ADVANCED BOTIFY CODE GENERATION UTILITIES
|
|
1231
|
-
# ========================================
|
|
1232
1189
|
|
|
1233
|
-
def generate_botify_bqlv2_python_code(self, query_payload, username, project_name, page_size, jobs_payload, display_name, get_step_name_from_payload_func, get_configured_template_func=None, query_templates=None):
|
|
1234
|
-
"""
|
|
1235
|
-
🚀 REUSABLE UTILITY: Generate complete Python code for BQLv2 queries (crawl, GSC)
|
|
1236
|
-
|
|
1237
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1238
|
-
"""
|
|
1239
|
-
return botify_code_generation.generate_botify_bqlv2_python_code(
|
|
1240
|
-
query_payload=query_payload,
|
|
1241
|
-
username=username,
|
|
1242
|
-
project_name=project_name,
|
|
1243
|
-
page_size=page_size,
|
|
1244
|
-
jobs_payload=jobs_payload,
|
|
1245
|
-
display_name=display_name,
|
|
1246
|
-
get_step_name_from_payload_func=get_step_name_from_payload_func,
|
|
1247
|
-
get_configured_template_func=get_configured_template_func,
|
|
1248
|
-
query_templates=query_templates
|
|
1249
|
-
)
|
|
1250
|
-
|
|
1251
|
-
def generate_botify_bqlv1_python_code(self, query_payload, username, project_name, jobs_payload, display_name, get_step_name_from_payload_func):
|
|
1252
|
-
"""
|
|
1253
|
-
🚀 REUSABLE UTILITY: Generate complete Python code for BQLv1 queries (web logs)
|
|
1254
|
-
|
|
1255
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1256
|
-
"""
|
|
1257
|
-
return botify_code_generation.generate_botify_bqlv1_python_code(
|
|
1258
|
-
query_payload=query_payload,
|
|
1259
|
-
username=username,
|
|
1260
|
-
project_name=project_name,
|
|
1261
|
-
jobs_payload=jobs_payload,
|
|
1262
|
-
display_name=display_name,
|
|
1263
|
-
get_step_name_from_payload_func=get_step_name_from_payload_func
|
|
1264
|
-
)
|
|
1265
|
-
|
|
1266
|
-
def get_botify_analysis_path(self, app_name, username, project_name, analysis_slug, filename=None):
|
|
1267
|
-
"""
|
|
1268
|
-
🚀 REUSABLE UTILITY: Construct standardized Botify analysis file paths
|
|
1269
|
-
|
|
1270
|
-
Delegates to external botify_code_generation module to reduce server.py size.
|
|
1271
|
-
"""
|
|
1272
|
-
return botify_code_generation.get_botify_analysis_path(app_name, username, project_name, analysis_slug, filename)
|
|
1273
1190
|
|
|
1274
1191
|
def fmt(self, endpoint: str) -> str:
|
|
1275
1192
|
"""Format an endpoint string into a human-readable form."""
|
|
@@ -2466,8 +2383,6 @@ class Pipulate:
|
|
|
2466
2383
|
|
|
2467
2384
|
if "CLIENT_DOMAIN" in target_names:
|
|
2468
2385
|
node.value = ast.Constant(value="uhnd.com")
|
|
2469
|
-
elif "BOTIFY_PROJECT_URL" in target_names:
|
|
2470
|
-
node.value = ast.Constant(value="https://app.botify.com/uhnd-com/uhnd.com-demo-account/")
|
|
2471
2386
|
else:
|
|
2472
2387
|
# Replace all other string values with None
|
|
2473
2388
|
node.value = ast.Constant(value=None)
|
|
@@ -2835,8 +2750,8 @@ class Pipulate:
|
|
|
2835
2750
|
then immediately prompts the user to enter a new one via the UI widget.
|
|
2836
2751
|
|
|
2837
2752
|
Args:
|
|
2838
|
-
env_var_name (str): The environment variable to remove (e.g., '
|
|
2839
|
-
service_name (str): Friendly name for the UI (e.g., '
|
|
2753
|
+
env_var_name (str): The environment variable to remove (e.g., 'GEMINI_API_KEY').
|
|
2754
|
+
service_name (str): Friendly name for the UI (e.g., 'Gemini'). Auto-derived if None.
|
|
2840
2755
|
"""
|
|
2841
2756
|
import os
|
|
2842
2757
|
from dotenv import load_dotenv, set_key
|
|
@@ -3088,8 +3003,8 @@ class Pipulate:
|
|
|
3088
3003
|
mid-workflow lazy-loading crashes. Renders a secure widget if missing.
|
|
3089
3004
|
|
|
3090
3005
|
Args:
|
|
3091
|
-
env_var_name: The environment variable to look for (e.g., '
|
|
3092
|
-
service_name: Friendly name for the UI (e.g., '
|
|
3006
|
+
env_var_name: The environment variable to look for (e.g., 'GEMINI_API_KEY').
|
|
3007
|
+
service_name: Friendly name for the UI (e.g., 'Gemini'). Auto-derived if None.
|
|
3093
3008
|
force_prompt: If True, ignores cached credentials and forces the UI widget.
|
|
3094
3009
|
"""
|
|
3095
3010
|
import os
|
|
@@ -3098,7 +3013,7 @@ class Pipulate:
|
|
|
3098
3013
|
from dotenv import load_dotenv, set_key
|
|
3099
3014
|
|
|
3100
3015
|
if not service_name:
|
|
3101
|
-
# Auto-derive friendly name (e.g., '
|
|
3016
|
+
# Auto-derive friendly name (e.g., 'GEMINI_API_KEY' -> 'Gemini')
|
|
3102
3017
|
service_name = env_var_name.split('_')[0].title()
|
|
3103
3018
|
|
|
3104
3019
|
# 1. Load existing environment variables (override=True fights caching stubbornness)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pipulate
|
|
3
|
-
Version: 2.
|
|
3
|
+
Version: 2.81
|
|
4
4
|
Summary: The context compiler that helps you prompt well -- local-first, Nix-reproducible, replayable. The successor to AI SEO software.
|
|
5
5
|
Author-email: Mike Levin <pipulate@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-or-later
|
|
@@ -24,8 +24,6 @@ apps/040_hello_workflow.py
|
|
|
24
24
|
apps/050_documentation.py
|
|
25
25
|
apps/060_tasks.py
|
|
26
26
|
apps/070_history.py
|
|
27
|
-
apps/110_parameter_buster.py
|
|
28
|
-
apps/120_link_graph.py
|
|
29
27
|
apps/130_gap_analysis.py
|
|
30
28
|
apps/200_workflow_genesis.py
|
|
31
29
|
apps/210_widget_examples.py
|
|
@@ -33,7 +31,6 @@ apps/220_roadmap.py
|
|
|
33
31
|
apps/230_dev_assistant.py
|
|
34
32
|
apps/240_simon_mcp.py
|
|
35
33
|
apps/300_blank_placeholder.py
|
|
36
|
-
apps/400_botify_trifecta.py
|
|
37
34
|
apps/440_browser_automation.py
|
|
38
35
|
apps/450_stream_simulator.py
|
|
39
36
|
apps/510_text_field.py
|
|
@@ -59,7 +56,6 @@ imports/ai_dictdb.py
|
|
|
59
56
|
imports/ai_tool_discovery_simple_parser.py
|
|
60
57
|
imports/append_only_conversation.py
|
|
61
58
|
imports/ascii_displays.py
|
|
62
|
-
imports/botify_code_generation.py
|
|
63
59
|
imports/crud.py
|
|
64
60
|
imports/database_safety_wrapper.py
|
|
65
61
|
imports/durable_backup_system.py
|
|
@@ -67,9 +63,6 @@ imports/mcp_orchestrator.py
|
|
|
67
63
|
imports/server_logging.py
|
|
68
64
|
imports/stream_orchestrator.py
|
|
69
65
|
imports/voice_synthesis.py
|
|
70
|
-
imports/botify/__init__.py
|
|
71
|
-
imports/botify/code_generators.py
|
|
72
|
-
imports/botify/true_schema_discoverer.py
|
|
73
66
|
imports/dom_processing/__init__.py
|
|
74
67
|
imports/dom_processing/ai_dom_beautifier.py
|
|
75
68
|
imports/dom_processing/enhanced_dom_processor.py
|
|
@@ -85,7 +78,6 @@ tests/test_apply_airlock.py
|
|
|
85
78
|
tests/test_mck_rep2.py
|
|
86
79
|
tools/__init__.py
|
|
87
80
|
tools/advanced_automation_tools.py
|
|
88
|
-
tools/botify_tools.py
|
|
89
81
|
tools/connector_tools.py
|
|
90
82
|
tools/conversation_tools.py
|
|
91
83
|
tools/dom_tools.py
|
|
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "pipulate"
|
|
7
7
|
# VERSION NOTE: Synced from pipulate/__init__.py.__version__ via python version_sync.py
|
|
8
|
-
version = "2.
|
|
8
|
+
version = "2.81"
|
|
9
9
|
authors = [
|
|
10
10
|
{ name="Mike Levin", email="pipulate@gmail.com" },
|
|
11
11
|
]
|
|
@@ -72,7 +72,7 @@ from config import get_db_filename, get_app_name, APP_NAME, get_current_environm
|
|
|
72
72
|
import imports.ascii_displays as aa
|
|
73
73
|
import tools.mcp_tools as mcp_tools
|
|
74
74
|
from tools import get_all_tools
|
|
75
|
-
from imports import
|
|
75
|
+
from imports import mcp_orchestrator
|
|
76
76
|
import imports.server_logging as slog
|
|
77
77
|
|
|
78
78
|
from pipulate.core import Pipulate
|
|
@@ -616,7 +616,7 @@ def append_to_conversation(message=None, role='user'):
|
|
|
616
616
|
|
|
617
617
|
Root Cause: modules.append_only_conversation.get_conversation_system() creates a
|
|
618
618
|
separate SQLite connection to data/discussion.db while the main app uses
|
|
619
|
-
data/
|
|
619
|
+
data/pipulate.db. SQLite doesn't handle concurrent connections well, causing
|
|
620
620
|
transaction corruption and data loss.
|
|
621
621
|
|
|
622
622
|
Solution: Use simple in-memory deque for conversation history. This eliminates the
|
|
@@ -3592,11 +3592,6 @@ async def mcp_tool_executor_endpoint(request):
|
|
|
3592
3592
|
if tool_name == "pipeline_state_inspector":
|
|
3593
3593
|
pipeline_count = len(tool_result.get("result", {}).get("pipelines", []))
|
|
3594
3594
|
logger.info(f"🔧 MCP_SEMANTIC: Pipeline inspector found {pipeline_count} active pipelines")
|
|
3595
|
-
elif tool_name.startswith("botify_"):
|
|
3596
|
-
if "projects" in str(tool_result.get("result", "")):
|
|
3597
|
-
logger.info(f"🔧 MCP_SEMANTIC: Botify API call returned project data")
|
|
3598
|
-
elif "schema" in str(tool_result.get("result", "")):
|
|
3599
|
-
logger.info(f"🔧 MCP_SEMANTIC: Botify API call returned schema information")
|
|
3600
3595
|
elif tool_name.startswith("local_llm_"):
|
|
3601
3596
|
if tool_name == "local_llm_grep_logs":
|
|
3602
3597
|
matches = tool_result.get("result", {}).get("matches", [])
|
|
@@ -3737,8 +3732,7 @@ async def reset_config_onboarding(request):
|
|
|
3737
3732
|
"OPENAI_API_KEY",
|
|
3738
3733
|
"ANTHROPIC_API_KEY",
|
|
3739
3734
|
"GEMINI_API_KEY",
|
|
3740
|
-
"GOOGLE_API_KEY"
|
|
3741
|
-
"BOTIFY_API_TOKEN"
|
|
3735
|
+
"GOOGLE_API_KEY"
|
|
3742
3736
|
]
|
|
3743
3737
|
|
|
3744
3738
|
# Clear from active memory
|
|
@@ -4866,8 +4860,7 @@ async def prepare_local_llm_context():
|
|
|
4866
4860
|
"available_mcp_tools": {
|
|
4867
4861
|
"file_access": ["local_llm_read_file", "local_llm_list_files"],
|
|
4868
4862
|
"log_search": ["local_llm_grep_logs"],
|
|
4869
|
-
"state_inspection": ["pipeline_state_inspector"]
|
|
4870
|
-
"botify_api": ["botify_get_full_schema", "botify_list_available_analyses", "botify_execute_custom_bql_query"]
|
|
4863
|
+
"state_inspection": ["pipeline_state_inspector"]
|
|
4871
4864
|
},
|
|
4872
4865
|
"key_directories": {
|
|
4873
4866
|
"training": "AI training materials and guides",
|
|
@@ -4875,11 +4868,6 @@ async def prepare_local_llm_context():
|
|
|
4875
4868
|
"helpers": "Utility scripts and API integrations",
|
|
4876
4869
|
"logs": "Server logs with FINDER_TOKEN patterns"
|
|
4877
4870
|
},
|
|
4878
|
-
"botify_capabilities": {
|
|
4879
|
-
"demo_projects": ["uhnd.com-demo-account", "mikelev.in"],
|
|
4880
|
-
"key_features": ["GA4/Adobe Analytics integration", "Traffic source attribution", "Custom BQL queries"],
|
|
4881
|
-
"field_count": "4,449+ fields available via schema discovery"
|
|
4882
|
-
},
|
|
4883
4871
|
"transparency_patterns": {
|
|
4884
4872
|
"log_tokens": "Search logs with FINDER_TOKEN patterns",
|
|
4885
4873
|
"mcp_execution": "All tool calls logged with full transparency",
|
|
@@ -4903,12 +4891,11 @@ async def prepare_local_llm_context():
|
|
|
4903
4891
|
context_msg = """🤖 Local LLM Context Initialized
|
|
4904
4892
|
|
|
4905
4893
|
Your MCP tools are now available:
|
|
4906
|
-
|
|
4907
|
-
|
|
4908
|
-
|
|
4909
|
-
|
|
4910
|
-
|
|
4911
|
-
• Botify API tools - Full schema access with 4,449+ fields
|
|
4894
|
+
- local_llm_get_context - Get system overview
|
|
4895
|
+
- local_llm_read_file - Read training materials and code
|
|
4896
|
+
- local_llm_list_files - Explore safe directories
|
|
4897
|
+
- local_llm_grep_logs - Search server logs for patterns
|
|
4898
|
+
- pipeline_state_inspector - Check application state
|
|
4912
4899
|
|
|
4913
4900
|
Use these tools to assist users within your guided capabilities. Remember that advanced AI exploration (file system access, complex debugging) is handled by Claude/GPT in Cursor/Windsurf/VSCode when needed."""
|
|
4914
4901
|
|
|
@@ -4922,6 +4909,280 @@ Use these tools to assist users within your guided capabilities. Remember that a
|
|
|
4922
4909
|
logger.error(f"Error preparing local LLM context: {e}")
|
|
4923
4910
|
# Don't fail startup if context preparation fails
|
|
4924
4911
|
|
|
4912
|
+
@rt('/diff')
|
|
4913
|
+
@rt('/preview')
|
|
4914
|
+
async def diff_preview(request):
|
|
4915
|
+
"""Live-preview prompt.md and working tree git diffs with Prism syntax highlighting."""
|
|
4916
|
+
pipulate_root = Path(os.environ.get('PIPULATE_ROOT', os.getcwd()))
|
|
4917
|
+
prompt_path = pipulate_root / 'prompt.md'
|
|
4918
|
+
|
|
4919
|
+
prompt_content = ""
|
|
4920
|
+
if prompt_path.exists():
|
|
4921
|
+
try:
|
|
4922
|
+
prompt_content = prompt_path.read_text(encoding='utf-8')
|
|
4923
|
+
prompt_content = prompt_content.replace('\r\n', '\n').replace('\r', '\n')
|
|
4924
|
+
except Exception as e:
|
|
4925
|
+
prompt_content = f"Error reading prompt.md: {e}"
|
|
4926
|
+
else:
|
|
4927
|
+
prompt_content = f"No prompt.md found at {prompt_path}"
|
|
4928
|
+
|
|
4929
|
+
git_diff = ""
|
|
4930
|
+
try:
|
|
4931
|
+
diff_proc = subprocess.run(
|
|
4932
|
+
['git', 'diff', 'HEAD'],
|
|
4933
|
+
cwd=str(pipulate_root),
|
|
4934
|
+
capture_output=True,
|
|
4935
|
+
text=True,
|
|
4936
|
+
timeout=5
|
|
4937
|
+
)
|
|
4938
|
+
if diff_proc.returncode == 0:
|
|
4939
|
+
git_diff = diff_proc.stdout.strip()
|
|
4940
|
+
except Exception as e:
|
|
4941
|
+
git_diff = f"Error running git diff: {e}"
|
|
4942
|
+
|
|
4943
|
+
diff_section = Article(
|
|
4944
|
+
Header(
|
|
4945
|
+
H3("Working Tree Diff (git diff HEAD)", style="margin-bottom: 0.25rem;"),
|
|
4946
|
+
P("Cherenkov flash: red deletions (erased state), green additions (mutated state)",
|
|
4947
|
+
cls="text-muted", style="margin-bottom: 0; font-size: 0.85rem;")
|
|
4948
|
+
),
|
|
4949
|
+
Pre(Code(git_diff, cls="language-diff"), cls="language-diff", style="max-height: 450px; overflow-y: auto; overflow-x: auto; max-width: 100%; box-sizing: border-box;")
|
|
4950
|
+
if git_diff else
|
|
4951
|
+
P("✓ Working tree clean (zero uncommitted diffs relative to HEAD).",
|
|
4952
|
+
style="color: var(--pico-color-green-500); margin: 1rem 0; font-weight: bold;")
|
|
4953
|
+
)
|
|
4954
|
+
|
|
4955
|
+
prompt_json = json.dumps(prompt_content).replace('</', '<\\/')
|
|
4956
|
+
prompt_section = Article(
|
|
4957
|
+
Header(
|
|
4958
|
+
H3("prompt.md Preview", style="margin-bottom: 0.25rem;"),
|
|
4959
|
+
P(f"Source: {prompt_path}",
|
|
4960
|
+
cls="text-muted", style="margin-bottom: 0; font-family: monospace; font-size: 0.8rem;")
|
|
4961
|
+
),
|
|
4962
|
+
Div(id="prompt-rendered-view", style="min-height: 100px;"),
|
|
4963
|
+
Script(f"""
|
|
4964
|
+
(function() {{
|
|
4965
|
+
function renderPrompt() {{
|
|
4966
|
+
const viewEl = document.getElementById('prompt-rendered-view');
|
|
4967
|
+
if (!viewEl) return;
|
|
4968
|
+
const raw = {prompt_json};
|
|
4969
|
+
let content = raw;
|
|
4970
|
+
if (content.trim().startsWith('diff --git') || content.trim().startsWith('--- a/')) {{
|
|
4971
|
+
content = '```diff\\n' + content + '\\n```';
|
|
4972
|
+
}}
|
|
4973
|
+
|
|
4974
|
+
function updateView(html) {{
|
|
4975
|
+
if (typeof html !== 'string') {{
|
|
4976
|
+
if (html && typeof html === 'object') {{
|
|
4977
|
+
html = html.html || html.body || html.content || html.text || '';
|
|
4978
|
+
}}
|
|
4979
|
+
}}
|
|
4980
|
+
if (!html || html === '[object Object]' || html === '[object Promise]') {{
|
|
4981
|
+
const pre = document.createElement('pre');
|
|
4982
|
+
const code = document.createElement('code');
|
|
4983
|
+
code.className = 'language-markdown';
|
|
4984
|
+
code.textContent = raw;
|
|
4985
|
+
pre.appendChild(code);
|
|
4986
|
+
viewEl.innerHTML = '';
|
|
4987
|
+
viewEl.appendChild(pre);
|
|
4988
|
+
}} else {{
|
|
4989
|
+
viewEl.innerHTML = html;
|
|
4990
|
+
}}
|
|
4991
|
+
if (window.Prism) {{
|
|
4992
|
+
Prism.highlightAll();
|
|
4993
|
+
}}
|
|
4994
|
+
}}
|
|
4995
|
+
|
|
4996
|
+
window.copyCodeBlock = function(btn) {{
|
|
4997
|
+
const wrapper = btn.closest('.code-block-wrapper');
|
|
4998
|
+
const code = wrapper ? wrapper.querySelector('code') : null;
|
|
4999
|
+
if (code && navigator.clipboard) {{
|
|
5000
|
+
navigator.clipboard.writeText(code.textContent);
|
|
5001
|
+
btn.textContent = '✓ Copied';
|
|
5002
|
+
setTimeout(function() {{ btn.textContent = 'Copy'; }}, 1500);
|
|
5003
|
+
}}
|
|
5004
|
+
}};
|
|
5005
|
+
|
|
5006
|
+
if (window.marked) {{
|
|
5007
|
+
const prevCode = marked.defaults && marked.defaults.renderer && marked.defaults.renderer.code;
|
|
5008
|
+
const origCode = (prevCode && !prevCode._isFixCode) ? prevCode : null;
|
|
5009
|
+
|
|
5010
|
+
const fixCode = function(codeOrToken, infostring, escaped) {{
|
|
5011
|
+
let text = '';
|
|
5012
|
+
let lang = '';
|
|
5013
|
+
if (typeof codeOrToken === 'object' && codeOrToken !== null) {{
|
|
5014
|
+
text = codeOrToken.text || codeOrToken.raw || '';
|
|
5015
|
+
lang = (codeOrToken.lang || infostring || '');
|
|
5016
|
+
}} else {{
|
|
5017
|
+
text = String(codeOrToken || '');
|
|
5018
|
+
lang = (infostring || '');
|
|
5019
|
+
}}
|
|
5020
|
+
lang = lang.trim().split(/\\s+/)[0];
|
|
5021
|
+
|
|
5022
|
+
let rendered = null;
|
|
5023
|
+
if (origCode) {{
|
|
5024
|
+
try {{
|
|
5025
|
+
rendered = origCode.call(this, text, lang, escaped);
|
|
5026
|
+
}} catch (e) {{}}
|
|
5027
|
+
}}
|
|
5028
|
+
if (!rendered || typeof rendered !== 'string' || rendered.includes('[object Object]')) {{
|
|
5029
|
+
const esc = text
|
|
5030
|
+
.replace(/&/g, '&')
|
|
5031
|
+
.replace(/</g, '<')
|
|
5032
|
+
.replace(/>/g, '>')
|
|
5033
|
+
.replace(/"/g, '"')
|
|
5034
|
+
.replace(/'/g, ''');
|
|
5035
|
+
const validLang = lang || 'text';
|
|
5036
|
+
return '<div class="code-block-wrapper" style="position: relative;">' +
|
|
5037
|
+
'<button type="button" class="copy-code-btn" onclick="copyCodeBlock(this)" style="position: absolute; top: 0.5rem; right: 0.5rem; font-size: 0.75rem; padding: 0.2rem 0.5rem; z-index: 10; cursor: pointer; opacity: 0.8;">Copy</button>' +
|
|
5038
|
+
'<pre class="language-' + validLang + '"><code class="language-' + validLang + '">' + esc + '</code></pre>' +
|
|
5039
|
+
'</div>';
|
|
5040
|
+
}}
|
|
5041
|
+
return rendered;
|
|
5042
|
+
}};
|
|
5043
|
+
fixCode._isFixCode = true;
|
|
5044
|
+
|
|
5045
|
+
if (marked.use) {{
|
|
5046
|
+
marked.use({{ renderer: {{ code: fixCode }} }});
|
|
5047
|
+
}} else if (marked.setOptions) {{
|
|
5048
|
+
const r = new marked.Renderer();
|
|
5049
|
+
r.code = fixCode;
|
|
5050
|
+
marked.setOptions({{ renderer: r }});
|
|
5051
|
+
}} else if (marked.defaults && marked.defaults.renderer) {{
|
|
5052
|
+
marked.defaults.renderer.code = fixCode;
|
|
5053
|
+
}}
|
|
5054
|
+
}}
|
|
5055
|
+
|
|
5056
|
+
try {{
|
|
5057
|
+
let out = null;
|
|
5058
|
+
if (window.marked) {{
|
|
5059
|
+
if (typeof marked.parse === 'function') {{
|
|
5060
|
+
out = marked.parse(content);
|
|
5061
|
+
}} else if (typeof marked === 'function') {{
|
|
5062
|
+
out = marked(content);
|
|
5063
|
+
}}
|
|
5064
|
+
}}
|
|
5065
|
+
if (out && typeof out.then === 'function') {{
|
|
5066
|
+
out.then(updateView).catch(function(err) {{
|
|
5067
|
+
console.error('Marked async parse error:', err);
|
|
5068
|
+
updateView(null);
|
|
5069
|
+
}});
|
|
5070
|
+
}} else if (out !== null && out !== undefined) {{
|
|
5071
|
+
updateView(out);
|
|
5072
|
+
}} else {{
|
|
5073
|
+
updateView(null);
|
|
5074
|
+
}}
|
|
5075
|
+
}} catch (e) {{
|
|
5076
|
+
console.error('Marked parsing error:', e);
|
|
5077
|
+
updateView(null);
|
|
5078
|
+
}}
|
|
5079
|
+
}}
|
|
5080
|
+
|
|
5081
|
+
if (document.readyState === 'loading') {{
|
|
5082
|
+
document.addEventListener('DOMContentLoaded', renderPrompt);
|
|
5083
|
+
}} else {{
|
|
5084
|
+
setTimeout(renderPrompt, 50);
|
|
5085
|
+
}}
|
|
5086
|
+
}})();
|
|
5087
|
+
""")
|
|
5088
|
+
)
|
|
5089
|
+
|
|
5090
|
+
diff_styling = Style("""
|
|
5091
|
+
html, body {
|
|
5092
|
+
height: auto !important;
|
|
5093
|
+
min-height: 100% !important;
|
|
5094
|
+
overflow-x: auto !important;
|
|
5095
|
+
overflow-y: auto !important;
|
|
5096
|
+
}
|
|
5097
|
+
body > main:first-of-type {
|
|
5098
|
+
height: auto !important;
|
|
5099
|
+
min-height: 100vh !important;
|
|
5100
|
+
overflow: visible !important;
|
|
5101
|
+
}
|
|
5102
|
+
body > main:first-of-type > main.container {
|
|
5103
|
+
height: auto !important;
|
|
5104
|
+
overflow: visible !important;
|
|
5105
|
+
max-width: 960px !important;
|
|
5106
|
+
width: 100% !important;
|
|
5107
|
+
padding-bottom: 5rem !important;
|
|
5108
|
+
box-sizing: border-box !important;
|
|
5109
|
+
}
|
|
5110
|
+
main.container > nav {
|
|
5111
|
+
position: sticky;
|
|
5112
|
+
top: 0;
|
|
5113
|
+
z-index: 100;
|
|
5114
|
+
background-color: var(--pico-background-color);
|
|
5115
|
+
border-bottom: 1px solid var(--pico-muted-border-color);
|
|
5116
|
+
padding: 0.5rem 0;
|
|
5117
|
+
margin-bottom: 1.5rem;
|
|
5118
|
+
}
|
|
5119
|
+
article, #prompt-rendered-view {
|
|
5120
|
+
max-width: 100% !important;
|
|
5121
|
+
box-sizing: border-box !important;
|
|
5122
|
+
}
|
|
5123
|
+
.code-block-wrapper, .code-container {
|
|
5124
|
+
position: relative !important;
|
|
5125
|
+
max-width: 100% !important;
|
|
5126
|
+
width: 100% !important;
|
|
5127
|
+
box-sizing: border-box !important;
|
|
5128
|
+
margin-bottom: 1rem !important;
|
|
5129
|
+
}
|
|
5130
|
+
pre[class*="language-"], pre {
|
|
5131
|
+
border: 1px solid var(--pico-muted-border-color);
|
|
5132
|
+
border-radius: var(--pico-border-radius);
|
|
5133
|
+
padding: 1rem;
|
|
5134
|
+
background-color: var(--pico-card-background-color);
|
|
5135
|
+
overflow-x: auto !important;
|
|
5136
|
+
max-width: 100% !important;
|
|
5137
|
+
box-sizing: border-box !important;
|
|
5138
|
+
white-space: pre !important;
|
|
5139
|
+
word-break: normal !important;
|
|
5140
|
+
word-wrap: normal !important;
|
|
5141
|
+
}
|
|
5142
|
+
pre code, pre[class*="language-"] code {
|
|
5143
|
+
white-space: pre !important;
|
|
5144
|
+
word-break: normal !important;
|
|
5145
|
+
word-wrap: normal !important;
|
|
5146
|
+
}
|
|
5147
|
+
.token.inserted, pre[class*="language-"] .token.inserted, code[class*="language-"] .token.inserted {
|
|
5148
|
+
color: #2ea043 !important;
|
|
5149
|
+
background-color: rgba(46, 160, 67, 0.15) !important;
|
|
5150
|
+
}
|
|
5151
|
+
.token.deleted, pre[class*="language-"] .token.deleted, code[class*="language-"] .token.deleted {
|
|
5152
|
+
color: #f85149 !important;
|
|
5153
|
+
background-color: rgba(248, 81, 73, 0.15) !important;
|
|
5154
|
+
}
|
|
5155
|
+
.token.coord, pre[class*="language-"] .token.coord {
|
|
5156
|
+
color: #58a6ff !important;
|
|
5157
|
+
font-weight: bold;
|
|
5158
|
+
}
|
|
5159
|
+
table {
|
|
5160
|
+
display: block !important;
|
|
5161
|
+
max-width: 100% !important;
|
|
5162
|
+
overflow-x: auto !important;
|
|
5163
|
+
}
|
|
5164
|
+
""")
|
|
5165
|
+
|
|
5166
|
+
nav = Nav(
|
|
5167
|
+
Ul(
|
|
5168
|
+
Li(Strong(A(APP_NAME, href='/', cls='contrast'))),
|
|
5169
|
+
Li(Span(" / ")),
|
|
5170
|
+
Li(Span("Diff & Prompt Preview"))
|
|
5171
|
+
),
|
|
5172
|
+
Ul(
|
|
5173
|
+
Li(A("🏠 Back to Workshop", href='/', role='button', cls='secondary outline', style='padding: 0.35rem 0.75rem; font-size: 0.85rem;')),
|
|
5174
|
+
Li(A("🔄 Refresh", href=request.url.path, role='button', cls='outline', style='padding: 0.35rem 0.75rem; font-size: 0.85rem;'))
|
|
5175
|
+
),
|
|
5176
|
+
style="margin-bottom: 1.5rem;"
|
|
5177
|
+
)
|
|
5178
|
+
|
|
5179
|
+
return (
|
|
5180
|
+
Title(f"{APP_NAME} - Diff & Prompt Preview"),
|
|
5181
|
+
diff_styling,
|
|
5182
|
+
Main(nav, diff_section, prompt_section, cls="container", style="max-width: 960px; padding-top: 1rem;")
|
|
5183
|
+
)
|
|
5184
|
+
|
|
5185
|
+
|
|
4925
5186
|
ALL_ROUTES = list(set([''] + MENU_ITEMS))
|
|
4926
5187
|
for route in ALL_ROUTES:
|
|
4927
5188
|
route_path = f'/{route}' if route else '/'
|