pipulate 2.64__tar.gz → 2.65__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.64/pipulate.egg-info → pipulate-2.65}/PKG-INFO +1 -1
  2. {pipulate-2.64 → pipulate-2.65}/__init__.py +2 -2
  3. {pipulate-2.64 → pipulate-2.65}/apps/030_roles.py +3 -3
  4. {pipulate-2.64 → pipulate-2.65}/imports/ascii_displays.py +97 -17
  5. {pipulate-2.64 → pipulate-2.65}/pipulate/__init__.py +3 -3
  6. {pipulate-2.64 → pipulate-2.65}/pipulate/core.py +5 -5
  7. {pipulate-2.64 → pipulate-2.65/pipulate.egg-info}/PKG-INFO +1 -1
  8. {pipulate-2.64 → pipulate-2.65}/pyproject.toml +1 -1
  9. {pipulate-2.64 → pipulate-2.65}/server.py +2 -2
  10. {pipulate-2.64 → pipulate-2.65}/LICENSE +0 -0
  11. {pipulate-2.64 → pipulate-2.65}/MANIFEST.in +0 -0
  12. {pipulate-2.64 → pipulate-2.65}/README.md +0 -0
  13. {pipulate-2.64 → pipulate-2.65}/apps/010_introduction.py +0 -0
  14. {pipulate-2.64 → pipulate-2.65}/apps/015_config.py +0 -0
  15. {pipulate-2.64 → pipulate-2.65}/apps/020_profiles.py +0 -0
  16. {pipulate-2.64 → pipulate-2.65}/apps/025_aspect.py +0 -0
  17. {pipulate-2.64 → pipulate-2.65}/apps/040_hello_workflow.py +0 -0
  18. {pipulate-2.64 → pipulate-2.65}/apps/050_documentation.py +0 -0
  19. {pipulate-2.64 → pipulate-2.65}/apps/060_tasks.py +0 -0
  20. {pipulate-2.64 → pipulate-2.65}/apps/070_history.py +0 -0
  21. {pipulate-2.64 → pipulate-2.65}/apps/110_parameter_buster.py +0 -0
  22. {pipulate-2.64 → pipulate-2.65}/apps/120_link_graph.py +0 -0
  23. {pipulate-2.64 → pipulate-2.65}/apps/130_gap_analysis.py +0 -0
  24. {pipulate-2.64 → pipulate-2.65}/apps/200_workflow_genesis.py +0 -0
  25. {pipulate-2.64 → pipulate-2.65}/apps/210_widget_examples.py +0 -0
  26. {pipulate-2.64 → pipulate-2.65}/apps/220_roadmap.py +0 -0
  27. {pipulate-2.64 → pipulate-2.65}/apps/230_dev_assistant.py +0 -0
  28. {pipulate-2.64 → pipulate-2.65}/apps/240_simon_mcp.py +0 -0
  29. {pipulate-2.64 → pipulate-2.65}/apps/300_blank_placeholder.py +0 -0
  30. {pipulate-2.64 → pipulate-2.65}/apps/400_botify_trifecta.py +0 -0
  31. {pipulate-2.64 → pipulate-2.65}/apps/440_browser_automation.py +0 -0
  32. {pipulate-2.64 → pipulate-2.65}/apps/450_stream_simulator.py +0 -0
  33. {pipulate-2.64 → pipulate-2.65}/apps/510_text_field.py +0 -0
  34. {pipulate-2.64 → pipulate-2.65}/apps/520_text_area.py +0 -0
  35. {pipulate-2.64 → pipulate-2.65}/apps/530_dropdown.py +0 -0
  36. {pipulate-2.64 → pipulate-2.65}/apps/540_checkboxes.py +0 -0
  37. {pipulate-2.64 → pipulate-2.65}/apps/550_radios.py +0 -0
  38. {pipulate-2.64 → pipulate-2.65}/apps/560_range.py +0 -0
  39. {pipulate-2.64 → pipulate-2.65}/apps/570_switch.py +0 -0
  40. {pipulate-2.64 → pipulate-2.65}/apps/580_upload.py +0 -0
  41. {pipulate-2.64 → pipulate-2.65}/apps/610_markdown.py +0 -0
  42. {pipulate-2.64 → pipulate-2.65}/apps/620_mermaid.py +0 -0
  43. {pipulate-2.64 → pipulate-2.65}/apps/630_prism.py +0 -0
  44. {pipulate-2.64 → pipulate-2.65}/apps/640_javascript.py +0 -0
  45. {pipulate-2.64 → pipulate-2.65}/apps/710_pandas.py +0 -0
  46. {pipulate-2.64 → pipulate-2.65}/apps/720_rich.py +0 -0
  47. {pipulate-2.64 → pipulate-2.65}/apps/730_matplotlib.py +0 -0
  48. {pipulate-2.64 → pipulate-2.65}/apps/810_webbrowser.py +0 -0
  49. {pipulate-2.64 → pipulate-2.65}/apps/820_selenium.py +0 -0
  50. {pipulate-2.64 → pipulate-2.65}/apps/830_pico_slider.py +0 -0
  51. {pipulate-2.64 → pipulate-2.65}/cli.py +0 -0
  52. {pipulate-2.64 → pipulate-2.65}/config.py +0 -0
  53. {pipulate-2.64 → pipulate-2.65}/imports/__init__.py +0 -0
  54. {pipulate-2.64 → pipulate-2.65}/imports/ai_dictdb.py +0 -0
  55. {pipulate-2.64 → pipulate-2.65}/imports/ai_tool_discovery_simple_parser.py +0 -0
  56. {pipulate-2.64 → pipulate-2.65}/imports/append_only_conversation.py +0 -0
  57. {pipulate-2.64 → pipulate-2.65}/imports/botify/__init__.py +0 -0
  58. {pipulate-2.64 → pipulate-2.65}/imports/botify/code_generators.py +0 -0
  59. {pipulate-2.64 → pipulate-2.65}/imports/botify/true_schema_discoverer.py +0 -0
  60. {pipulate-2.64 → pipulate-2.65}/imports/botify_code_generation.py +0 -0
  61. {pipulate-2.64 → pipulate-2.65}/imports/crud.py +0 -0
  62. {pipulate-2.64 → pipulate-2.65}/imports/database_safety_wrapper.py +0 -0
  63. {pipulate-2.64 → pipulate-2.65}/imports/dom_processing/__init__.py +0 -0
  64. {pipulate-2.64 → pipulate-2.65}/imports/dom_processing/ai_dom_beautifier.py +0 -0
  65. {pipulate-2.64 → pipulate-2.65}/imports/dom_processing/enhanced_dom_processor.py +0 -0
  66. {pipulate-2.64 → pipulate-2.65}/imports/durable_backup_system.py +0 -0
  67. {pipulate-2.64 → pipulate-2.65}/imports/mcp_orchestrator.py +0 -0
  68. {pipulate-2.64 → pipulate-2.65}/imports/server_logging.py +0 -0
  69. {pipulate-2.64 → pipulate-2.65}/imports/stream_orchestrator.py +0 -0
  70. {pipulate-2.64 → pipulate-2.65}/imports/voice_synthesis.py +0 -0
  71. {pipulate-2.64 → pipulate-2.65}/pipulate.egg-info/SOURCES.txt +0 -0
  72. {pipulate-2.64 → pipulate-2.65}/pipulate.egg-info/dependency_links.txt +0 -0
  73. {pipulate-2.64 → pipulate-2.65}/pipulate.egg-info/entry_points.txt +0 -0
  74. {pipulate-2.64 → pipulate-2.65}/pipulate.egg-info/requires.txt +0 -0
  75. {pipulate-2.64 → pipulate-2.65}/pipulate.egg-info/top_level.txt +0 -0
  76. {pipulate-2.64 → pipulate-2.65}/setup.cfg +0 -0
  77. {pipulate-2.64 → pipulate-2.65}/tests/test_apply_airlock.py +0 -0
  78. {pipulate-2.64 → pipulate-2.65}/tests/test_mck_rep2.py +0 -0
  79. {pipulate-2.64 → pipulate-2.65}/tools/__init__.py +0 -0
  80. {pipulate-2.64 → pipulate-2.65}/tools/advanced_automation_tools.py +0 -0
  81. {pipulate-2.64 → pipulate-2.65}/tools/botify_tools.py +0 -0
  82. {pipulate-2.64 → pipulate-2.65}/tools/connector_tools.py +0 -0
  83. {pipulate-2.64 → pipulate-2.65}/tools/conversation_tools.py +0 -0
  84. {pipulate-2.64 → pipulate-2.65}/tools/dom_tools.py +0 -0
  85. {pipulate-2.64 → pipulate-2.65}/tools/keychain_tools.py +0 -0
  86. {pipulate-2.64 → pipulate-2.65}/tools/llm_optics.py +0 -0
  87. {pipulate-2.64 → pipulate-2.65}/tools/mcp_tools.py +0 -0
  88. {pipulate-2.64 → pipulate-2.65}/tools/scraper_tools.py +0 -0
  89. {pipulate-2.64 → pipulate-2.65}/tools/system_tools.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.64
