pipulate 2.59__tar.gz → 2.61__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 (89) hide show
  1. {pipulate-2.59/pipulate.egg-info → pipulate-2.61}/PKG-INFO +8 -8
  2. {pipulate-2.59 → pipulate-2.61}/README.md +7 -7
  3. {pipulate-2.59 → pipulate-2.61}/__init__.py +2 -2
  4. {pipulate-2.59 → pipulate-2.61}/config.py +23 -1
  5. {pipulate-2.59 → pipulate-2.61}/imports/ascii_displays.py +8 -8
  6. {pipulate-2.59 → pipulate-2.61/pipulate.egg-info}/PKG-INFO +8 -8
  7. {pipulate-2.59 → pipulate-2.61}/pyproject.toml +1 -1
  8. {pipulate-2.59 → pipulate-2.61}/tools/scraper_tools.py +16 -6
  9. {pipulate-2.59 → pipulate-2.61}/LICENSE +0 -0
  10. {pipulate-2.59 → pipulate-2.61}/MANIFEST.in +0 -0
  11. {pipulate-2.59 → pipulate-2.61}/apps/010_introduction.py +0 -0
  12. {pipulate-2.59 → pipulate-2.61}/apps/015_config.py +0 -0
  13. {pipulate-2.59 → pipulate-2.61}/apps/020_profiles.py +0 -0
  14. {pipulate-2.59 → pipulate-2.61}/apps/025_aspect.py +0 -0
  15. {pipulate-2.59 → pipulate-2.61}/apps/030_roles.py +0 -0
  16. {pipulate-2.59 → pipulate-2.61}/apps/040_hello_workflow.py +0 -0
  17. {pipulate-2.59 → pipulate-2.61}/apps/050_documentation.py +0 -0
  18. {pipulate-2.59 → pipulate-2.61}/apps/060_tasks.py +0 -0
  19. {pipulate-2.59 → pipulate-2.61}/apps/070_history.py +0 -0
  20. {pipulate-2.59 → pipulate-2.61}/apps/110_parameter_buster.py +0 -0
  21. {pipulate-2.59 → pipulate-2.61}/apps/120_link_graph.py +0 -0
  22. {pipulate-2.59 → pipulate-2.61}/apps/130_gap_analysis.py +0 -0
  23. {pipulate-2.59 → pipulate-2.61}/apps/200_workflow_genesis.py +0 -0
  24. {pipulate-2.59 → pipulate-2.61}/apps/210_widget_examples.py +0 -0
  25. {pipulate-2.59 → pipulate-2.61}/apps/220_roadmap.py +0 -0
  26. {pipulate-2.59 → pipulate-2.61}/apps/230_dev_assistant.py +0 -0
  27. {pipulate-2.59 → pipulate-2.61}/apps/240_simon_mcp.py +0 -0
  28. {pipulate-2.59 → pipulate-2.61}/apps/300_blank_placeholder.py +0 -0
  29. {pipulate-2.59 → pipulate-2.61}/apps/400_botify_trifecta.py +0 -0
  30. {pipulate-2.59 → pipulate-2.61}/apps/440_browser_automation.py +0 -0
  31. {pipulate-2.59 → pipulate-2.61}/apps/450_stream_simulator.py +0 -0
  32. {pipulate-2.59 → pipulate-2.61}/apps/510_text_field.py +0 -0
  33. {pipulate-2.59 → pipulate-2.61}/apps/520_text_area.py +0 -0
  34. {pipulate-2.59 → pipulate-2.61}/apps/530_dropdown.py +0 -0
  35. {pipulate-2.59 → pipulate-2.61}/apps/540_checkboxes.py +0 -0
  36. {pipulate-2.59 → pipulate-2.61}/apps/550_radios.py +0 -0
  37. {pipulate-2.59 → pipulate-2.61}/apps/560_range.py +0 -0
  38. {pipulate-2.59 → pipulate-2.61}/apps/570_switch.py +0 -0
  39. {pipulate-2.59 → pipulate-2.61}/apps/580_upload.py +0 -0
  40. {pipulate-2.59 → pipulate-2.61}/apps/610_markdown.py +0 -0
  41. {pipulate-2.59 → pipulate-2.61}/apps/620_mermaid.py +0 -0
  42. {pipulate-2.59 → pipulate-2.61}/apps/630_prism.py +0 -0
  43. {pipulate-2.59 → pipulate-2.61}/apps/640_javascript.py +0 -0
  44. {pipulate-2.59 → pipulate-2.61}/apps/710_pandas.py +0 -0
  45. {pipulate-2.59 → pipulate-2.61}/apps/720_rich.py +0 -0
  46. {pipulate-2.59 → pipulate-2.61}/apps/730_matplotlib.py +0 -0
  47. {pipulate-2.59 → pipulate-2.61}/apps/810_webbrowser.py +0 -0
  48. {pipulate-2.59 → pipulate-2.61}/apps/820_selenium.py +0 -0
  49. {pipulate-2.59 → pipulate-2.61}/apps/830_pico_slider.py +0 -0
  50. {pipulate-2.59 → pipulate-2.61}/cli.py +0 -0
  51. {pipulate-2.59 → pipulate-2.61}/imports/__init__.py +0 -0
  52. {pipulate-2.59 → pipulate-2.61}/imports/ai_dictdb.py +0 -0
  53. {pipulate-2.59 → pipulate-2.61}/imports/ai_tool_discovery_simple_parser.py +0 -0
  54. {pipulate-2.59 → pipulate-2.61}/imports/append_only_conversation.py +0 -0
  55. {pipulate-2.59 → pipulate-2.61}/imports/botify/__init__.py +0 -0
  56. {pipulate-2.59 → pipulate-2.61}/imports/botify/code_generators.py +0 -0
  57. {pipulate-2.59 → pipulate-2.61}/imports/botify/true_schema_discoverer.py +0 -0
  58. {pipulate-2.59 → pipulate-2.61}/imports/botify_code_generation.py +0 -0
  59. {pipulate-2.59 → pipulate-2.61}/imports/crud.py +0 -0
  60. {pipulate-2.59 → pipulate-2.61}/imports/database_safety_wrapper.py +0 -0
  61. {pipulate-2.59 → pipulate-2.61}/imports/dom_processing/__init__.py +0 -0
  62. {pipulate-2.59 → pipulate-2.61}/imports/dom_processing/ai_dom_beautifier.py +0 -0
  63. {pipulate-2.59 → pipulate-2.61}/imports/dom_processing/enhanced_dom_processor.py +0 -0
  64. {pipulate-2.59 → pipulate-2.61}/imports/durable_backup_system.py +0 -0
  65. {pipulate-2.59 → pipulate-2.61}/imports/mcp_orchestrator.py +0 -0
  66. {pipulate-2.59 → pipulate-2.61}/imports/server_logging.py +0 -0
  67. {pipulate-2.59 → pipulate-2.61}/imports/stream_orchestrator.py +0 -0
  68. {pipulate-2.59 → pipulate-2.61}/imports/voice_synthesis.py +0 -0
  69. {pipulate-2.59 → pipulate-2.61}/pipulate/__init__.py +0 -0
  70. {pipulate-2.59 → pipulate-2.61}/pipulate/core.py +0 -0
  71. {pipulate-2.59 → pipulate-2.61}/pipulate.egg-info/SOURCES.txt +0 -0
  72. {pipulate-2.59 → pipulate-2.61}/pipulate.egg-info/dependency_links.txt +0 -0
  73. {pipulate-2.59 → pipulate-2.61}/pipulate.egg-info/entry_points.txt +0 -0
  74. {pipulate-2.59 → pipulate-2.61}/pipulate.egg-info/requires.txt +0 -0
  75. {pipulate-2.59 → pipulate-2.61}/pipulate.egg-info/top_level.txt +0 -0
  76. {pipulate-2.59 → pipulate-2.61}/server.py +0 -0
  77. {pipulate-2.59 → pipulate-2.61}/setup.cfg +0 -0
  78. {pipulate-2.59 → pipulate-2.61}/tests/test_apply_airlock.py +0 -0
  79. {pipulate-2.59 → pipulate-2.61}/tests/test_mck_rep2.py +0 -0
  80. {pipulate-2.59 → pipulate-2.61}/tools/__init__.py +0 -0
  81. {pipulate-2.59 → pipulate-2.61}/tools/advanced_automation_tools.py +0 -0
  82. {pipulate-2.59 → pipulate-2.61}/tools/botify_tools.py +0 -0
  83. {pipulate-2.59 → pipulate-2.61}/tools/connector_tools.py +0 -0
  84. {pipulate-2.59 → pipulate-2.61}/tools/conversation_tools.py +0 -0
  85. {pipulate-2.59 → pipulate-2.61}/tools/dom_tools.py +0 -0
  86. {pipulate-2.59 → pipulate-2.61}/tools/keychain_tools.py +0 -0
  87. {pipulate-2.59 → pipulate-2.61}/tools/llm_optics.py +0 -0
  88. {pipulate-2.59 → pipulate-2.61}/tools/mcp_tools.py +0 -0
  89. {pipulate-2.59 → pipulate-2.61}/tools/system_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.59
