pipulate 2.2__tar.gz → 2.4__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 (87) hide show
  1. {pipulate-2.2/pipulate.egg-info → pipulate-2.4}/PKG-INFO +1 -1
  2. {pipulate-2.2 → pipulate-2.4}/__init__.py +2 -2
  3. {pipulate-2.2 → pipulate-2.4}/cli.py +12 -1
  4. {pipulate-2.2 → pipulate-2.4}/imports/voice_synthesis.py +61 -6
  5. {pipulate-2.2 → pipulate-2.4/pipulate.egg-info}/PKG-INFO +1 -1
  6. {pipulate-2.2 → pipulate-2.4}/pyproject.toml +1 -1
  7. {pipulate-2.2 → pipulate-2.4}/LICENSE +0 -0
  8. {pipulate-2.2 → pipulate-2.4}/MANIFEST.in +0 -0
  9. {pipulate-2.2 → pipulate-2.4}/README.md +0 -0
  10. {pipulate-2.2 → pipulate-2.4}/apps/010_introduction.py +0 -0
  11. {pipulate-2.2 → pipulate-2.4}/apps/015_config.py +0 -0
  12. {pipulate-2.2 → pipulate-2.4}/apps/020_profiles.py +0 -0
  13. {pipulate-2.2 → pipulate-2.4}/apps/025_aspect.py +0 -0
  14. {pipulate-2.2 → pipulate-2.4}/apps/030_roles.py +0 -0
  15. {pipulate-2.2 → pipulate-2.4}/apps/040_hello_workflow.py +0 -0
  16. {pipulate-2.2 → pipulate-2.4}/apps/050_documentation.py +0 -0
  17. {pipulate-2.2 → pipulate-2.4}/apps/060_tasks.py +0 -0
  18. {pipulate-2.2 → pipulate-2.4}/apps/070_history.py +0 -0
  19. {pipulate-2.2 → pipulate-2.4}/apps/110_parameter_buster.py +0 -0
  20. {pipulate-2.2 → pipulate-2.4}/apps/120_link_graph.py +0 -0
  21. {pipulate-2.2 → pipulate-2.4}/apps/130_gap_analysis.py +0 -0
  22. {pipulate-2.2 → pipulate-2.4}/apps/200_workflow_genesis.py +0 -0
  23. {pipulate-2.2 → pipulate-2.4}/apps/210_widget_examples.py +0 -0
  24. {pipulate-2.2 → pipulate-2.4}/apps/220_roadmap.py +0 -0
  25. {pipulate-2.2 → pipulate-2.4}/apps/230_dev_assistant.py +0 -0
  26. {pipulate-2.2 → pipulate-2.4}/apps/240_simon_mcp.py +0 -0
  27. {pipulate-2.2 → pipulate-2.4}/apps/300_blank_placeholder.py +0 -0
  28. {pipulate-2.2 → pipulate-2.4}/apps/400_botify_trifecta.py +0 -0
  29. {pipulate-2.2 → pipulate-2.4}/apps/440_browser_automation.py +0 -0
  30. {pipulate-2.2 → pipulate-2.4}/apps/450_stream_simulator.py +0 -0
  31. {pipulate-2.2 → pipulate-2.4}/apps/510_text_field.py +0 -0
  32. {pipulate-2.2 → pipulate-2.4}/apps/520_text_area.py +0 -0
  33. {pipulate-2.2 → pipulate-2.4}/apps/530_dropdown.py +0 -0
  34. {pipulate-2.2 → pipulate-2.4}/apps/540_checkboxes.py +0 -0
  35. {pipulate-2.2 → pipulate-2.4}/apps/550_radios.py +0 -0
  36. {pipulate-2.2 → pipulate-2.4}/apps/560_range.py +0 -0
  37. {pipulate-2.2 → pipulate-2.4}/apps/570_switch.py +0 -0
  38. {pipulate-2.2 → pipulate-2.4}/apps/580_upload.py +0 -0
  39. {pipulate-2.2 → pipulate-2.4}/apps/610_markdown.py +0 -0
  40. {pipulate-2.2 → pipulate-2.4}/apps/620_mermaid.py +0 -0
  41. {pipulate-2.2 → pipulate-2.4}/apps/630_prism.py +0 -0
  42. {pipulate-2.2 → pipulate-2.4}/apps/640_javascript.py +0 -0
  43. {pipulate-2.2 → pipulate-2.4}/apps/710_pandas.py +0 -0
  44. {pipulate-2.2 → pipulate-2.4}/apps/720_rich.py +0 -0
  45. {pipulate-2.2 → pipulate-2.4}/apps/730_matplotlib.py +0 -0
  46. {pipulate-2.2 → pipulate-2.4}/apps/810_webbrowser.py +0 -0
  47. {pipulate-2.2 → pipulate-2.4}/apps/820_selenium.py +0 -0
  48. {pipulate-2.2 → pipulate-2.4}/apps/830_pico_slider.py +0 -0
  49. {pipulate-2.2 → pipulate-2.4}/config.py +0 -0
  50. {pipulate-2.2 → pipulate-2.4}/imports/__init__.py +0 -0
  51. {pipulate-2.2 → pipulate-2.4}/imports/ai_dictdb.py +0 -0
  52. {pipulate-2.2 → pipulate-2.4}/imports/ai_tool_discovery_simple_parser.py +0 -0
  53. {pipulate-2.2 → pipulate-2.4}/imports/append_only_conversation.py +0 -0
  54. {pipulate-2.2 → pipulate-2.4}/imports/ascii_displays.py +0 -0
  55. {pipulate-2.2 → pipulate-2.4}/imports/botify/__init__.py +0 -0
  56. {pipulate-2.2 → pipulate-2.4}/imports/botify/code_generators.py +0 -0
  57. {pipulate-2.2 → pipulate-2.4}/imports/botify/true_schema_discoverer.py +0 -0
  58. {pipulate-2.2 → pipulate-2.4}/imports/botify_code_generation.py +0 -0
  59. {pipulate-2.2 → pipulate-2.4}/imports/crud.py +0 -0
  60. {pipulate-2.2 → pipulate-2.4}/imports/database_safety_wrapper.py +0 -0
  61. {pipulate-2.2 → pipulate-2.4}/imports/dom_processing/__init__.py +0 -0
  62. {pipulate-2.2 → pipulate-2.4}/imports/dom_processing/ai_dom_beautifier.py +0 -0
  63. {pipulate-2.2 → pipulate-2.4}/imports/dom_processing/enhanced_dom_processor.py +0 -0
  64. {pipulate-2.2 → pipulate-2.4}/imports/durable_backup_system.py +0 -0
  65. {pipulate-2.2 → pipulate-2.4}/imports/mcp_orchestrator.py +0 -0
  66. {pipulate-2.2 → pipulate-2.4}/imports/server_logging.py +0 -0
  67. {pipulate-2.2 → pipulate-2.4}/imports/stream_orchestrator.py +0 -0
  68. {pipulate-2.2 → pipulate-2.4}/pipulate/__init__.py +0 -0
  69. {pipulate-2.2 → pipulate-2.4}/pipulate/core.py +0 -0
  70. {pipulate-2.2 → pipulate-2.4}/pipulate.egg-info/SOURCES.txt +0 -0
  71. {pipulate-2.2 → pipulate-2.4}/pipulate.egg-info/dependency_links.txt +0 -0
  72. {pipulate-2.2 → pipulate-2.4}/pipulate.egg-info/entry_points.txt +0 -0
  73. {pipulate-2.2 → pipulate-2.4}/pipulate.egg-info/requires.txt +0 -0
  74. {pipulate-2.2 → pipulate-2.4}/pipulate.egg-info/top_level.txt +0 -0
  75. {pipulate-2.2 → pipulate-2.4}/server.py +0 -0
  76. {pipulate-2.2 → pipulate-2.4}/setup.cfg +0 -0
  77. {pipulate-2.2 → pipulate-2.4}/tests/test_mck_rep2.py +0 -0
  78. {pipulate-2.2 → pipulate-2.4}/tools/__init__.py +0 -0
  79. {pipulate-2.2 → pipulate-2.4}/tools/advanced_automation_tools.py +0 -0
  80. {pipulate-2.2 → pipulate-2.4}/tools/botify_tools.py +0 -0
  81. {pipulate-2.2 → pipulate-2.4}/tools/conversation_tools.py +0 -0
  82. {pipulate-2.2 → pipulate-2.4}/tools/dom_tools.py +0 -0
  83. {pipulate-2.2 → pipulate-2.4}/tools/keychain_tools.py +0 -0
  84. {pipulate-2.2 → pipulate-2.4}/tools/llm_optics.py +0 -0
  85. {pipulate-2.2 → pipulate-2.4}/tools/mcp_tools.py +0 -0
  86. {pipulate-2.2 → pipulate-2.4}/tools/scraper_tools.py +0 -0
  87. {pipulate-2.2 → pipulate-2.4}/tools/system_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.2