3
+ Version: 2.65
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.64"
15
+ __version__ = "2.65"
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.64"
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__ = "Notebooks to Workshop"
26
+ __version_description__ = "About on the Menu"
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".
@@ -44,15 +44,15 @@ class PluginIdentityManager:
44
44
 
45
45
  @property
46
46
  def TRAINING_PROMPT(self):
47
- """Load the roles skill from Workshop/.agents/skills/roles/SKILL.md (graceful fallback)."""
48
- skill_path = os.path.join(os.path.dirname(__file__), '..', 'Workshop', '.agents', 'skills', 'roles', 'SKILL.md')
47
+ """Load the roles skill from .agents/skills/roles/SKILL.md at the repo root (graceful fallback)."""
48
+ skill_path = os.path.join(os.path.dirname(__file__), '..', '.agents', 'skills', 'roles', 'SKILL.md')
49
49
  try:
50
50
  with open(skill_path, 'r', encoding='utf-8') as f:
51
51
  return f.read()
52
52
  except Exception:
53
53
  return (
54
54
  '# Pipulate Roles System\n\n'
55
- 'SKILL.md not found at Workshop/.agents/skills/roles/SKILL.md. '
55
+ 'SKILL.md not found at .agents/skills/roles/SKILL.md. '
56
56
  'The Roles app is the homepage and APP-menu control center: checkboxes toggle roles, '
57
57
  'drag reorders the APP menu, Core is always on. Secret word: ORCHESTRATOR.'
58
58
  )
@@ -124,6 +124,88 @@ def figurate(name: str, context: Optional[str] = None) -> FigurateResult:
124
124
  return FigurateResult(name=name, human=human_out, ai=ai_out, drift=drift)
125
125
 
126
126
 
127
+ def seal_readings():
128
+ """One (name, sealed, computed) per registered art: the FIGURATE_LEDGER
129
+ value (None when the art has no seal) beside the CRC32 of the ai string
130
+ rendered right now, computed exactly as figurate() computes drift."""
131
+ rows = []
132
+ for name, entry in FIGURATE_REGISTRY.items():
133
+ render_fn = entry.get("render")
134
+ if render_fn is None:
135
+ continue
136
+ _, ai_out = render_fn()
137
+ rows.append((name, FIGURATE_LEDGER.get(name), binascii.crc32(ai_out.encode('utf-8'))))
138
+ return rows
139
+
140
+
141
+ def reseal_ledger(write=False):
142
+ """THE RESEAL (2026-09-28, the operator's ruling: pragmatism over paranoia
143
+ while the art is redrawn). Rewrite the drifted FIGURATE_LEDGER lines of THIS
144
+ file in place, each line's trailing comment kept, and give an unsealed art a
145
+ line above the EXTRUDE_BOTTOM marker. write=False prints the same readings
146
+ and touches nothing. Returns (changed, missing). The seal still detects:
147
+ figurate() and --check read drift exactly as before, and only a hand typing
148
+ --reseal moves a number. THE THREE-REGION ART EDIT keeps its three regions;
149
+ its ordering becomes author, --reseal, figurate reads Drift: 0.
150
+ """
151
+ import re
152
+ from pathlib import Path
153
+ path = Path(__file__).resolve()
154
+ text = path.read_text(encoding='utf-8')
155
+ head = "FIGURATE_LEDGER: dict = {"
156
+ start = text.index(head) + len(head)
157
+ stop = text.index("# === FIGURATE_LEDGER_EXTRUDE_BOTTOM ===", start)
158
+ line_re = re.compile(r'^(?P<head>\s*"(?P<name>[^"]+)":\s*)(?P<crc>\d+)(?P<tail>,.*)$')
159
+ computed = {name: crc for name, _, crc in seal_readings()}
160
+ lines = text[start:stop].split('\n')
161
+ changed, seen = [], set()
162
+ for i, line in enumerate(lines):
163
+ m = line_re.match(line)
164
+ if not m or m.group('name') not in computed:
165
+ continue
166
+ name = m.group('name')
167
+ seen.add(name)
168
+ old, new = int(m.group('crc')), computed[name]
169
+ if old != new:
170
+ changed.append((name, old, new))
171
+ lines[i] = f"{m.group('head')}{new}{m.group('tail')}"
172
+ missing = [name for name in computed if name not in seen]
173
+ for name in missing:
174
+ lines.insert(len(lines) - 1, f' "{name}": {computed[name]}, # sealed by --reseal')
175
+ verb = "RESEALED" if write else "DRIFT"
176
+ for name, old, new in changed:
177
+ print(f"{name:22s} {old:>11d} -> {new:<11d} {verb}")
178
+ for name in missing:
179
+ print(f"{name:22s} {'(none)':>11s} -> {computed[name]:<11d} {'SEALED' if write else 'UNSEALED'}")
180
+ if write and (changed or missing):
181
+ path.write_text(text[:start] + '\n'.join(lines) + text[stop:], encoding='utf-8')
182
+ return changed, missing
183
+
184
+
185
+ def _main(argv=None):
186
+ """python imports/ascii_displays.py --check | --reseal
187
+ --check prints every art's seal beside its live CRC32 and exits 1 on any
188
+ drift or unsealed art; --reseal prints the same table, then rewrites the
189
+ ledger and exits 0. Plain print on purpose: the readings land in a `!`
190
+ receipt and on Honeybot, where this file is rsynced alone."""
191
+ import sys
192
+ argv = sys.argv[1:] if argv is None else argv
193
+ if argv not in (["--check"], ["--reseal"]):
194
+ print("usage: python imports/ascii_displays.py --check | --reseal", file=sys.stderr)
195
+ return 2
196
+ rows = seal_readings()
197
+ bad = [name for name, sealed, computed in rows if sealed != computed]
198
+ for name, sealed, computed in rows:
199
+ state = "ok" if sealed == computed else ("UNSEALED" if sealed is None else "DRIFT")
200
+ print(f"{name:22s} sealed={str(sealed if sealed is not None else '-'):<11} computed={computed:<11d} {state}")
201
+ print(f"{len(rows)} art(s), {len(bad)} to reseal")
202
+ if argv == ["--reseal"] and bad:
203
+ changed, missing = reseal_ledger(write=True)
204
+ print(f"resealed {len(changed)} line(s), sealed {len(missing)} new line(s); figurate reads Drift: 0 from here")
205
+ return 0
206
+ return 1 if bad else 0
207
+
208
+
127
209
  def _x11_screen_geometry(env):
128
210
  """Return (width, height) in pixels for the active X display, or None.
129
211
 
@@ -791,7 +873,7 @@ def _figurate_mechanical_man():
791
873
 
792
874
 
793
875
  def _figurate_workspace_tree():
794
- """Render the Workshop as it ACTUALLY sits on disk: flat.
876
+ """Render the Workshop as it sits on disk: three tiers and nothing else.
795
877
  THE NESTED VERSION WAS THE ONLY AUTHORITY THAT SHIPPED, AND IT WAS WRONG.
796
878
  update_agents_md_in_place() splices this art into AGENTS.md on every
797
879
  compile, so it is what every external agent tool reads -- while flake.nix
@@ -799,9 +881,11 @@ def _figurate_workspace_tree():
799
881
  and no Corporate/, Personal/, or Workshop/ at all. Three authorities, one
800
882
  winner, and the winner disagreed with the disk. Fixed at the GENERATOR so
801
883
  the output cannot drift back.
802
- Three tiers, zero nesting: canon arrives by the flake's copy-if-absent
803
- loop, personal is gitignored, and Shared/ is the single deliberate
804
- outbound surface with one folder per person. Authored with no .strip() so
884
+ Three tiers and nothing else (2026-09-28): corporate/ is the org's,
885
+ personal/ is the person's (the starter notebooks land in
886
+ personal/Notebooks/), shared/ is the single deliberate outbound surface
887
+ with one folder per person; flake.nix's THE THREE TIERS block rules and
888
+ this drawing follows it. Authored with no .strip() so
805
889
  the leading newline is preserved (the honeybot_pipeline convention), which
806
890
  the FIGURATE_LEDGER seal must match. No color-bit or angle-tag markers, so
807
891
  _expand_color_bits_ai leaves it untouched and ai_art == art.
@@ -810,23 +894,15 @@ def _figurate_workspace_tree():
810
894
  Workshop/ — the JupyterLab root (NOT Pipulate's own root)
811
895
  │ FLAT siblings. Nothing nests. Nothing to get wrong.
812
896
  │
813
- ├── Advanced_Notebooks/ canon · flake-delivered, copy-if-absent
814
- ├── Educational_Notebooks/ canon · your edits survive, updates do not arrive
815
- ├── imports/ canon · the code-behind "sauce" modules
816
- │
817
- ├── personal/ personal · gitignored · your own git repo goes here
818
- ├── Client_Work/ personal · gitignored · never leaves this machine
819
- ├── Deliverables/ personal · gitignored
820
- │
821
897
  ├── corporate/ the org's canon · gitignored · its own private repo
822
- │
823
- └── Shared/ the ONE folder for handing work to a teammate
898
+ ├── personal/ personal · gitignored · your own git repo goes here
899
+ └── shared/ the ONE folder for handing work to a teammate
824
900
  ├── alice/ one folder per person; you write ONLY your own
825
901
  └── bob/ single-writer partitions = zero merge conflicts
826
902
  """
827
903
  ai_art = _expand_color_bits_ai(art)
828
904
  human_art = _expand_color_bits_human(art)
829
- human = Panel(human_art, title="🗂️ Workshop — canon · personal · corporate · Shared", border_style="white")
905
+ human = Panel(human_art, title="🗂️ Workshop — corporate · personal · shared", border_style="white")
830
906
  return human, ai_art
831
907
 
832
908
 
@@ -1539,6 +1615,10 @@ Rich console blocked on Mac (Error: {e}), using fallback display.
1539
1615
  return fallback_summary.strip()
1540
1616
 
1541
1617
 
1618
+ if __name__ == "__main__":
1619
+ raise SystemExit(_main())
1620
+
1621
+
1542
1622
  # _ ____ ____ ___ ___ _ ____ _____ ____ _ _
1543
1623
  # / \ / ___| / ___|_ _|_ _| / \ | _ \_ _| | _ \| | __ _ _ _ __ _ _ __ ___ _ _ _ __ __| |
1544
1624
  # / _ \ \___ \| | | | | | / _ \ | |_) || | | |_) | |/ _` | | | |/ _` | '__/ _ \| | | | '_ \ / _` |
@@ -1862,7 +1942,7 @@ Rich console blocked on Mac (Error: {e}), using fallback display.
1862
1942
  # └── assets/ # optional templates and files
