100xprism 2.3.1

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 (207) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +196 -0
  3. package/VERSION +1 -0
  4. package/adapters/antigravity.sh +14 -0
  5. package/adapters/claude-code.sh +160 -0
  6. package/adapters/codex.sh +13 -0
  7. package/adapters/copilot.sh +13 -0
  8. package/adapters/cursor.sh +13 -0
  9. package/adapters/gemini.sh +13 -0
  10. package/adapters/lib/__pycache__/modules.cpython-312.pyc +0 -0
  11. package/adapters/lib/modules.py +592 -0
  12. package/adapters/lib/shared.sh +83 -0
  13. package/adapters/lib/sync_plugins.py +113 -0
  14. package/adapters/windsurf.sh +15 -0
  15. package/bin/100xprism.js +29 -0
  16. package/get.sh +24 -0
  17. package/install-project.sh +82 -0
  18. package/install.sh +281 -0
  19. package/lib/adapters/windows.js +429 -0
  20. package/lib/bootstrap.js +33 -0
  21. package/lib/init.js +19 -0
  22. package/lib/install.js +18 -0
  23. package/lib/migrate.js +52 -0
  24. package/lib/platform.js +22 -0
  25. package/lib/update.js +29 -0
  26. package/modules/_lib/reference.md +77 -0
  27. package/modules/a11y-auditor/SKILL.md +151 -0
  28. package/modules/ab-test-setup/SKILL.md +266 -0
  29. package/modules/ab-test-setup/evals/evals.json +105 -0
  30. package/modules/ab-test-setup/references/sample-size-guide.md +263 -0
  31. package/modules/ab-test-setup/references/test-templates.md +277 -0
  32. package/modules/ad-creative/SKILL.md +362 -0
  33. package/modules/ad-creative/evals/evals.json +90 -0
  34. package/modules/ad-creative/references/generative-tools.md +637 -0
  35. package/modules/ad-creative/references/platform-specs.md +213 -0
  36. package/modules/ai-seo/SKILL.md +398 -0
  37. package/modules/ai-seo/evals/evals.json +90 -0
  38. package/modules/ai-seo/references/content-patterns.md +285 -0
  39. package/modules/ai-seo/references/platform-ranking-factors.md +152 -0
  40. package/modules/analytics-tracking/SKILL.md +309 -0
  41. package/modules/analytics-tracking/evals/evals.json +90 -0
  42. package/modules/analytics-tracking/references/event-library.md +260 -0
  43. package/modules/analytics-tracking/references/ga4-implementation.md +300 -0
  44. package/modules/analytics-tracking/references/gtm-implementation.md +390 -0
  45. package/modules/architect/SKILL.md +282 -0
  46. package/modules/branch/SKILL.md +105 -0
  47. package/modules/churn-prevention/SKILL.md +424 -0
  48. package/modules/churn-prevention/evals/evals.json +93 -0
  49. package/modules/churn-prevention/references/cancel-flow-patterns.md +316 -0
  50. package/modules/churn-prevention/references/dunning-playbook.md +408 -0
  51. package/modules/cloud-security/SKILL.md +240 -0
  52. package/modules/cold-email/SKILL.md +178 -0
  53. package/modules/cold-email/evals/evals.json +94 -0
  54. package/modules/cold-email/references/benchmarks.md +83 -0
  55. package/modules/cold-email/references/follow-up-sequences.md +81 -0
  56. package/modules/cold-email/references/frameworks.md +90 -0
  57. package/modules/cold-email/references/personalization.md +79 -0
  58. package/modules/cold-email/references/subject-lines.md +53 -0
  59. package/modules/commit/SKILL.md +195 -0
  60. package/modules/competitor-alternatives/SKILL.md +256 -0
  61. package/modules/competitor-alternatives/evals/evals.json +93 -0
  62. package/modules/competitor-alternatives/references/content-architecture.md +271 -0
  63. package/modules/competitor-alternatives/references/templates.md +223 -0
  64. package/modules/connect/SKILL.md +894 -0
  65. package/modules/content-strategy/SKILL.md +359 -0
  66. package/modules/content-strategy/evals/evals.json +90 -0
  67. package/modules/context-dump/SKILL.md +67 -0
  68. package/modules/copy-editing/SKILL.md +447 -0
  69. package/modules/copy-editing/evals/evals.json +89 -0
  70. package/modules/copy-editing/references/plain-english-alternatives.md +394 -0
  71. package/modules/copywriting/SKILL.md +271 -0
  72. package/modules/copywriting/evals/evals.json +111 -0
  73. package/modules/copywriting/references/cold-email-benchmarks.md +83 -0
  74. package/modules/copywriting/references/cold-email-follow-ups.md +81 -0
  75. package/modules/copywriting/references/cold-email-frameworks.md +90 -0
  76. package/modules/copywriting/references/cold-email-personalization.md +79 -0
  77. package/modules/copywriting/references/cold-email-subject-lines.md +53 -0
  78. package/modules/copywriting/references/copy-frameworks.md +344 -0
  79. package/modules/copywriting/references/email-copy-guidelines.md +113 -0
  80. package/modules/copywriting/references/email-types.md +515 -0
  81. package/modules/copywriting/references/natural-transitions.md +272 -0
  82. package/modules/copywriting/references/sequence-templates.md +168 -0
  83. package/modules/data-query/SKILL.md +58 -0
  84. package/modules/data-viz/SKILL.md +225 -0
  85. package/modules/db/SKILL.md +205 -0
  86. package/modules/db/db-engines/_router.md +24 -0
  87. package/modules/db/db-engines/athena.md +16 -0
  88. package/modules/db/db-engines/cloud-sql.md +16 -0
  89. package/modules/db/db-engines/databricks.md +14 -0
  90. package/modules/db/db-engines/oracle.md +14 -0
  91. package/modules/db/db-engines/postgres.md +15 -0
  92. package/modules/db/db-engines/presto.md +14 -0
  93. package/modules/db/db-engines/snowflake.md +14 -0
  94. package/modules/docs/SKILL.md +100 -0
  95. package/modules/email-sequence/SKILL.md +309 -0
  96. package/modules/email-sequence/evals/evals.json +93 -0
  97. package/modules/email-sequence/references/copy-guidelines.md +113 -0
  98. package/modules/email-sequence/references/email-types.md +515 -0
  99. package/modules/email-sequence/references/sequence-templates.md +168 -0
  100. package/modules/enterprise-design/SKILL.md +75 -0
  101. package/modules/eval/SKILL.md +105 -0
  102. package/modules/figma-translator/SKILL.md +49 -0
  103. package/modules/fix-bugs/SKILL.md +104 -0
  104. package/modules/form-cro/SKILL.md +429 -0
  105. package/modules/form-cro/evals/evals.json +90 -0
  106. package/modules/free-tool-strategy/SKILL.md +178 -0
  107. package/modules/free-tool-strategy/evals/evals.json +90 -0
  108. package/modules/free-tool-strategy/references/tool-types.md +217 -0
  109. package/modules/gate/SKILL.md +232 -0
  110. package/modules/grill-me/SKILL.md +59 -0
  111. package/modules/interaction-engineer/SKILL.md +49 -0
  112. package/modules/issue/SKILL.md +272 -0
  113. package/modules/launch/SKILL.md +345 -0
  114. package/modules/launch-strategy/SKILL.md +353 -0
  115. package/modules/launch-strategy/evals/evals.json +91 -0
  116. package/modules/lint/SKILL.md +126 -0
  117. package/modules/marketing-ideas/SKILL.md +167 -0
  118. package/modules/marketing-ideas/evals/evals.json +90 -0
  119. package/modules/marketing-ideas/references/ideas-by-category.md +366 -0
  120. package/modules/marketing-psychology/SKILL.md +455 -0
  121. package/modules/marketing-psychology/evals/evals.json +88 -0
  122. package/modules/motion-designer/SKILL.md +214 -0
  123. package/modules/onboarding-cro/SKILL.md +220 -0
  124. package/modules/onboarding-cro/evals/evals.json +92 -0
  125. package/modules/onboarding-cro/references/experiments.md +258 -0
  126. package/modules/orchestrate/SKILL.md +77 -0
  127. package/modules/page-cro/SKILL.md +182 -0
  128. package/modules/page-cro/evals/evals.json +111 -0
  129. package/modules/page-cro/references/experiments.md +248 -0
  130. package/modules/page-cro/references/paywall-experiments.md +164 -0
  131. package/modules/paid-ads/SKILL.md +315 -0
  132. package/modules/paid-ads/evals/evals.json +90 -0
  133. package/modules/paid-ads/references/ad-copy-templates.md +207 -0
  134. package/modules/paid-ads/references/audience-targeting.md +243 -0
  135. package/modules/paid-ads/references/platform-setup-checklists.md +277 -0
  136. package/modules/paywall-upgrade-cro/SKILL.md +227 -0
  137. package/modules/paywall-upgrade-cro/evals/evals.json +93 -0
  138. package/modules/paywall-upgrade-cro/references/experiments.md +164 -0
  139. package/modules/popup-cro/SKILL.md +453 -0
  140. package/modules/popup-cro/evals/evals.json +94 -0
  141. package/modules/pr/SKILL.md +203 -0
  142. package/modules/pricing-strategy/SKILL.md +231 -0
  143. package/modules/pricing-strategy/evals/evals.json +90 -0
  144. package/modules/pricing-strategy/references/research-methods.md +152 -0
  145. package/modules/pricing-strategy/references/tier-structure.md +232 -0
  146. package/modules/product-marketing-context/SKILL.md +241 -0
  147. package/modules/product-marketing-context/evals/evals.json +85 -0
  148. package/modules/programmatic-seo/SKILL.md +238 -0
  149. package/modules/programmatic-seo/evals/evals.json +94 -0
  150. package/modules/programmatic-seo/references/playbooks.md +308 -0
  151. package/modules/push/SKILL.md +202 -0
  152. package/modules/referral-program/SKILL.md +255 -0
  153. package/modules/referral-program/evals/evals.json +89 -0
  154. package/modules/referral-program/references/affiliate-programs.md +164 -0
  155. package/modules/referral-program/references/program-examples.md +143 -0
  156. package/modules/release/SKILL.md +293 -0
  157. package/modules/revops/SKILL.md +343 -0
  158. package/modules/revops/evals/evals.json +91 -0
  159. package/modules/revops/references/automation-playbooks.md +290 -0
  160. package/modules/revops/references/lifecycle-definitions.md +278 -0
  161. package/modules/revops/references/routing-rules.md +203 -0
  162. package/modules/revops/references/scoring-models.md +247 -0
  163. package/modules/sales-enablement/SKILL.md +349 -0
  164. package/modules/sales-enablement/evals/evals.json +91 -0
  165. package/modules/sales-enablement/references/deck-frameworks.md +263 -0
  166. package/modules/sales-enablement/references/demo-scripts.md +355 -0
  167. package/modules/sales-enablement/references/objection-library.md +270 -0
  168. package/modules/sales-enablement/references/one-pager-templates.md +208 -0
  169. package/modules/schema-markup/SKILL.md +179 -0
  170. package/modules/schema-markup/evals/evals.json +87 -0
  171. package/modules/schema-markup/references/schema-examples.md +398 -0
  172. package/modules/security/SKILL.md +138 -0
  173. package/modules/seo-audit/SKILL.md +412 -0
  174. package/modules/seo-audit/evals/evals.json +136 -0
  175. package/modules/seo-audit/references/ai-writing-detection.md +200 -0
  176. package/modules/seo-audit/references/content-patterns.md +285 -0
  177. package/modules/seo-audit/references/platform-ranking-factors.md +152 -0
  178. package/modules/signup-flow-cro/SKILL.md +359 -0
  179. package/modules/signup-flow-cro/evals/evals.json +88 -0
  180. package/modules/site-architecture/SKILL.md +357 -0
  181. package/modules/site-architecture/evals/evals.json +88 -0
  182. package/modules/site-architecture/references/mermaid-templates.md +216 -0
  183. package/modules/site-architecture/references/navigation-patterns.md +305 -0
  184. package/modules/site-architecture/references/site-type-templates.md +293 -0
  185. package/modules/social-content/SKILL.md +278 -0
  186. package/modules/social-content/evals/evals.json +92 -0
  187. package/modules/social-content/references/platforms.md +170 -0
  188. package/modules/social-content/references/post-templates.md +177 -0
  189. package/modules/social-content/references/reverse-engineering.md +195 -0
  190. package/modules/spec/SKILL.md +81 -0
  191. package/modules/subagents/SKILL.md +123 -0
  192. package/modules/techdebt/SKILL.md +71 -0
  193. package/modules/terminal-setup/SKILL.md +49 -0
  194. package/modules/test/SKILL.md +493 -0
  195. package/modules/test/references/e2e-patterns.md +294 -0
  196. package/modules/update-claude-md/SKILL.md +52 -0
  197. package/modules/visual-system-architect/SKILL.md +53 -0
  198. package/package.json +44 -0
  199. package/plugins/plugins.json +43 -0
  200. package/shell/aliases.sh +24 -0
  201. package/shell/check-update.sh +212 -0
  202. package/templates/.env.example +199 -0
  203. package/templates/docker-compose.md +46 -0
  204. package/templates/node-frontend.md +56 -0
  205. package/templates/node-fullstack.md +59 -0
  206. package/templates/python-api.md +57 -0
  207. package/update.sh +231 -0
@@ -0,0 +1,592 @@
1
+ #!/usr/bin/env python3
2
+ """Single parser for modules/<slug>/SKILL.md.
3
+
4
+ Used by all adapters. Outputs JSON to stdout with one of these subcommands:
5
+
6
+ list — full manifest (all modules, sorted by category/name)
7
+ emit-concat <out> [limit] — write concatenated core bodies + on-demand index;
8
+ if <limit> set (chars), fall back to index-only mode
9
+ when over budget.
10
+ emit-cursor <project_dir> — write .cursor/rules/<slug>.mdc per module
11
+ emit-codex <project_dir> — write Codex-native AGENTS.md, repo skills, hooks
12
+ emit-claude-code — write ~/.claude/skills/<slug>/* + ~/.claude/commands/<slug>.md
13
+ emit-hooks [--sync] — idempotently merge first-party hooks into
14
+ ~/.claude/settings.json from hooks/hooks.manifest.json.
15
+ Each hook is enabled per its manifest `default`, overridable
16
+ by its `toggle_env` env var (1/true=on, 0/false=off).
17
+ --sync only refreshes hooks already present (used on update).
18
+ """
19
+ from __future__ import annotations
20
+
21
+ import json
22
+ import os
23
+ import re
24
+ import shutil
25
+ import sys
26
+ from pathlib import Path
27
+
28
+ REPO = Path(__file__).resolve().parents[2]
29
+ MODULES_DIR = REPO / "modules"
30
+
31
+ # Markers + manifest let `emit-claude-code` prune ONLY 100xprism's own artifacts
32
+ # (never the user's hand-authored skills/commands) when a module is removed or
33
+ # merged upstream. See cmd_emit_claude_code.
34
+ GENERATED_MARKER = ".100xprism-generated"
35
+ ALIAS_MARKER = "<!-- 100xprism generated alias — regenerate, do not edit -->"
36
+ MANIFEST_NAME = ".100xprism-manifest.json"
37
+
38
+ # Modules removed in past releases. Cleaned up on update even for installs whose
39
+ # skills predate the manifest/marker (so they carry neither). Safe to trim once
40
+ # installs have cycled through a manifest-aware update.
41
+ REMOVED_MODULES = {"systems-architect", "conversion-copy"}
42
+
43
+ # Central model-routing map. Modules declare a bare alias (`model: haiku`) in
44
+ # frontmatter. Claude Code reads that alias natively and auto-resolves it to the
45
+ # latest version — so the alias is emitted verbatim there (see cmd_emit_claude_code,
46
+ # which copies frontmatter as-is). Every other adapter picks its own (often
47
+ # non-Claude) model, so they receive a vendor-neutral reasoning-tier hint instead of
48
+ # a Claude model name. `id`/`label` are the single source of truth for the deferred
49
+ # slash-command / documentation work; the emitters in this pass use `tier_hint`.
50
+ MODEL_ALIASES = {
51
+ "haiku": {"id": "claude-haiku-4-5", "label": "Claude Haiku 4.5", "tier_hint": "fast / low-cost (mechanical task)"},
52
+ "sonnet": {"id": "claude-sonnet-4-6", "label": "Claude Sonnet 4.6", "tier_hint": "balanced (moderate reasoning)"},
53
+ "opus": {"id": "claude-opus-4-8", "label": "Claude Opus 4.8", "tier_hint": "most capable (deep reasoning)"},
54
+ }
55
+
56
+
57
+ def tier_annotation(model: str) -> str:
58
+ """Vendor-neutral routing hint for non-Claude adapters. '' for unknown/empty alias."""
59
+ info = MODEL_ALIASES.get(model.strip())
60
+ return f"_Suggested model tier: {info['tier_hint']}_" if info else ""
61
+
62
+
63
+ def short_description(desc: str) -> str:
64
+ """First sentence of a description, capped at 140 chars — for tight rule triggering."""
65
+ d = desc.split(". ", 1)[0]
66
+ if len(d) > 140:
67
+ d = d[:137] + "..."
68
+ return d
69
+
70
+
71
+ def render_command_alias(fm: dict, slug: str, body: str) -> str:
72
+ """Slash-command alias that mirrors the skill's routing/guardrails in frontmatter.
73
+
74
+ Commands and skills share a frontmatter schema, so the alias carries description,
75
+ model routing, and allowed-tools instead of dropping them. argument-hint is emitted
76
+ generically only when the body actually consumes positional input.
77
+ """
78
+ lines = ["---", f"description: {short_description(fm.get('description', ''))}"]
79
+ model = fm.get("model", "").strip()
80
+ if model:
81
+ lines.append(f"model: {model}")
82
+ allowed = fm.get("allowed-tools", "").strip()
83
+ if allowed:
84
+ lines.append(f"allowed-tools: {allowed}")
85
+ if "$ARGUMENTS" in body or "$1" in body:
86
+ lines.append("argument-hint: [arguments]")
87
+ lines += ["---", "", ALIAS_MARKER, "", f"Use the `{fm.get('name', slug)}` skill.", "", "$ARGUMENTS", ""]
88
+ return "\n".join(lines)
89
+
90
+
91
+ def split_frontmatter(text: str) -> tuple[dict, str]:
92
+ if not text.startswith("---\n"):
93
+ return {}, text
94
+ end = text.find("\n---\n", 4)
95
+ if end == -1:
96
+ return {}, text
97
+ fm_block = text[4:end]
98
+ body = text[end + 5 :]
99
+ fm: dict[str, str] = {}
100
+ current_key: str | None = None
101
+ for line in fm_block.splitlines():
102
+ if not line.strip():
103
+ continue
104
+ if line.startswith(" ") and current_key:
105
+ fm[current_key] = (fm[current_key] + " " + line.strip()).strip()
106
+ continue
107
+ if ":" in line:
108
+ key, _, val = line.partition(":")
109
+ current_key = key.strip()
110
+ fm[current_key] = val.strip()
111
+ return fm, body
112
+
113
+
114
+ def list_modules() -> list[dict]:
115
+ out: list[dict] = []
116
+ for skill_md in sorted(MODULES_DIR.glob("*/SKILL.md")):
117
+ fm, body = split_frontmatter(skill_md.read_text())
118
+ out.append({
119
+ "slug": skill_md.parent.name,
120
+ "name": fm.get("name", skill_md.parent.name),
121
+ "description": fm.get("description", ""),
122
+ "category": fm.get("category", "uncategorized"),
123
+ "tier": fm.get("tier", "on-demand"),
124
+ "slash_command": fm.get("slash_command", ""),
125
+ "allowed_tools": fm.get("allowed-tools", ""),
126
+ "model": fm.get("model", ""),
127
+ "body": body,
128
+ "dir": str(skill_md.parent),
129
+ })
130
+ out.sort(key=lambda m: (m["tier"] != "core", m["category"], m["slug"]))
131
+ return out
132
+
133
+
134
+ CATEGORY_ORDER = [
135
+ "lifecycle", "quality", "engineering", "data", "design", "docs", "marketing", "uncategorized"
136
+ ]
137
+
138
+
139
+ def category_sort_key(c: str) -> int:
140
+ try:
141
+ return CATEGORY_ORDER.index(c)
142
+ except ValueError:
143
+ return len(CATEGORY_ORDER)
144
+
145
+
146
+ def emit_index(modules: list[dict], indent: str = "") -> str:
147
+ """One-line-per-module index, grouped by category. Used for on-demand listing."""
148
+ lines: list[str] = []
149
+ by_cat: dict[str, list[dict]] = {}
150
+ for m in modules:
151
+ by_cat.setdefault(m["category"], []).append(m)
152
+ for cat in sorted(by_cat.keys(), key=category_sort_key):
153
+ lines.append(f"{indent}**{cat.title()}** ({len(by_cat[cat])}):")
154
+ for m in by_cat[cat]:
155
+ slash = f" `{m['slash_command']}`" if m["slash_command"] else ""
156
+ d = short_description(m["description"])
157
+ tier = tier_annotation(m.get("model", ""))
158
+ tier_suffix = f" {tier}" if tier else ""
159
+ lines.append(f"{indent}- `{m['slug']}`{slash} — {d}{tier_suffix}")
160
+ lines.append("")
161
+ return "\n".join(lines).rstrip() + "\n"
162
+
163
+
164
+ HEADER = (
165
+ "# 100x Dev — Modules\n"
166
+ "# Generated by 100xprism (https://github.com/rajitsaha/100xprism)\n"
167
+ "# Source of truth: modules/<slug>/SKILL.md. Edit there and regenerate.\n\n"
168
+ )
169
+
170
+
171
+ def render_concat(modules: list[dict]) -> str:
172
+ """Full concat: inline core module bodies + index of on-demand modules."""
173
+ out = [HEADER]
174
+ core = [m for m in modules if m["tier"] == "core"]
175
+ on_demand = [m for m in modules if m["tier"] != "core"]
176
+
177
+ out.append("## Core modules (always-on)\n")
178
+ for m in sorted(core, key=lambda x: (category_sort_key(x["category"]), x["slug"])):
179
+ out.append(f"---\n\n## {m['slug']}\n")
180
+ if m["slash_command"]:
181
+ out.append(f"_Slash command: `{m['slash_command']}`_\n")
182
+ tier = tier_annotation(m.get("model", ""))
183
+ if tier:
184
+ out.append(f"{tier}\n")
185
+ out.append(m["body"].lstrip("\n").rstrip() + "\n\n")
186
+
187
+ out.append("---\n\n## On-demand modules (invoke by name)\n\n")
188
+ out.append(
189
+ "These load only when triggered. Ask Claude to use the relevant module by "
190
+ "name when the situation matches.\n\n"
191
+ )
192
+ out.append(emit_index(on_demand))
193
+ return "".join(out)
194
+
195
+
196
+ def render_index_only(modules: list[dict]) -> str:
197
+ """Index-only: every module gets one line. Used when over byte budget (Windsurf)."""
198
+ out = [HEADER, "## Modules available\n\n"]
199
+ out.append(
200
+ "100xprism installs modules in your global config (`~/.claude/skills/` or "
201
+ "`.cursor/rules/`). When the user's request matches one of these, use it.\n\n"
202
+ )
203
+ out.append(emit_index(modules))
204
+ return "".join(out)
205
+
206
+
207
+ def render_codex_agents(modules: list[dict]) -> str:
208
+ """Compact Codex project guidance.
209
+
210
+ Codex has a default AGENTS.md byte budget, while repo skills load through
211
+ progressive disclosure. Keep AGENTS.md as routing/orientation text and emit
212
+ full module bodies into .agents/skills instead.
213
+ """
214
+ out = [
215
+ "# 100x Dev for Codex\n",
216
+ "# Generated by 100xprism (https://github.com/rajitsaha/100xprism)\n",
217
+ "# Source of truth: modules/<slug>/SKILL.md. Regenerate instead of hand-editing.\n\n",
218
+ "## How Codex Should Use 100x Dev\n\n",
219
+ "- Full reusable workflows live in `.agents/skills/<slug>/SKILL.md` so Codex can load them on demand.\n",
220
+ "- When the user names a 100xprism slash workflow like `/gate`, treat it as a request to use the matching skill listed below.\n",
221
+ "- Prefer explicit skill invocation (`$gate`, `$commit`, `$test`, etc.) or `/skills` when available; custom prompt slash commands are intentionally not generated.\n",
222
+ "- Codex hooks, when generated, live in `.codex/hooks.json`. Review and trust them with `/hooks` before expecting enforcement.\n",
223
+ "- Claude Code plugins in `plugins/plugins.json` are not Codex plugins. Use Codex `/plugins` for Codex-native plugins and app/MCP integrations.\n\n",
224
+ "## 100xprism Command Map\n\n",
225
+ ]
226
+
227
+ commands = [m for m in modules if m["slash_command"]]
228
+ for m in sorted(commands, key=lambda x: x["slash_command"]):
229
+ out.append(f"- `{m['slash_command']}` → `${m['slug']}` — {short_description(m['description'])}\n")
230
+
231
+ out.append("\n## Available Skills\n\n")
232
+ out.append(
233
+ "Codex can invoke these implicitly from their descriptions or explicitly by `$name` / `/skills`.\n\n"
234
+ )
235
+ out.append(emit_index(modules))
236
+ return "".join(out)
237
+
238
+
239
+ def cmd_list():
240
+ mods = list_modules()
241
+ print(json.dumps(mods, indent=2))
242
+
243
+
244
+ def cmd_emit_concat(out_path: str, mode: str = "concat"):
245
+ """mode: 'concat' (core bodies + on-demand index) or 'index' (one-liners only)."""
246
+ mods = list_modules()
247
+ if mode == "index":
248
+ text = render_index_only(mods)
249
+ else:
250
+ text = render_concat(mods)
251
+ Path(out_path).parent.mkdir(parents=True, exist_ok=True)
252
+ Path(out_path).write_text(text)
253
+ print(f"wrote {out_path} ({len(text)} bytes, mode={mode})")
254
+
255
+
256
+ def cmd_emit_cursor(project_dir: str):
257
+ rules_dir = Path(project_dir) / ".cursor" / "rules"
258
+ if rules_dir.exists():
259
+ # remove only files we previously wrote (those with our generation marker)
260
+ for f in rules_dir.glob("*.mdc"):
261
+ try:
262
+ first = f.read_text().splitlines()[0:6]
263
+ if any("100xprism" in line for line in first):
264
+ f.unlink()
265
+ except OSError:
266
+ pass
267
+ rules_dir.mkdir(parents=True, exist_ok=True)
268
+ mods = list_modules()
269
+ for m in mods:
270
+ # Map tier to Cursor rule type: core modules are always in context;
271
+ # on-demand modules are agent-requested via a tight first-sentence description.
272
+ always_apply = "true" if m["tier"] == "core" else "false"
273
+ cursor_fm = [
274
+ "---",
275
+ f"description: {short_description(m['description'])}",
276
+ "globs:",
277
+ f"alwaysApply: {always_apply}",
278
+ "---",
279
+ "",
280
+ f"<!-- generated by 100xprism from modules/{m['slug']}/SKILL.md -->",
281
+ "",
282
+ ]
283
+ tier = tier_annotation(m.get("model", ""))
284
+ if tier:
285
+ cursor_fm += [tier, ""]
286
+ text = "\n".join(cursor_fm) + m["body"].lstrip("\n")
287
+ (rules_dir / f"{m['slug']}.mdc").write_text(text)
288
+ print(f"wrote {len(mods)} files to {rules_dir}")
289
+
290
+
291
+ def cmd_emit_codex(project_dir: str):
292
+ """Write Codex-native project artifacts.
293
+
294
+ - AGENTS.md stays compact and under Codex's default project-doc budget.
295
+ - .agents/skills contains the full module directories for progressive loading.
296
+ - .codex/hooks.json wires first-party hooks for Codex's hook browser/trust flow.
297
+ """
298
+ project = Path(project_dir)
299
+ mods = list_modules()
300
+
301
+ agents_file = project / "AGENTS.md"
302
+ agents_file.parent.mkdir(parents=True, exist_ok=True)
303
+ agents_file.write_text(render_codex_agents(mods))
304
+
305
+ skills_dir = project / ".agents" / "skills"
306
+ if skills_dir.exists():
307
+ for child in skills_dir.iterdir():
308
+ if not child.is_dir():
309
+ continue
310
+ marker = child / ".100xprism-generated"
311
+ if marker.exists():
312
+ shutil.rmtree(child)
313
+ skills_dir.mkdir(parents=True, exist_ok=True)
314
+ skill_count = 0
315
+ for module_dir in sorted(MODULES_DIR.glob("*")):
316
+ if not module_dir.is_dir() or not (module_dir / "SKILL.md").is_file():
317
+ continue
318
+ target = skills_dir / module_dir.name
319
+ if target.exists():
320
+ marker = target / ".100xprism-generated"
321
+ if marker.exists():
322
+ shutil.rmtree(target)
323
+ else:
324
+ print(f"skipped existing non-100xprism skill: {target}", file=sys.stderr)
325
+ continue
326
+ shutil.copytree(module_dir, target)
327
+ (target / ".100xprism-generated").write_text(
328
+ "Generated by 100xprism from modules/<slug>/SKILL.md. Regenerate instead of editing here.\n"
329
+ )
330
+ skill_count += 1
331
+
332
+ hooks_dir = project / ".codex"
333
+ hooks_dir.mkdir(parents=True, exist_ok=True)
334
+ manifest = json.loads((HOOKS_DIR / "hooks.manifest.json").read_text())
335
+ hooks: dict[str, list[dict]] = {}
336
+ for hook in manifest.get("hooks", []):
337
+ if not _hook_enabled(hook):
338
+ continue
339
+ command = _hook_command(hook["script"])
340
+ hooks.setdefault(hook["event"], []).append({
341
+ "matcher": hook["matcher"],
342
+ "hooks": [{
343
+ "type": "command",
344
+ "command": command,
345
+ "statusMessage": f"100xprism: {hook['description']}",
346
+ }],
347
+ })
348
+ (hooks_dir / "hooks.json").write_text(json.dumps({"hooks": hooks}, indent=2) + "\n")
349
+
350
+ print(
351
+ f"wrote Codex AGENTS.md + {skill_count} repo skills + "
352
+ f"{sum(len(v) for v in hooks.values())} hook group(s) to {project}"
353
+ )
354
+
355
+
356
+ def _read_manifest(skills_dir: Path) -> dict:
357
+ try:
358
+ return json.loads((skills_dir / MANIFEST_NAME).read_text())
359
+ except (OSError, ValueError):
360
+ return {}
361
+
362
+
363
+ # Pre-rebrand marker names. Renamed in place so manifest/prune logic recognizes
364
+ # installs that were last emitted under the old `100x-dev` name.
365
+ _LEGACY_GENERATED_MARKER = ".100x-dev-generated"
366
+ _LEGACY_MANIFEST_NAME = ".100x-dev-manifest.json"
367
+ _LEGACY_ALIAS_MARKER = "<!-- 100x-dev generated alias — regenerate, do not edit -->"
368
+
369
+
370
+ def _migrate_legacy_markers(skills_dir: Path, commands_dir: Path) -> None:
371
+ legacy_manifest = skills_dir / _LEGACY_MANIFEST_NAME
372
+ if legacy_manifest.exists() and not (skills_dir / MANIFEST_NAME).exists():
373
+ legacy_manifest.rename(skills_dir / MANIFEST_NAME)
374
+ for child in skills_dir.iterdir():
375
+ if not child.is_dir():
376
+ continue
377
+ legacy = child / _LEGACY_GENERATED_MARKER
378
+ if legacy.exists() and not (child / GENERATED_MARKER).exists():
379
+ legacy.rename(child / GENERATED_MARKER)
380
+ for f in commands_dir.iterdir():
381
+ if not f.is_file() or f.suffix != ".md":
382
+ continue
383
+ try:
384
+ text = f.read_text()
385
+ except OSError:
386
+ continue
387
+ if _LEGACY_ALIAS_MARKER in text:
388
+ f.write_text(text.replace(_LEGACY_ALIAS_MARKER, ALIAS_MARKER))
389
+
390
+
391
+ def cmd_emit_claude_code():
392
+ home = Path(os.environ.get("HOME", str(Path.home())))
393
+ skills_dir = home / ".claude" / "skills"
394
+ commands_dir = home / ".claude" / "commands"
395
+ skills_dir.mkdir(parents=True, exist_ok=True)
396
+ commands_dir.mkdir(parents=True, exist_ok=True)
397
+
398
+ _migrate_legacy_markers(skills_dir, commands_dir)
399
+ prev = _read_manifest(skills_dir)
400
+ prev_skills = set(prev.get("skills", []))
401
+ prev_cmds = set(prev.get("commands", []))
402
+
403
+ current_slugs: list[str] = []
404
+ current_cmds: list[str] = []
405
+ skill_count = 0
406
+ cmd_count = 0
407
+ for module_dir in sorted(MODULES_DIR.glob("*")):
408
+ if not module_dir.is_dir():
409
+ continue
410
+ # A module is a dir with a SKILL.md. Shared-reference dirs (e.g. `_lib/`, which
411
+ # holds maintainer conventions in reference.md) have none — skip, don't emit them.
412
+ if not (module_dir / "SKILL.md").is_file():
413
+ continue
414
+ slug = module_dir.name
415
+ target = skills_dir / slug
416
+ if target.exists():
417
+ shutil.rmtree(target)
418
+ shutil.copytree(module_dir, target)
419
+ (target / GENERATED_MARKER).write_text(
420
+ "Generated by 100xprism from modules/<slug>/SKILL.md. Regenerate instead of editing here.\n"
421
+ )
422
+ current_slugs.append(slug)
423
+ skill_count += 1
424
+
425
+ # Slash command alias for any module with slash_command in frontmatter
426
+ skill_md = target / "SKILL.md"
427
+ fm, body = split_frontmatter(skill_md.read_text())
428
+ slash = fm.get("slash_command", "").lstrip("/")
429
+ if slash:
430
+ (commands_dir / f"{slash}.md").write_text(render_command_alias(fm, slug, body))
431
+ current_cmds.append(slash)
432
+ cmd_count += 1
433
+
434
+ cur_skill_set = set(current_slugs)
435
+ cur_cmd_set = set(current_cmds)
436
+
437
+ # Prune orphaned skills: anything we previously emitted (manifest), carrying
438
+ # our marker, or a known-removed module — that is no longer current. We only
439
+ # ever rmtree dirs that are unambiguously ours.
440
+ orphan_skills = (prev_skills | REMOVED_MODULES) - cur_skill_set
441
+ for child in skills_dir.iterdir():
442
+ if child.is_dir() and child.name not in cur_skill_set and (child / GENERATED_MARKER).exists():
443
+ orphan_skills.add(child.name)
444
+ pruned_skills = 0
445
+ for slug in sorted(orphan_skills):
446
+ p = skills_dir / slug
447
+ if p.is_dir() and ((p / GENERATED_MARKER).exists() or slug in prev_skills or slug in REMOVED_MODULES):
448
+ shutil.rmtree(p)
449
+ pruned_skills += 1
450
+
451
+ # Prune orphaned slash-command aliases we previously wrote (marker-guarded so
452
+ # a user's own command of the same name is never deleted).
453
+ pruned_cmds = 0
454
+ for name in sorted(prev_cmds - cur_cmd_set):
455
+ f = commands_dir / f"{name}.md"
456
+ try:
457
+ if f.is_file() and ALIAS_MARKER in f.read_text():
458
+ f.unlink()
459
+ pruned_cmds += 1
460
+ except OSError:
461
+ pass
462
+
463
+ (skills_dir / MANIFEST_NAME).write_text(
464
+ json.dumps({"skills": sorted(current_slugs), "commands": sorted(current_cmds)}, indent=2) + "\n"
465
+ )
466
+
467
+ suffix = ""
468
+ if pruned_skills or pruned_cmds:
469
+ suffix = f" (pruned {pruned_skills} stale skill(s), {pruned_cmds} stale alias(es))"
470
+ print(f"wrote {skill_count} skills + {cmd_count} slash command aliases{suffix}")
471
+
472
+
473
+ HOOKS_DIR = REPO / "hooks"
474
+
475
+
476
+ def _hook_command(script: str) -> str:
477
+ return f'python3 "{HOOKS_DIR / script}"'
478
+
479
+
480
+ def _hook_enabled(hook: dict) -> bool:
481
+ """manifest `default`, overridden by the hook's `toggle_env` env var if set."""
482
+ env = hook.get("toggle_env")
483
+ if env and env in os.environ:
484
+ val = os.environ[env].strip().lower()
485
+ if val in ("1", "true", "yes", "on"):
486
+ return True
487
+ if val in ("0", "false", "no", "off", ""):
488
+ return False
489
+ return bool(hook.get("default"))
490
+
491
+
492
+ def _command_present(entries: list, command: str) -> bool:
493
+ return any(
494
+ h.get("command") == command
495
+ for e in entries
496
+ for h in e.get("hooks", [])
497
+ )
498
+
499
+
500
+ def _strip_command(entries: list, command: str) -> list:
501
+ """Remove our command from every entry; drop entries left with no hooks."""
502
+ out = []
503
+ for e in entries:
504
+ kept = [h for h in e.get("hooks", []) if h.get("command") != command]
505
+ if kept:
506
+ out.append({**e, "hooks": kept})
507
+ return out
508
+
509
+
510
+ def cmd_emit_hooks(sync: bool = False):
511
+ """Idempotently merge first-party hooks into settings.json (declarative).
512
+
513
+ For each manifest hook we strip any existing copy of its command, then re-add a
514
+ single entry when it should be enabled — so re-running never duplicates entries and
515
+ flipping a toggle off removes the hook. `sync` keeps only hooks already present
516
+ (used on update, so we never silently enable a hook the user opted out of).
517
+ """
518
+ manifest = json.loads((HOOKS_DIR / "hooks.manifest.json").read_text())
519
+ hooks_spec = manifest.get("hooks", [])
520
+
521
+ settings_file = Path(
522
+ os.environ.get("SETTINGS_FILE")
523
+ or os.path.expanduser("~/.claude/settings.json")
524
+ )
525
+ settings_file.parent.mkdir(parents=True, exist_ok=True)
526
+ try:
527
+ settings = json.loads(settings_file.read_text())
528
+ except (OSError, json.JSONDecodeError):
529
+ settings = {}
530
+
531
+ settings_hooks = settings.setdefault("hooks", {})
532
+ added, kept, removed = [], [], []
533
+
534
+ for hook in hooks_spec:
535
+ command = _hook_command(hook["script"])
536
+ event = hook["event"]
537
+ entries = settings_hooks.get(event, [])
538
+ was_present = _command_present(entries, command)
539
+ entries = _strip_command(entries, command) # dedupe / declarative reset
540
+
541
+ keep = was_present if sync else _hook_enabled(hook)
542
+ if keep:
543
+ entries.append({
544
+ "matcher": hook["matcher"],
545
+ "hooks": [{"type": "command", "command": command}],
546
+ })
547
+ (kept if was_present else added).append(hook["id"])
548
+ elif was_present:
549
+ removed.append(hook["id"])
550
+
551
+ if entries:
552
+ settings_hooks[event] = entries
553
+ elif event in settings_hooks:
554
+ del settings_hooks[event]
555
+
556
+ settings_file.write_text(json.dumps(settings, indent=2))
557
+
558
+ parts = []
559
+ if added:
560
+ parts.append(f"added {len(added)} ({', '.join(added)})")
561
+ if kept:
562
+ parts.append(f"refreshed {len(kept)}")
563
+ if removed:
564
+ parts.append(f"removed {len(removed)} ({', '.join(removed)})")
565
+ print(f"hooks: {'; '.join(parts) if parts else 'no changes'} → {settings_file}")
566
+
567
+
568
+ def main(argv: list[str]):
569
+ if len(argv) < 2:
570
+ print(__doc__, file=sys.stderr)
571
+ return 2
572
+ cmd = argv[1]
573
+ if cmd == "list":
574
+ cmd_list()
575
+ elif cmd == "emit-concat":
576
+ cmd_emit_concat(argv[2], argv[3] if len(argv) > 3 else "")
577
+ elif cmd == "emit-cursor":
578
+ cmd_emit_cursor(argv[2])
579
+ elif cmd == "emit-codex":
580
+ cmd_emit_codex(argv[2])
581
+ elif cmd == "emit-claude-code":
582
+ cmd_emit_claude_code()
583
+ elif cmd == "emit-hooks":
584
+ cmd_emit_hooks(sync="--sync" in argv[2:])
585
+ else:
586
+ print(f"unknown command: {cmd}", file=sys.stderr)
587
+ return 2
588
+ return 0
589
+
590
+
591
+ if __name__ == "__main__":
592
+ sys.exit(main(sys.argv))
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env bash
2
+ # shared.sh — common logic for all 100xprism adapter scripts.
3
+ # Source this file; do not execute directly.
4
+ #
5
+ # All adapters dispatch to adapters/lib/modules.py for module reading and
6
+ # rendering. Adapters here provide thin shell wrappers + per-tool output paths.
7
+
8
+ _LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
9
+ REPO_DIR="$(cd "$_LIB_DIR/../.." && pwd)"
10
+ MODULES_PY="$_LIB_DIR/modules.py"
11
+
12
+ GREEN='\033[0;32m'
13
+ YELLOW='\033[1;33m'
14
+ NC='\033[0m'
15
+
16
+ # _run_concat <project_path> <output_file_relative> <display_name> [<mode>] [<warning>]
17
+ #
18
+ # Used by tools that consume a single rules file (Codex AGENTS.md, Windsurf, etc.).
19
+ # mode is 'concat' (default — full core bodies + on-demand index) or 'index'
20
+ # (one-line per module; used when the tool has tight size limits).
21
+ _run_concat() {
22
+ local project_path="$1"
23
+ local output_rel="$2"
24
+ local display_name="$3"
25
+ local mode="${4:-concat}"
26
+ local warning_message="${5:-}"
27
+
28
+ local output_file="$project_path/$output_rel"
29
+ mkdir -p "$(dirname "$output_file")"
30
+
31
+ echo ""
32
+ echo "Generating $output_rel for $display_name..."
33
+ if [[ -n "$warning_message" ]]; then
34
+ echo -e "${YELLOW}${warning_message}${NC}"
35
+ fi
36
+
37
+ python3 "$MODULES_PY" emit-concat "$output_file" "$mode"
38
+
39
+ _track_project "$project_path"
40
+ echo -e " ${GREEN}→ Generated $output_file ✓${NC}"
41
+ }
42
+
43
+ # _run_cursor <project_path>
44
+ # Cursor supports per-rule files with description-based auto-trigger, so we
45
+ # write one .cursor/rules/<slug>.mdc per module.
46
+ _run_cursor() {
47
+ local project_path="$1"
48
+ echo ""
49
+ echo "Generating .cursor/rules/ for Cursor..."
50
+ python3 "$MODULES_PY" emit-cursor "$project_path"
51
+ _track_project "$project_path"
52
+ echo -e " ${GREEN}→ Generated .cursor/rules/ in $project_path ✓${NC}"
53
+ }
54
+
55
+ # _run_codex <project_path>
56
+ # Codex supports repo-scoped skills and hooks, so we keep AGENTS.md compact and
57
+ # emit full module bodies into .agents/skills for progressive loading.
58
+ _run_codex() {
59
+ local project_path="$1"
60
+ echo ""
61
+ echo "Generating Codex project artifacts..."
62
+ python3 "$MODULES_PY" emit-codex "$project_path"
63
+ _track_project "$project_path"
64
+ echo -e " ${GREEN}→ Generated AGENTS.md, .agents/skills/, and .codex/hooks.json in $project_path ✓${NC}"
65
+ echo -e " ${YELLOW}→ In Codex, run /hooks to review and trust generated hooks.${NC}"
66
+ }
67
+
68
+ _track_project() {
69
+ local project_path="$1"
70
+ local _tracked_file="$HOME/.100xprism/tracked-projects"
71
+ mkdir -p "$(dirname "$_tracked_file")"
72
+ local _abs_path
73
+ _abs_path="$(cd "$project_path" && pwd)"
74
+ if ! grep -qxF "$_abs_path" "$_tracked_file" 2>/dev/null; then
75
+ echo "$_abs_path" >> "$_tracked_file"
76
+ fi
77
+ }
78
+
79
+ # Back-compat: old adapters call _run_generate with a single output file.
80
+ # Keep it as an alias for _run_concat with default 'concat' mode.
81
+ _run_generate() {
82
+ _run_concat "$@"
83
+ }