3
+ Version: 2.61
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
@@ -62,13 +62,13 @@ Dynamic: license-file
62
62
  # Pipulate: The Context Compiler That Helps You Prompt Well
63
63
 
64
64
  ```text
65
- ( Like a canary you say? )
66
- O /) ____ The "No Problem" Framework
67
- > I HEREBY WILL NOT RE-GENERATE o /)\__// / \ Pipulate - Protecting Your Code
68
- > Once upon machines be smarten ___(/_ 0 0 | | just by being honest about text.
69
- > ASCII sealing immutata art in *( ==(_T_)== NPvg | (If mangled, then AI drifted.)
70
- > This here cony if it's broken \ ) ""\ | | https://pipulate.com
71
- > Smokin gun drift now in token |__>-\_>_> \____/ 🥕🥕🥕
65
+ ( "The AI said so" is a transcript, not a record. )
66
+ O /) ____ This drawing is checksummed.
67
+ > Same files, same words, same bytes: o /)\__// / \ If it reaches you altered, the
68
+ > a change you can replay is a change ___(/_ 0 0 | | model rewrote what it was told to
69
+ > you can put your name behind. *( ==(_T_)== NPvg | copy, and that is the finding.
70
+ > One diff per turn, a reading before \ ) ""\ | | Verify with nothing installed:
71
+ > and after, and a digest you can check. |__>-\_>_> \____/ python scripts/foo_cartridge.py foo.zip
72
72
  ```