1863
1943
  # ```
1864
1944
  #
1865
- # You already have this. `Workshop/.agents/skills/hello_workflow/SKILL.md`, `gsc_readonly`, `roles` — it's in your manifest.
1945
+ # You already have this. `.agents/skills/hello_workflow/SKILL.md`, `gsc_readonly`, `roles` — it's in your manifest.
1866
1946
  #
1867
1947
  # **3. OKF** — what the v0.1 specification actually fixes is a folder layout, markdown files, YAML frontmatter, reserved filenames, and a single required field: `type`. A bundle is a directory of markdown files, each carrying a short YAML block — type, title, description — linking to its neighbors; add an index.md that lists the files so an agent can see what's there before opening everything, and that's the format. The spec fits on a single page.
1868
1948
  #
@@ -1888,7 +1968,7 @@ Rich console blocked on Mac (Error: {e}), using fallback display.
1888
1968
  # ├── cli.py # = the `allowed-tools` surface: mcp-discover / call
1889
1969
  # ├── apply.py # = "PR instructions" made executable (SEARCH/REPLACE actuator)
1890
1970
  # ├── flake.nix # = "Dev environment setup" made executable (nix develop)
1891
- # ├── Workshop/.agents/skills/ # = Agent Skills, already standard-shaped
1971
+ # ├── .agents/skills/ # = Agent Skills, already standard-shaped
1892
1972
  # │ ├── hello_workflow/SKILL.md
1893
1973
  # │ ├── gsc_readonly/SKILL.md
1894
1974
  # │ └── roles/SKILL.md
@@ -28,13 +28,13 @@ project_root = find_project_root(os.getcwd()) or Path.cwd()
28
28
 
29
29
  # ... after project_root is defined ...
30
30
  if project_root:
31
- notebooks_path = str(project_root / "Workshop")
31
+ notebooks_path = str(project_root / "Workshop" / "personal" / "Notebooks")
32
32
  if notebooks_path not in sys.path:
33
33
  sys.path.append(notebooks_path)
34
34
 
35
35
  # 🪄 THE FUSION DANCE: Merge core imports with user imports
36
36
  import imports
37
- notebook_imports_path = project_root / "Workshop" / "imports"
37
+ notebook_imports_path = project_root / "Workshop" / "personal" / "Notebooks" / "imports"
38
38
  if notebook_imports_path.exists() and str(notebook_imports_path) not in imports.__path__:
39
39
  imports.__path__.append(str(notebook_imports_path))
40
40
 
@@ -44,7 +44,7 @@ logger.remove()
44
44
  logger.add(sys.stderr, level="WARNING", colorize=True, format="<level>{level: <8}</level> | <cyan>{name}:{function}:{line}</cyan> - <level>{message}</level>")
45
45
 
46
46
  # 2. Instantiate the wand so we can use its Topological Manifold for paths
47
- DB_PATH = project_root / "Workshop" / "data" / "pipeline.sqlite"
47
+ DB_PATH = project_root / "Workshop" / "personal" / "Notebooks" / "data" / "pipeline.sqlite"
48
48
  DB_PATH.parent.mkdir(parents=True, exist_ok=True)
49
49
  wand = Pipulate(db_path=str(DB_PATH))
50
50
 
@@ -225,7 +225,7 @@ class Pipulate:
225
225
  class WorkspaceManifold:
226
226
  def __init__(self, root: Path, is_notebook: bool):
227
227
  self.root = root