3
+ Version: 2.4
4
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
6
  License-Expression: AGPL-3.0-or-later
@@ -12,8 +12,8 @@ Usage:
12
12
  pipulate
13
13
  """
14
14
 
15
- __version__ = "2.02"
16
- __version_description__ = "Operation Stick Bug"
15
+ __version__ = "2.04"
16
+ __version_description__ = "So'wI' chu'"
17
17
  # SPDX expression, single source of truth, synced into pyproject.toml by
18
18
  # scripts/release/version_sync.py. "-or-later" (not bare AGPL-3.0, which is
19
19
  # deprecated SPDX) because the header below grants "any later version".
@@ -478,7 +478,18 @@ def main():
478
478
  sys.exit(1)
479
479
  except Exception as e:
480
480
  console.print(f"❌ [bold red]Unexpected error:[/bold red] {e}")
481
- sys.exit(1)
481
+ sys.exit(1)
482
+ # THE DISCRIMINATION QUESTION, APPLIED TO AN EXIT CODE (convicted
483
+ # 2026-08-03): this line sat at THIS indentation, outside every
484
+ # handler, so success fell through to it -- and so did failure, and
485
+ # interrupt, and exception. Four worlds, one printout. The console
486
+ # printed a green checkmark while the shell read 1, which means the
487
+ # EXIT-CODE PROTOCOL RULE's whole premise ("a program invoked for a
488
+ # decision speaks through its exit code") was unavailable here, and
489
+ # any `cli.py call X && echo GREEN` could never print GREEN in any
490
+ # world. Sibling of THE SUCCESS-ONLY WITNESS: an instrument that
491
+ # cannot report the outcome is the thing hiding it. Success exits 0.
492
+ sys.exit(0)
482
493
 
483
494
  if __name__ == "__main__":
484
495
  main()
@@ -47,6 +47,7 @@ class ChipVoiceSystem:
47
47
  self.voice = None
48
48
  self.voice_ready = False
49
49
  self.current_process = None # Track the running audio process
50
+ self.last_error = None # Populated by synthesize_and_play on failure
50
51
 
51
52
  if VOICE_SYNTHESIS_AVAILABLE:
52
53
  self.setup_voice_model()
@@ -173,7 +174,19 @@ class ChipVoiceSystem:
173
174
  # rare audible overlap over a permanent silent deadlock if a player hangs.
174
175
  import fcntl
175
176
  import time
176
- lock_file = open("/tmp/pipulate_voice.lock", "w")
177
+ # PER-USER LOCK PATH (convicted 2026-08-02, cold-start ride). This
178
+ # was a FIXED path in a shared world-readable directory, so the
179
+ # first user to speak created it 0644 and every OTHER user on the
180
+ # machine got EACCES on open(..., "w") forever. Receipt:
181
+ # `-rw-r--r-- 1 mike users 0 /tmp/pipulate_voice.lock` while a
182
+ # second account's ride printed Errno 13 at every single stop.
183
+ # A mode change is the WRONG fix: 0666 in /tmp is a shared-write
184
+ # target for anyone on the box, and the lock has no cross-user job
185
+ # anyway -- two users are two audio sessions. The uid suffix makes
186
+ # collision unrepresentable instead of merely permitted, which is
187
+ # THE DERIVED-PATH RULE applied to a lock file.
188
+ lock_path = f"/tmp/pipulate_voice.lock.{os.getuid()}"
189
+ lock_file = open(lock_path, "w")
177
190
  _deadline = time.monotonic() + 30
178
191
  while True:
179
192
  try:
@@ -209,15 +222,34 @@ class ChipVoiceSystem:
209
222
  # Use Popen instead of run to allow interruption
210
223
  self.current_process = subprocess.Popen(
211
224
  play_cmd,
212
- stderr=subprocess.DEVNULL,
225
+ stderr=subprocess.PIPE,
213
226
  stdout=subprocess.DEVNULL
214
227
  )
215
228
 
216
229
  # Keep logging the original 'text' so the terminal output matches the Markdown
217
230
  logger.info(f"🎤 Speaking (PID {self.current_process.pid}): {text[:50]}...")
218
231
 
219
- # Wait for the process to finish naturally
220
- self.current_process.wait()
232
+ # THE SUCCESS-ONLY WITNESS (convicted 2026-08-02, cold-start ride
233
+ # five): this called wait() and DISCARDED the exit code while
234
+ # stderr went to DEVNULL, so a player that could not open an
235
+ # audio device exited nonzero, printed its reason into the void,
236
+ # and this function returned True anyway. speak_text reported
237
+ # success, mother_cat printed nothing, and the human heard
238
+ # silence beside a green console. Mirror image of
239
+ # REFUSAL-ONLY WITNESS: that rule names a guard observed only
240
+ # refusing; this is a claim observed only succeeding, and the
241
+ # two are indistinguishable from broken-shut and cannot-fail
242
+ # respectively. Also the missing third suspect from the
243
+ # 2026-07-26 THE DEMO WENT SILENT todo, which named transport
244
+ # and engine and never looked at the subprocess exit status.
245
+ _stderr = self.current_process.communicate()[1]
246
+ rc = self.current_process.returncode
247
+ if rc != 0:
248
+ detail = (_stderr or b"").decode("utf-8", "replace").strip()
249
+ detail = detail.splitlines()[-1] if detail else f"exit {rc}"
250
+ logger.error(f"🎤 Audio playback failed (exit {rc}): {detail}")
251
+ self.last_error = f"playback exit {rc}: {detail}"
252
+ return False
221
253
 
222
254
  return True
223
255
 
@@ -231,7 +263,16 @@ class ChipVoiceSystem:
231
263
  stdout=subprocess.DEVNULL
232
264
  )
233
265
  logger.info(f"🎤 Speaking via nix-shell (PID {self.current_process.pid}): {text[:50]}...")
234
- self.current_process.wait()
266
+ rc = self.current_process.wait()
267
+ if rc != 0:
268
+ # Same false green as the primary path. stderr stays
269
+ # DEVNULL here on purpose: this branch fires only when
270
+ # `play` is missing entirely, so the exit code alone is
271
+ # enough to stop it lying, and the DEVNULL line carries
272
+ # trailing whitespace that the patch transport mangles.
273
+ logger.error(f"🎤 Audio playback failed via nix-shell (exit {rc}).")
274
+ self.last_error = f"nix-shell playback exit {rc}"
275
+ return False
235
276
  return True
236
277
  except Exception as nix_e:
237
278
  logger.error(f"🎤 Audio playback failed on {system} (nix fallback): {nix_e}")
@@ -241,7 +282,14 @@ class ChipVoiceSystem:
241
282
  return False
242
283
 
243
284
  except Exception as e:
285
+ # CARRY THE MESSAGE, NOT JUST THE LOG (convicted 2026-08-02): this
286
+ # branch returned a bare False, so speak_text's result dict had no
287
+ # 'error' key and mother_cat's .get("error", "unknown error") fell
288
+ # to the default -- printing "unknown error" one line BELOW a log
289
+ # line naming the exact errno and path. LAST-INCH: everything
290
+ # upstream knew; the surface the human reads did not.
244
291
  logger.error(f"🎤 Voice synthesis failed: {e}")
292
+ self.last_error = f"{type(e).__name__}: {e}"
245
293
  return False
246
294
  finally:
247
295
  # Clean up temporary file
@@ -277,15 +325,22 @@ class ChipVoiceSystem:
277
325
  "details": "Piper TTS model not loaded"
278
326
  }
279
327
 
328
+ self.last_error = None
280
329
  success = self.synthesize_and_play(text)
281
330
 
282
- return {
331
+ result = {
283
332
  "success": success,
284
333
  "text": text,
285
334
  "text_length": len(text),
286
335
  "voice_model": "en_US-amy-low",
287
336
  "message": "Speech synthesis completed" if success else "Speech synthesis failed"
288
337
  }
338
+ # The failure branch MUST carry an 'error' key, because every caller
339
+ # reads .get("error", ...) and a missing key silently becomes a lie
340
+ # about how much was known.
341
+ if not success:
342
+ result["error"] = self.last_error or "synthesis or playback failed"
343
+ return result
289
344
 
290
345
  class MemoryToVoiceConverter:
291
346
  """
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.2
3
+ Version: 2.4
4
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
6
  License-Expression: AGPL-3.0-or-later
@@ -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.02"
8
+ version = "2.04"
9
9
  authors = [
10
10
  { name="Mike Levin", email="pipulate@gmail.com" },
11
11
  ]
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
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes