pipulate 2.63__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.63/pipulate.egg-info → pipulate-2.65}/PKG-INFO +5 -3
  2. {pipulate-2.63 → pipulate-2.65}/README.md +4 -2
  3. {pipulate-2.63 → pipulate-2.65}/__init__.py +2 -2
  4. {pipulate-2.63 → pipulate-2.65}/apps/030_roles.py +3 -3
  5. {pipulate-2.63 → pipulate-2.65}/imports/ascii_displays.py +100 -18
  6. {pipulate-2.63 → pipulate-2.65}/pipulate/__init__.py +3 -3
  7. {pipulate-2.63 → pipulate-2.65}/pipulate/core.py +6 -6
  8. {pipulate-2.63 → pipulate-2.65/pipulate.egg-info}/PKG-INFO +5 -3
  9. {pipulate-2.63 → pipulate-2.65}/pyproject.toml +3 -3
  10. {pipulate-2.63 → pipulate-2.65}/server.py +2 -2
  11. {pipulate-2.63 → pipulate-2.65}/LICENSE +0 -0
  12. {pipulate-2.63 → pipulate-2.65}/MANIFEST.in +0 -0
  13. {pipulate-2.63 → pipulate-2.65}/apps/010_introduction.py +0 -0
  14. {pipulate-2.63 → pipulate-2.65}/apps/015_config.py +0 -0
  15. {pipulate-2.63 → pipulate-2.65}/apps/020_profiles.py +0 -0
  16. {pipulate-2.63 → pipulate-2.65}/apps/025_aspect.py +0 -0
  17. {pipulate-2.63 → pipulate-2.65}/apps/040_hello_workflow.py +0 -0
  18. {pipulate-2.63 → pipulate-2.65}/apps/050_documentation.py +0 -0
  19. {pipulate-2.63 → pipulate-2.65}/apps/060_tasks.py +0 -0
  20. {pipulate-2.63 → pipulate-2.65}/apps/070_history.py +0 -0
  21. {pipulate-2.63 → pipulate-2.65}/apps/110_parameter_buster.py +0 -0
  22. {pipulate-2.63 → pipulate-2.65}/apps/120_link_graph.py +0 -0
  23. {pipulate-2.63 → pipulate-2.65}/apps/130_gap_analysis.py +0 -0
  24. {pipulate-2.63 → pipulate-2.65}/apps/200_workflow_genesis.py +0 -0
  25. {pipulate-2.63 → pipulate-2.65}/apps/210_widget_examples.py +0 -0
  26. {pipulate-2.63 → pipulate-2.65}/apps/220_roadmap.py +0 -0
  27. {pipulate-2.63 → pipulate-2.65}/apps/230_dev_assistant.py +0 -0
  28. {pipulate-2.63 → pipulate-2.65}/apps/240_simon_mcp.py +0 -0
  29. {pipulate-2.63 → pipulate-2.65}/apps/300_blank_placeholder.py +0 -0
  30. {pipulate-2.63 → pipulate-2.65}/apps/400_botify_trifecta.py +0 -0
  31. {pipulate-2.63 → pipulate-2.65}/apps/440_browser_automation.py +0 -0
  32. {pipulate-2.63 → pipulate-2.65}/apps/450_stream_simulator.py +0 -0
  33. {pipulate-2.63 → pipulate-2.65}/apps/510_text_field.py +0 -0
  34. {pipulate-2.63 → pipulate-2.65}/apps/520_text_area.py +0 -0
  35. {pipulate-2.63 → pipulate-2.65}/apps/530_dropdown.py +0 -0
  36. {pipulate-2.63 → pipulate-2.65}/apps/540_checkboxes.py +0 -0
  37. {pipulate-2.63 → pipulate-2.65}/apps/550_radios.py +0 -0
  38. {pipulate-2.63 → pipulate-2.65}/apps/560_range.py +0 -0
  39. {pipulate-2.63 → pipulate-2.65}/apps/570_switch.py +0 -0
  40. {pipulate-2.63 → pipulate-2.65}/apps/580_upload.py +0 -0
  41. {pipulate-2.63 → pipulate-2.65}/apps/610_markdown.py +0 -0
  42. {pipulate-2.63 → pipulate-2.65}/apps/620_mermaid.py +0 -0
  43. {pipulate-2.63 → pipulate-2.65}/apps/630_prism.py +0 -0
  44. {pipulate-2.63 → pipulate-2.65}/apps/640_javascript.py +0 -0
  45. {pipulate-2.63 → pipulate-2.65}/apps/710_pandas.py +0 -0
  46. {pipulate-2.63 → pipulate-2.65}/apps/720_rich.py +0 -0
  47. {pipulate-2.63 → pipulate-2.65}/apps/730_matplotlib.py +0 -0
  48. {pipulate-2.63 → pipulate-2.65}/apps/810_webbrowser.py +0 -0
  49. {pipulate-2.63 → pipulate-2.65}/apps/820_selenium.py +0 -0
  50. {pipulate-2.63 → pipulate-2.65}/apps/830_pico_slider.py +0 -0
  51. {pipulate-2.63 → pipulate-2.65}/cli.py +0 -0
  52. {pipulate-2.63 → pipulate-2.65}/config.py +0 -0
  53. {pipulate-2.63 → pipulate-2.65}/imports/__init__.py +0 -0
  54. {pipulate-2.63 → pipulate-2.65}/imports/ai_dictdb.py +0 -0
  55. {pipulate-2.63 → pipulate-2.65}/imports/ai_tool_discovery_simple_parser.py +0 -0
  56. {pipulate-2.63 → pipulate-2.65}/imports/append_only_conversation.py +0 -0
  57. {pipulate-2.63 → pipulate-2.65}/imports/botify/__init__.py +0 -0
  58. {pipulate-2.63 → pipulate-2.65}/imports/botify/code_generators.py +0 -0
  59. {pipulate-2.63 → pipulate-2.65}/imports/botify/true_schema_discoverer.py +0 -0
  60. {pipulate-2.63 → pipulate-2.65}/imports/botify_code_generation.py +0 -0
  61. {pipulate-2.63 → pipulate-2.65}/imports/crud.py +0 -0
  62. {pipulate-2.63 → pipulate-2.65}/imports/database_safety_wrapper.py +0 -0
  63. {pipulate-2.63 → pipulate-2.65}/imports/dom_processing/__init__.py +0 -0
  64. {pipulate-2.63 → pipulate-2.65}/imports/dom_processing/ai_dom_beautifier.py +0 -0
  65. {pipulate-2.63 → pipulate-2.65}/imports/dom_processing/enhanced_dom_processor.py +0 -0
  66. {pipulate-2.63 → pipulate-2.65}/imports/durable_backup_system.py +0 -0
  67. {pipulate-2.63 → pipulate-2.65}/imports/mcp_orchestrator.py +0 -0
  68. {pipulate-2.63 → pipulate-2.65}/imports/server_logging.py +0 -0
  69. {pipulate-2.63 → pipulate-2.65}/imports/stream_orchestrator.py +0 -0
  70. {pipulate-2.63 → pipulate-2.65}/imports/voice_synthesis.py +0 -0
  71. {pipulate-2.63 → pipulate-2.65}/pipulate.egg-info/SOURCES.txt +0 -0
  72. {pipulate-2.63 → pipulate-2.65}/pipulate.egg-info/dependency_links.txt +0 -0
  73. {pipulate-2.63 → pipulate-2.65}/pipulate.egg-info/entry_points.txt +0 -0
  74. {pipulate-2.63 → pipulate-2.65}/pipulate.egg-info/requires.txt +0 -0
  75. {pipulate-2.63 → pipulate-2.65}/pipulate.egg-info/top_level.txt +0 -0
  76. {pipulate-2.63 → pipulate-2.65}/setup.cfg +0 -0
  77. {pipulate-2.63 → pipulate-2.65}/tests/test_apply_airlock.py +0 -0
  78. {pipulate-2.63 → pipulate-2.65}/tests/test_mck_rep2.py +0 -0
  79. {pipulate-2.63 → pipulate-2.65}/tools/__init__.py +0 -0
  80. {pipulate-2.63 → pipulate-2.65}/tools/advanced_automation_tools.py +0 -0
  81. {pipulate-2.63 → pipulate-2.65}/tools/botify_tools.py +0 -0
  82. {pipulate-2.63 → pipulate-2.65}/tools/connector_tools.py +0 -0
  83. {pipulate-2.63 → pipulate-2.65}/tools/conversation_tools.py +0 -0
  84. {pipulate-2.63 → pipulate-2.65}/tools/dom_tools.py +0 -0
  85. {pipulate-2.63 → pipulate-2.65}/tools/keychain_tools.py +0 -0
  86. {pipulate-2.63 → pipulate-2.65}/tools/llm_optics.py +0 -0
  87. {pipulate-2.63 → pipulate-2.65}/tools/mcp_tools.py +0 -0
  88. {pipulate-2.63 → pipulate-2.65}/tools/scraper_tools.py +0 -0
  89. {pipulate-2.63 → 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.63
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
@@ -127,17 +127,19 @@ nix develop
127
127
  ### Where your work lives
128
128
  <!-- --- START WORKSPACE TREE --- -->
129
129
  ```text
130
- Notebooks/ — the JupyterLab root (NOT Pipulate's own root)
130
+ Workshop/ — the JupyterLab root (NOT Pipulate's own root)
131
131
  │ FLAT siblings. Nothing nests. Nothing to get wrong.
132
132
  │
133
133
  ├── Advanced_Notebooks/ canon · flake-delivered, copy-if-absent
134
134
  ├── Educational_Notebooks/ canon · your edits survive, updates do not arrive
135
135
  ├── imports/ canon · the code-behind "sauce" modules
136
136
  │
137
- ├── Playground/ personal · gitignored · your own git repo goes here
137
+ ├── personal/ personal · gitignored · your own git repo goes here
138
138
  ├── Client_Work/ personal · gitignored · never leaves this machine
139
139
  ├── Deliverables/ personal · gitignored
140
140
  │
141
+ ├── corporate/ the org's canon · gitignored · its own private repo
142
+ │
141
143
  └── Shared/ the ONE folder for handing work to a teammate
142
144
  ├── alice/ one folder per person; you write ONLY your own
143
145
  └── bob/ single-writer partitions = zero merge conflicts
@@ -70,17 +70,19 @@ nix develop
70
70
  ### Where your work lives
71
71
  <!-- --- START WORKSPACE TREE --- -->
72
72
  ```text
73
- Notebooks/ — the JupyterLab root (NOT Pipulate's own root)
73
+ Workshop/ — the JupyterLab root (NOT Pipulate's own root)
74
74
  │ FLAT siblings. Nothing nests. Nothing to get wrong.
75
75
  │
76
76
  ├── Advanced_Notebooks/ canon · flake-delivered, copy-if-absent
77
77
  ├── Educational_Notebooks/ canon · your edits survive, updates do not arrive
78
78
  ├── imports/ canon · the code-behind "sauce" modules
79
79
  │
80
- ├── Playground/ personal · gitignored · your own git repo goes here
80
+ ├── personal/ personal · gitignored · your own git repo goes here
81
81
  ├── Client_Work/ personal · gitignored · never leaves this machine
82
82
  ├── Deliverables/ personal · gitignored
83
83
  │
84
+ ├── corporate/ the org's canon · gitignored · its own private repo
85
+ │
84
86
  └── Shared/ the ONE folder for handing work to a teammate
85
87
  ├── alice/ one folder per person; you write ONLY your own
86
88
  └── bob/ single-writer partitions = zero merge conflicts
@@ -12,7 +12,7 @@ Usage:
12
12
  pipulate
13
13
  """
14
14
 
15
- __version__ = "2.63"
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.63"
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__ = "Empty Interlock Edits"
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 Notebooks/.agents/skills/roles/SKILL.md (graceful fallback)."""
48
- skill_path = os.path.join(os.path.dirname(__file__), '..', 'Notebooks', '.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 Notebooks/.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
  )
@@ -66,7 +66,7 @@ FIGURATE_LEDGER: dict = {
66
66
  "canal_lock": 311868176,
67
67
  "mechanical_man": 4142234675,
68
68
  # CRC32 after _expand_color_bits_ai() + .strip()
69
- "workspace_tree": 2408173560, # CRC32 no-strip (leading newline preserved, like honeybot_pipeline)
69
+ "workspace_tree": 545211116, # CRC32 no-strip (leading newline preserved, like honeybot_pipeline); resealed 2026-09-27 for the Workshop rename, was 2408173560
70
70
  "forcing_pair": 3594006631, # CRC32 no-strip; seal re-taken 2026-07-27 — 208394658 sealed a re-typed copy of the art, not the bytes on disk; the straddle caught it (drift 1→0)
71
71
  # === FIGURATE_LEDGER_EXTRUDE_BOTTOM ===
72
72
  # Add new artwork CRC32 entries immediately above this line
@@ -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 Notebooks workspace 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,32 +881,28 @@ 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.
808
892
  """
809
893
  art = r"""
810
- Notebooks/ — the JupyterLab root (NOT Pipulate's own root)
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
- ├── Playground/ personal · gitignored · your own git repo goes here
818
- ├── Client_Work/ personal · gitignored · never leaves this machine
819
- ├── Deliverables/ personal · gitignored
820
- │
821
- └── Shared/ the ONE folder for handing work to a teammate
897
+ ├── corporate/ the org's canon · gitignored · its own private repo
898
+ ├── personal/ personal · gitignored · your own git repo goes here
899
+ └── shared/ the ONE folder for handing work to a teammate
822
900
  ├── alice/ one folder per person; you write ONLY your own
823
901
  └── bob/ single-writer partitions = zero merge conflicts
824
902
  """
825
903
  ai_art = _expand_color_bits_ai(art)
826
904
  human_art = _expand_color_bits_human(art)
827
- human = Panel(human_art, title="🗂️ Notebooks Workspace — canon · personal · Shared", border_style="white")
905
+ human = Panel(human_art, title="🗂️ Workshop — corporate · personal · shared", border_style="white")
828
906
  return human, ai_art
829
907
 
830
908
 
@@ -1537,6 +1615,10 @@ Rich console blocked on Mac (Error: {e}), using fallback display.
1537
1615
  return fallback_summary.strip()
1538
1616
 
1539
1617
 
1618
+ if __name__ == "__main__":
1619
+ raise SystemExit(_main())
1620
+
1621
+
1540
1622
  # _ ____ ____ ___ ___ _ ____ _____ ____ _ _
1541
1623
  # / \ / ___| / ___|_ _|_ _| / \ | _ \_ _| | _ \| | __ _ _ _ __ _ _ __ ___ _ _ _ __ __| |
1542
1624
  # / _ \ \___ \| | | | | | / _ \ | |_) || | | |_) | |/ _` | | | |/ _` | '__/ _ \| | | | '_ \ / _` |
@@ -1860,7 +1942,7 @@ Rich console blocked on Mac (Error: {e}), using fallback display.
1860
1942
  # └── assets/ # optional templates and files
1861
1943
  # ```
1862
1944
  #
1863
- # You already have this. `Notebooks/.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.
1864
1946
  #
1865
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.
1866
1948
  #
@@ -1886,7 +1968,7 @@ Rich console blocked on Mac (Error: {e}), using fallback display.
1886
1968
  # ├── cli.py # = the `allowed-tools` surface: mcp-discover / call
1887
1969
  # ├── apply.py # = "PR instructions" made executable (SEARCH/REPLACE actuator)
1888
1970
  # ├── flake.nix # = "Dev environment setup" made executable (nix develop)
1889
- # ├── Notebooks/.agents/skills/ # = Agent Skills, already standard-shaped
1971
+ # ├── .agents/skills/ # = Agent Skills, already standard-shaped
1890
1972
  # │ ├── hello_workflow/SKILL.md
1891
1973
  # │ ├── gsc_readonly/SKILL.md
1892
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 / "Notebooks")
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 / "Notebooks" / "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 / "Notebooks" / "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 / "Notebooks" 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"
@@ -2437,7 +2437,7 @@ class Pipulate:
2437
2437
  def nbup(self, notebook_filename: str, modules: tuple = None):
2438
2438
  """
2439
2439
  Cleans and syncs a notebook and optionally its associated Python modules
2440
- from the working 'Notebooks/' directory back to the version-controlled
2440
+ from the working 'Workshop/' directory back to the version-controlled
2441
2441
  'assets/nbs/' template directory.
2442
2442
  """
2443
2443
  # Import necessary libraries inside the function
@@ -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 / "Notebooks" / 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 / "Notebooks" / "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 / "Notebooks" / "data" / "pipeline.sqlite"
3228
- sentinel_path = self.paths.root / "Notebooks" / "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.63
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
@@ -127,17 +127,19 @@ nix develop
127
127
  ### Where your work lives
128
128
  <!-- --- START WORKSPACE TREE --- -->
129
129
  ```text
130
- Notebooks/ — the JupyterLab root (NOT Pipulate's own root)
130
+ Workshop/ — the JupyterLab root (NOT Pipulate's own root)
131
131
  │ FLAT siblings. Nothing nests. Nothing to get wrong.
132
132
  │
133
133
  ├── Advanced_Notebooks/ canon · flake-delivered, copy-if-absent
134
134
  ├── Educational_Notebooks/ canon · your edits survive, updates do not arrive
135
135
  ├── imports/ canon · the code-behind "sauce" modules
136
136
  │
137
- ├── Playground/ personal · gitignored · your own git repo goes here
137
+ ├── personal/ personal · gitignored · your own git repo goes here
138
138
  ├── Client_Work/ personal · gitignored · never leaves this machine
139
139
  ├── Deliverables/ personal · gitignored
140
140
  │
141
+ ├── corporate/ the org's canon · gitignored · its own private repo
142
+ │
141
143
  └── Shared/ the ONE folder for handing work to a teammate
142
144
  ├── alice/ one folder per person; you write ONLY your own
143
145
  └── bob/ single-writer partitions = zero merge conflicts
@@ -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.63"
8
+ version = "2.65"
9
9
  authors = [
10
10
  { name="Mike Levin", email="pipulate@gmail.com" },
11
11
  ]
@@ -82,7 +82,7 @@ exclude = [
82
82
  "downloads*",
83
83
  "scripts*",
84
84
  "browser_cache*",
85
- "Notebooks*",
85
+ "Workshop*",
86
86
  ".venv*"
87
87
  ]
88
88
 
@@ -126,7 +126,7 @@ ignore = [
126
126
  # Never flag these directories
127
127
  exclude = [
128
128
  ".venv",
129
- "Notebooks",
129
+ "Workshop",
130
130
  "scripts",
131
131
  "browser_cache",
132
132
  "data",
@@ -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("Notebooks/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 / "Notebooks" / "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