228
- self.base = root / "Workshop" if is_notebook else root
228
+ self.base = root / "Workshop" / "personal" / "Notebooks" if is_notebook else root
229
229
  self.data = self.base / "data"
230
230
  self.logs = self.data / "logs"
231
231
  self.temp = self.data / "temp"
@@ -2536,7 +2536,7 @@ class Pipulate:
2536
2536
 
2537
2537
  # --- Notebook Sync Logic ---
2538
2538
  print(f"🔄 Syncing notebook '{notebook_filename}'...")
2539
- notebook_source_path = project_root / "Workshop" / notebook_filename
2539
+ notebook_source_path = project_root / "Workshop" / "personal" / "Notebooks" / notebook_filename
2540
2540
  notebook_dest_path = project_root / "assets" / "nbs" / notebook_filename
2541
2541
 
2542
2542
  if not notebook_source_path.exists():
@@ -2622,7 +2622,7 @@ class Pipulate:
2622
2622
  if isinstance(modules, str): modules = (modules,)
2623
2623
  for module_name in modules:
2624
2624
  module_filename = f"{module_name}.py"
2625
- module_source_path = project_root / "Workshop" / "imports" / module_filename # Look inside imports/
2625
+ module_source_path = project_root / "Workshop" / "personal" / "Notebooks" / "imports" / module_filename # Look inside imports/
2626
2626
  module_dest_path = project_root / "assets" / "nbs" / "imports" / module_filename # Sync back to imports/
2627
2627
  if module_source_path.exists():
2628
2628
  try:
@@ -3224,8 +3224,8 @@ class Pipulate:
3224
3224
  import json
3225
3225
 
3226
3226
  # 1. Explicitly define the path to the Jupyter chrysalis (ignoring current context)
3227
- jupyter_db_path = self.paths.root / "Workshop" / "data" / "pipeline.sqlite"
3228
- sentinel_path = self.paths.root / "Workshop" / "data" / ".onboarded"
3227
+ jupyter_db_path = self.paths.root / "Workshop" / "personal" / "Notebooks" / "data" / "pipeline.sqlite"
3228
+ sentinel_path = self.paths.root / "Workshop" / "personal" / "Notebooks" / "data" / ".onboarded"
3229
3229
 
3230
3230
  # 2. Safety Check: Only proceed if the chrysalis exists
3231
3231
  if not jupyter_db_path.exists():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pipulate
3
- Version: 2.64
3
+ Version: 2.65
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
@@ -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.64"
8
+ version = "2.65"
9
9
  authors = [
10
10
  { name="Mike Levin", email="pipulate@gmail.com" },
11
11
  ]
@@ -2095,7 +2095,7 @@ async def home(request):
2095
2095
  logger.debug(f'Selected explore item: {menux}')
2096
2096
 
2097
2097
  # ---> THE NEW AIRLOCK TRIGGER <---
2098
- sentinel_path = Path("Workshop/data/.onboarded")
2098
+ sentinel_path = Path("Workshop/personal/Notebooks/data/.onboarded")
2099
2099
  # Add the database flag check here:
2100
2100
  if sentinel_path.exists() and pipulate.db.get('airlock_sealed') != 'true':
2101
2101
  logger.info("🚨 Sentinel detected and airlock unsealed. Triggering Airlock.")
@@ -3723,7 +3723,7 @@ async def reset_config_onboarding(request):
3723
3723
  # 1. Wipe the File System Sentinel
3724
3724
  try:
3725
3725
  project_root = Path(os.getcwd())
3726
- sentinel_path = project_root / "Workshop" / "data" / ".onboarded"
3726
+ sentinel_path = project_root / "Workshop" / "personal" / "Notebooks" / "data" / ".onboarded"
3727
3727
  if sentinel_path.exists():
3728
3728
  sentinel_path.unlink()
3729
3729
  logger.info(f"🗑️ Deleted sentinel file: {sentinel_path}")
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