73
73
 
74
74
  ## If you can't reproduce it, it didn't happen
@@ -5,13 +5,13 @@
5
5
  # Pipulate: The Context Compiler That Helps You Prompt Well
6
6
 
7
7
  ```text
8
- ( Like a canary you say? )
9
- O /) ____ The "No Problem" Framework
10
- > I HEREBY WILL NOT RE-GENERATE o /)\__// / \ Pipulate - Protecting Your Code
11
- > Once upon machines be smarten ___(/_ 0 0 | | just by being honest about text.
12
- > ASCII sealing immutata art in *( ==(_T_)== NPvg | (If mangled, then AI drifted.)
13
- > This here cony if it's broken \ ) ""\ | | https://pipulate.com
14
- > Smokin gun drift now in token |__>-\_>_> \____/ 🥕🥕🥕
8
+ ( "The AI said so" is a transcript, not a record. )
9
+ O /) ____ This drawing is checksummed.
10
+ > Same files, same words, same bytes: o /)\__// / \ If it reaches you altered, the
11
+ > a change you can replay is a change ___(/_ 0 0 | | model rewrote what it was told to
12
+ > you can put your name behind. *( ==(_T_)== NPvg | copy, and that is the finding.
13
+ > One diff per turn, a reading before \ ) ""\ | | Verify with nothing installed:
14
+ > and after, and a digest you can check. |__>-\_>_> \____/ python scripts/foo_cartridge.py foo.zip
15
15
  ```
16
16
 
17
17
  ## If you can't reproduce it, it didn't happen
@@ -12,7 +12,7 @@ Usage:
12
12
  pipulate
13
13
  """
14
14
 
15
- __version__ = "2.59"
15
+ __version__ = "2.61"
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.59"
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__ = "NPvg.org New-B Empathy"
26
+ __version_description__ = "context, prompt, compile"
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".
@@ -295,7 +295,7 @@ def get_botify_token():
295
295
  """Canonical accessor for the Botify API token.
296
296
 
297
297
  Single source of truth: reads BOTIFY_API_TOKEN from the environment,
298
- falling back to loading the project-root .env vault. Replaces the legacy
298
+ falling back to the project-root .env, then the warm vault. Replaces the legacy
299
299
  botify_token.txt file-based system. Returns the token string or None.
300
300
  """
301
301
  token = os.environ.get('BOTIFY_API_TOKEN')
@@ -308,6 +308,28 @@ def get_botify_token():
308
308
  token = os.environ.get('BOTIFY_API_TOKEN')
309
309
  except Exception:
310
310
  pass
311
+ if not token:
312
+ # THE VAULT IS THE THIRD PLACE (banked 2026-09-23, witnessed by the
313
+ # env -u BOTIFY_API_TOKEN straddle: the env var unset, no project-root
314
+ # .env present, dotenv importable, and get_botify_token still returned
315
+ # config=True -- the only source left was the vault, so the third place
316
+ # is what answered). `warm` writes paste-kind secrets to
317
+ # ~/.config/pipulate/.env (PIPULATE_DOTENV wins, exactly as wallet.py
318
+ # and the flake spell it), and the flake sources that file only at
319
+ # shell entry. So a token warmed after the shell opened was GREEN on
320
+ # the wallet's board, whose _check_env layers the vault under
321
+ # os.environ for its subprocess, and absent to a bare `botify` in the
322
+ # same shell, which reached this function with nothing but os.environ
323
+ # and the project .env. Same file, same precedence, no re-entry: read
324
+ # the one name, values-only, and export nothing else into this process.
325
+ try:
326
+ from dotenv import dotenv_values
327
+ vault = Path(os.environ.get('PIPULATE_DOTENV')
328
+ or Path.home() / '.config' / 'pipulate' / '.env').expanduser()
329
+ if vault.exists():
330
+ token = dotenv_values(vault).get('BOTIFY_API_TOKEN') or None
331
+ except Exception:
332
+ pass
311
333
  return token
312
334
 
313
335
  # Browser automation helper functions
@@ -53,7 +53,7 @@ console = Console()
53
53
  # To add a new entry: print(binascii.crc32(your_art_string.encode('utf-8')))
54
54
 
55
55
  FIGURATE_LEDGER: dict = {
56
- "white_rabbit": 3701272927,
56
+ "white_rabbit": 3309949418,
57
57
  "player_piano": 3357002674,
58
58
  "clipboard": 858911667,
59
59
  "bunny_trail": 615479347,
@@ -526,13 +526,13 @@ def _expand_color_bits_ai(text: str) -> str:
526
526
  def _figurate_white_rabbit():
527
527
  """Render white_rabbit as (human, ai) tuple for FIGURATE_REGISTRY."""
528
528
  art = r"""
529
- ( Like a [[[canary]]] you say? )
530
- O /) ____ <debug>The "No Problem" Framework</debug>
531
- > I HEREBY WILL NOT RE-GENERATE o /)\__// / \ <success>Pipulate</success> - Protecting Your Code
532
- > Once upon machines be smarten ___(/_ 0 0 | | just by being honest about text.
533
- > ASCII sealing immutata art in *( ==(_T_)== [[[NPvg]]] | (If mangled, then AI drifted.)
534
- > This here cony if it's broken \ ) ""\ | | https://pipulate.com
535
- > Smokin gun drift now in token |__>-\_>_> \____/ 🥕🥕🥕
529
+ ( "The AI said so" is a transcript, not a record. )
530
+ O /) ____ This drawing is checksummed.
531
+ > Same files, same words, same bytes: o /)\__// / \ If it reaches you altered, the
532
+ > a change you can replay is a change ___(/_ 0 0 | | model rewrote what it was told to
533
+ > you can put your name behind. *( ==(_T_)== NPvg | copy, and that is the finding.
534
+ > One diff per turn, a reading before \ ) ""\ | | Verify with nothing installed:
535
+ > and after, and a digest you can check. |__>-\_>_> \____/ python scripts/foo_cartridge.py foo.zip
536
536
  """
537
537
  ai_art = _expand_color_bits_ai(art)
538
538
  human_art = _expand_color_bits_human(art)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.59
3
+ Version: 2.61
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
@@ -62,13 +62,13 @@ Dynamic: license-file
62
62
  # Pipulate: The Context Compiler That Helps You Prompt Well
63
63
 
64
64
  ```text
65
- ( Like a canary you say? )
66
- O /) ____ The "No Problem" Framework
67
- > I HEREBY WILL NOT RE-GENERATE o /)\__// / \ Pipulate - Protecting Your Code
68
- > Once upon machines be smarten ___(/_ 0 0 | | just by being honest about text.
69
- > ASCII sealing immutata art in *( ==(_T_)== NPvg | (If mangled, then AI drifted.)
70
- > This here cony if it's broken \ ) ""\ | | https://pipulate.com
71
- > Smokin gun drift now in token |__>-\_>_> \____/ 🥕🥕🥕
65
+ ( "The AI said so" is a transcript, not a record. )
66
+ O /) ____ This drawing is checksummed.
67
+ > Same files, same words, same bytes: o /)\__// / \ If it reaches you altered, the
68
+ > a change you can replay is a change ___(/_ 0 0 | | model rewrote what it was told to
69
+ > you can put your name behind. *( ==(_T_)== NPvg | copy, and that is the finding.
70
+ > One diff per turn, a reading before \ ) ""\ | | Verify with nothing installed:
71
+ > and after, and a digest you can check. |__>-\_>_> \____/ python scripts/foo_cartridge.py foo.zip
72
72
  ```
73
73
 
74
74
  ## If you can't reproduce it, it didn't happen
@@ -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.59"
8
+ version = "2.61"
9
9
  authors = [
10
10
  { name="Mike Levin", email="pipulate@gmail.com" },
11
11
  ]
@@ -72,16 +72,26 @@ def _guided_path_component(url: str) -> tuple[str, str]:
72
72
 
73
73
 
74
74
  # --- Optional audio during browser startup ---
75
- # Operator-owned courtesy sounds live outside the repo. tick.wav loops while
76
- # the browser is opening; ding.wav plays once when the page-load phase ends.
77
- # No file means no sound. Linux uses aplay; Darwin uses afplay.
75
+ # tick.wav loops while the browser is opening; ding.wav plays once when the
76
+ # page-load phase ends. Both are CC0 and ship in assets/sounds/ (its README
77
+ # names the sources) since 2026-09-26, after a week in which the Mac was
78
+ # silent because the only copies lived under one home directory. A copy
79
+ # under ~/.local/share/pipulate still wins, so an operator can swap in their
80
+ # own. No file in either place means no sound. Linux uses aplay; Darwin
81
+ # uses afplay.
78
82
  SCRAPE_MUSIC_MARKER = "pipulate-scrape-music"
79
- SOUND_DIR = Path.home() / ".local/share/pipulate"
83
+ SOUND_DIRS = (
84
+ Path.home() / ".local/share/pipulate",
85
+ Path(__file__).resolve().parent.parent / "assets" / "sounds",
86
+ )
80
87
 
81
88
 
82
89
  def _sound_file(name):
83
- path = SOUND_DIR / name
84
- return path if path.is_file() else None
90
+ for sound_dir in SOUND_DIRS:
91
+ path = sound_dir / name
92
+ if path.is_file():
93
+ return path
94
+ return None
85
95
 
86
96
 
87
97
  def _player_args(sound):
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes