@drunkcoding/agents-and-skills 0.0.44 → 0.0.46

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 (107) hide show
  1. package/.claude-plugin/marketplace.json +2 -47
  2. package/README.md +3 -6
  3. package/package.json +4 -1
  4. package/plugins/multica-tool/.claude-plugin/plugin.json +1 -1
  5. package/plugins/multica-tool/scripts/lib.mjs +53 -4
  6. package/plugins/multica-tool/scripts/multica-export.mjs +170 -50
  7. package/plugins/multica-tool/scripts/multica-import.mjs +209 -27
  8. package/plugins/multica-tool/skills/export/SKILL.md +91 -22
  9. package/plugins/multica-tool/skills/import/SKILL.md +24 -9
  10. package/plugins/multica-tool/skills/sync/SKILL.md +1 -1
  11. package/plugins/plugin-validator/.claude-plugin/plugin.json +1 -1
  12. package/plugins/html-effectiveness/.claude-plugin/plugin.json +0 -17
  13. package/plugins/html-effectiveness/README.md +0 -28
  14. package/plugins/html-effectiveness/agents/report-builder.md +0 -66
  15. package/plugins/html-effectiveness/assets/base.css +0 -51
  16. package/plugins/html-effectiveness/assets/base.js +0 -11
  17. package/plugins/html-effectiveness/assets/charts.js +0 -1
  18. package/plugins/html-effectiveness/assets/components.css +0 -10
  19. package/plugins/html-effectiveness/commands/html-report.md +0 -13
  20. package/plugins/html-effectiveness/docs/template-gallery.md +0 -28
  21. package/plugins/html-effectiveness/package.json +0 -6
  22. package/plugins/html-effectiveness/scripts/_tmp.js +0 -37
  23. package/plugins/html-effectiveness/scripts/catalog.js +0 -47
  24. package/plugins/html-effectiveness/scripts/extract.js +0 -104
  25. package/plugins/html-effectiveness/scripts/mustache.js +0 -90
  26. package/plugins/html-effectiveness/scripts/render.js +0 -176
  27. package/plugins/html-effectiveness/skills/html-effectiveness/SKILL.md +0 -94
  28. package/plugins/html-effectiveness/templates/01-exploration-code-approaches.html.tmpl +0 -400
  29. package/plugins/html-effectiveness/templates/02-exploration-visual-designs.html.tmpl +0 -456
  30. package/plugins/html-effectiveness/templates/03-code-review-pr.html.tmpl +0 -581
  31. package/plugins/html-effectiveness/templates/04-code-understanding.html.tmpl +0 -443
  32. package/plugins/html-effectiveness/templates/05-design-system.html.tmpl +0 -578
  33. package/plugins/html-effectiveness/templates/06-component-variants.html.tmpl +0 -557
  34. package/plugins/html-effectiveness/templates/07-prototype-animation.html.tmpl +0 -417
  35. package/plugins/html-effectiveness/templates/08-prototype-interaction.html.tmpl +0 -363
  36. package/plugins/html-effectiveness/templates/09-slide-deck.html.tmpl +0 -535
  37. package/plugins/html-effectiveness/templates/10-svg-illustrations.html.tmpl +0 -444
  38. package/plugins/html-effectiveness/templates/11-status-report.html.tmpl +0 -152
  39. package/plugins/html-effectiveness/templates/12-incident-report.html.tmpl +0 -547
  40. package/plugins/html-effectiveness/templates/13-flowchart-diagram.html.tmpl +0 -361
  41. package/plugins/html-effectiveness/templates/14-research-feature-explainer.html.tmpl +0 -348
  42. package/plugins/html-effectiveness/templates/15-research-concept-explainer.html.tmpl +0 -334
  43. package/plugins/html-effectiveness/templates/16-implementation-plan.html.tmpl +0 -657
  44. package/plugins/html-effectiveness/templates/17-pr-writeup.html.tmpl +0 -553
  45. package/plugins/html-effectiveness/templates/18-editor-triage-board.html.tmpl +0 -541
  46. package/plugins/html-effectiveness/templates/19-editor-feature-flags.html.tmpl +0 -627
  47. package/plugins/html-effectiveness/templates/20-editor-prompt-tuner.html.tmpl +0 -687
  48. package/plugins/html-effectiveness/templates/manifest.json +0 -441
  49. package/plugins/team-share/.claude-plugin/plugin.json +0 -18
  50. package/plugins/team-share/README.md +0 -46
  51. package/plugins/team-share/agents/team-share.md +0 -53
  52. package/plugins/team-share/commands/team-share.md +0 -4
  53. package/plugins/team-share/skills/claude-config/SKILL.md +0 -143
  54. package/plugins/team-share/skills/codegraph-setup/SKILL.md +0 -81
  55. package/plugins/team-share/skills/understand-setup/SKILL.md +0 -115
  56. package/plugins/tech-graph/.claude-plugin/plugin.json +0 -17
  57. package/plugins/tech-graph/README.md +0 -66
  58. package/plugins/tech-graph/agents/tech-graph.md +0 -97
  59. package/plugins/tech-graph/commands/tech-graph.md +0 -13
  60. package/plugins/tech-graph/scripts/check-deps.sh +0 -55
  61. package/plugins/tech-graph/skills/tech-graph/LICENSE +0 -21
  62. package/plugins/tech-graph/skills/tech-graph/README.md +0 -555
  63. package/plugins/tech-graph/skills/tech-graph/README.zh.md +0 -514
  64. package/plugins/tech-graph/skills/tech-graph/SKILL.md +0 -263
  65. package/plugins/tech-graph/skills/tech-graph/agents/openai.yaml +0 -4
  66. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style1-flat.png +0 -0
  67. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style2-dark.png +0 -0
  68. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style3-blueprint.png +0 -0
  69. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style4-notion.png +0 -0
  70. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style5-glass.png +0 -0
  71. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style6-claude.png +0 -0
  72. package/plugins/tech-graph/skills/tech-graph/assets/samples/sample-style7-openai.png +0 -0
  73. package/plugins/tech-graph/skills/tech-graph/fixtures/agent-memory-types-style4.json +0 -181
  74. package/plugins/tech-graph/skills/tech-graph/fixtures/api-flow-style7.json +0 -40
  75. package/plugins/tech-graph/skills/tech-graph/fixtures/mem0-style1.json +0 -297
  76. package/plugins/tech-graph/skills/tech-graph/fixtures/microservices-style3.json +0 -64
  77. package/plugins/tech-graph/skills/tech-graph/fixtures/multi-agent-style5.json +0 -45
  78. package/plugins/tech-graph/skills/tech-graph/fixtures/system-architecture-style6.json +0 -48
  79. package/plugins/tech-graph/skills/tech-graph/fixtures/tool-call-style2.json +0 -182
  80. package/plugins/tech-graph/skills/tech-graph/package.json +0 -42
  81. package/plugins/tech-graph/skills/tech-graph/references/diagram-types.md +0 -171
  82. package/plugins/tech-graph/skills/tech-graph/references/icons.md +0 -406
  83. package/plugins/tech-graph/skills/tech-graph/references/style-1-flat-icon.md +0 -108
  84. package/plugins/tech-graph/skills/tech-graph/references/style-2-dark-terminal.md +0 -107
  85. package/plugins/tech-graph/skills/tech-graph/references/style-3-blueprint.md +0 -113
  86. package/plugins/tech-graph/skills/tech-graph/references/style-4-notion-clean.md +0 -95
  87. package/plugins/tech-graph/skills/tech-graph/references/style-5-glassmorphism.md +0 -125
  88. package/plugins/tech-graph/skills/tech-graph/references/style-6-claude-official.md +0 -209
  89. package/plugins/tech-graph/skills/tech-graph/references/style-7-openai.md +0 -215
  90. package/plugins/tech-graph/skills/tech-graph/references/style-diagram-matrix.md +0 -135
  91. package/plugins/tech-graph/skills/tech-graph/references/svg-layout-best-practices.md +0 -100
  92. package/plugins/tech-graph/skills/tech-graph/references/svg-to-png.md +0 -115
  93. package/plugins/tech-graph/skills/tech-graph/scripts/README.md +0 -269
  94. package/plugins/tech-graph/skills/tech-graph/scripts/generate-diagram.sh +0 -181
  95. package/plugins/tech-graph/skills/tech-graph/scripts/generate-from-template.py +0 -1587
  96. package/plugins/tech-graph/skills/tech-graph/scripts/test-all-styles.sh +0 -143
  97. package/plugins/tech-graph/skills/tech-graph/scripts/validate-svg.sh +0 -306
  98. package/plugins/tech-graph/skills/tech-graph/templates/agent-architecture.svg +0 -28
  99. package/plugins/tech-graph/skills/tech-graph/templates/architecture.svg +0 -23
  100. package/plugins/tech-graph/skills/tech-graph/templates/comparison-matrix.svg +0 -14
  101. package/plugins/tech-graph/skills/tech-graph/templates/data-flow.svg +0 -28
  102. package/plugins/tech-graph/skills/tech-graph/templates/er-diagram.svg +0 -21
  103. package/plugins/tech-graph/skills/tech-graph/templates/flowchart.svg +0 -21
  104. package/plugins/tech-graph/skills/tech-graph/templates/sequence.svg +0 -20
  105. package/plugins/tech-graph/skills/tech-graph/templates/state-machine.svg +0 -20
  106. package/plugins/tech-graph/skills/tech-graph/templates/timeline.svg +0 -19
  107. package/plugins/tech-graph/skills/tech-graph/templates/use-case.svg +0 -21
@@ -1,687 +0,0 @@
1
- <!--
2
- template: 20-editor-prompt-tuner
3
- upstream: 20-editor-prompt-tuner.html
4
- slots:
5
- title: string # replaces first <h1> innerHTML
6
- -->
7
- <div class="wrap">
8
-
9
- <header>
10
- <div class="eyebrow">Acme / editor / prompt-tuner</div>
11
- <h1>{{title}}</h1>
12
- <p class="sub">Edit the system prompt on the left and watch three sample tickets re-render the filled template on the right, live as you type. When it reads well across all three moods, copy the template out.</p>
13
- </header>
14
-
15
- <div class="toolbar">
16
- <button id="copyBtn" class="btn-primary">Copy prompt</button>
17
- <button id="resetBtn" class="btn-ghost">Reset</button>
18
- <div class="spacer"></div>
19
- <div class="hint">slots use <strong>{{double_brace}}</strong> syntax</div>
20
- </div>
21
-
22
- <div class="cols">
23
-
24
- <!-- left -->
25
- <section class="panel">
26
- <div class="ed-toolbar">
27
- <span class="ed-label">Template</span>
28
- <div class="spacer"></div>
29
- <span id="counter" class="counter">0 chars · ~0 tokens</span>
30
- </div>
31
- <div id="editor" class="editor" contenteditable="true" spellcheck="false" autocapitalize="off" autocorrect="off"></div>
32
- <div class="legend">
33
- <div class="legend-label">Available slots</div>
34
- <div id="legendChips" class="legend-chips"></div>
35
- </div>
36
- </section>
37
-
38
- <!-- right -->
39
- <section class="panel preview-panel">
40
- <div class="preview-head">Live preview · 3 sample tickets</div>
41
- <div id="samples"></div>
42
- </section>
43
-
44
- </div>
45
-
46
- <footer>Highlighted slots fill from each sample's ticket fields. Anything underlined in dashed clay isn't a known field and will pass through unfilled.</footer>
47
-
48
- </div>
49
-
50
- <style>
51
- * { box-sizing: border-box; }
52
- .wrap {
53
- max-width: 1180px;
54
- margin: 0 auto;
55
- padding: 48px 32px 64px;
56
- }
57
- /* ---------- header ---------- */
58
- header { margin-bottom: 22px; }
59
- .eyebrow {
60
- font-family: var(--mono);
61
- font-size: 11px;
62
- letter-spacing: 0.08em;
63
- text-transform: uppercase;
64
- color: var(--gray-500);
65
- }
66
- h1 {
67
- font-family: var(--serif);
68
- font-weight: 500;
69
- font-size: 30px;
70
- letter-spacing: -0.01em;
71
- margin: 6px 0 4px;
72
- }
73
- .sub {
74
- color: var(--gray-500);
75
- max-width: 640px;
76
- }
77
- /* ---------- toolbar ---------- */
78
- .toolbar {
79
- position: sticky;
80
- top: 0;
81
- z-index: 10;
82
- display: flex;
83
- align-items: center;
84
- gap: 10px;
85
- background: var(--ivory);
86
- padding: 10px 0 14px;
87
- margin-bottom: 6px;
88
- border-bottom: 1px solid var(--gray-200);
89
- }
90
- .toolbar .spacer { flex: 1; }
91
- .toolbar .hint {
92
- font-family: var(--mono);
93
- font-size: 11px;
94
- color: var(--gray-500);
95
- letter-spacing: 0.02em;
96
- }
97
- button {
98
- font-family: var(--mono);
99
- font-size: 12px;
100
- cursor: pointer;
101
- -webkit-appearance: none;
102
- appearance: none;
103
- }
104
- .btn-primary {
105
- background: var(--slate);
106
- color: var(--ivory);
107
- border: 1.5px solid var(--slate);
108
- border-radius: 999px;
109
- padding: 9px 16px;
110
- transition: background 140ms ease, transform 80ms ease;
111
- }
112
- .btn-primary:hover { background: var(--gray-800); }
113
- .btn-primary:active { transform: translateY(1px); }
114
- .btn-primary.copied { background: var(--olive); border-color: var(--olive); }
115
- .btn-ghost {
116
- background: transparent;
117
- color: var(--gray-800);
118
- border: 1.5px solid var(--gray-200);
119
- border-radius: 999px;
120
- padding: 8px 15px;
121
- transition: border-color 140ms ease, color 140ms ease;
122
- }
123
- .btn-ghost:hover { border-color: var(--gray-500); color: var(--slate); }
124
- /* ---------- two-column layout ---------- */
125
- .cols {
126
- display: grid;
127
- grid-template-columns: minmax(0, 1.1fr) minmax(0, 1fr);
128
- gap: 24px;
129
- align-items: start;
130
- margin-top: 18px;
131
- }
132
- @media (max-width: 880px) {
133
- .cols { grid-template-columns: 1fr; }
134
- }
135
- .panel {
136
- background: var(--white);
137
- border: 1.5px solid var(--gray-200);
138
- border-radius: 12px;
139
- overflow: hidden;
140
- }
141
- /* ---------- left: editor ---------- */
142
- .ed-toolbar {
143
- display: flex;
144
- align-items: center;
145
- gap: 10px;
146
- padding: 11px 16px;
147
- border-bottom: 1.5px solid var(--gray-200);
148
- background: var(--gray-50);
149
- }
150
- .ed-label {
151
- font-family: var(--mono);
152
- font-size: 11px;
153
- letter-spacing: 0.1em;
154
- color: var(--gray-500);
155
- text-transform: uppercase;
156
- }
157
- .ed-toolbar .spacer { flex: 1; }
158
- .counter {
159
- font-family: var(--mono);
160
- font-size: 11px;
161
- color: var(--gray-800);
162
- background: var(--white);
163
- border: 1.5px solid var(--gray-200);
164
- border-radius: 999px;
165
- padding: 3px 10px;
166
- white-space: nowrap;
167
- font-variant-numeric: tabular-nums;
168
- }
169
- .editor {
170
- font-family: var(--mono);
171
- font-size: 13px;
172
- line-height: 1.7;
173
- padding: 18px;
174
- min-height: 360px;
175
- white-space: pre-wrap;
176
- word-break: break-word;
177
- background: var(--white);
178
- color: var(--slate);
179
- outline: none;
180
- caret-color: var(--clay);
181
- tab-size: 2;
182
- }
183
- .editor:focus-visible { outline: none; }
184
- .slot {
185
- background: var(--oat);
186
- color: var(--clay-d);
187
- border-radius: 4px;
188
- padding: 0 3px;
189
- font-weight: 600;
190
- }
191
- .slot.warn {
192
- background: rgba(217, 119, 87, 0.14);
193
- color: var(--clay-d);
194
- text-decoration: underline dashed var(--clay) 1.5px;
195
- text-underline-offset: 3px;
196
- }
197
- .legend {
198
- border-top: 1.5px solid var(--gray-200);
199
- padding: 12px 16px 14px;
200
- background: var(--gray-50);
201
- }
202
- .legend-label {
203
- font-family: var(--mono);
204
- font-size: 10px;
205
- letter-spacing: 0.1em;
206
- text-transform: uppercase;
207
- color: var(--gray-500);
208
- margin-bottom: 8px;
209
- }
210
- .legend-chips { display: flex; flex-wrap: wrap; gap: 6px; }
211
- .chip {
212
- font-family: var(--mono);
213
- font-size: 11px;
214
- background: var(--oat);
215
- color: var(--clay-d);
216
- border-radius: 4px;
217
- padding: 2px 7px;
218
- font-weight: 600;
219
- }
220
- /* ---------- right: preview ---------- */
221
- .preview-panel { padding: 16px; display: flex; flex-direction: column; gap: 14px; }
222
- .preview-head {
223
- font-family: var(--mono);
224
- font-size: 11px;
225
- letter-spacing: 0.1em;
226
- text-transform: uppercase;
227
- color: var(--gray-500);
228
- padding: 0 2px 2px;
229
- }
230
- .sample {
231
- border: 1.5px solid var(--gray-200);
232
- border-radius: 10px;
233
- overflow: hidden;
234
- }
235
- .sample-head {
236
- display: flex;
237
- align-items: center;
238
- gap: 9px;
239
- padding: 9px 13px;
240
- border-bottom: 1.5px solid var(--gray-200);
241
- background: var(--gray-50);
242
- }
243
- .badge {
244
- font-family: var(--mono);
245
- font-size: 10px;
246
- letter-spacing: 0.08em;
247
- color: var(--gray-500);
248
- }
249
- .cust {
250
- font-family: var(--serif);
251
- font-size: 14px;
252
- font-weight: 500;
253
- letter-spacing: -0.005em;
254
- }
255
- .sample-head .spacer { flex: 1; }
256
- .plan {
257
- font-family: var(--mono);
258
- font-size: 10px;
259
- letter-spacing: 0.05em;
260
- text-transform: uppercase;
261
- border-radius: 999px;
262
- padding: 2px 8px;
263
- border: 1.5px solid var(--gray-200);
264
- color: var(--gray-800);
265
- background: var(--white);
266
- }
267
- .plan.team { background: var(--oat); border-color: var(--oat); color: var(--clay-d); }
268
- .plan.studio { background: var(--olive); border-color: var(--olive); color: var(--ivory); }
269
- .rendered {
270
- font-family: var(--mono);
271
- font-size: 12.5px;
272
- line-height: 1.6;
273
- color: var(--gray-800);
274
- white-space: pre-wrap;
275
- word-break: break-word;
276
- padding: 12px 13px 14px;
277
- }
278
- .filled {
279
- background: var(--gray-50);
280
- border-radius: 3px;
281
- padding: 0 2px;
282
- box-shadow: inset 0 0 0 1px var(--gray-200);
283
- }
284
- .missing {
285
- background: rgba(217, 119, 87, 0.12);
286
- color: var(--clay-d);
287
- border-radius: 3px;
288
- padding: 0 2px;
289
- font-weight: 600;
290
- }
291
- footer {
292
- margin-top: 36px;
293
- font-family: var(--mono);
294
- font-size: 11px;
295
- color: var(--gray-500);
296
- letter-spacing: 0.02em;
297
- }
298
- </style>
299
-
300
- <script>
301
- (function () {
302
- "use strict";
303
-
304
- // ---------------------------------------------------------------------------
305
- // Data
306
- // ---------------------------------------------------------------------------
307
-
308
- var DEFAULT_TEMPLATE =
309
- 'You are a support agent for Acme, a project workspace for small teams.\n' +
310
- '\n' +
311
- 'A customer named {{customer_name}} on the {{plan_tier}} plan wrote in about:\n' +
312
- '"{{ticket_subject}}"\n' +
313
- '\n' +
314
- 'Their message:\n' +
315
- '{{ticket_body}}\n' +
316
- '\n' +
317
- 'Write a reply that is {{tone}}, no more than 120 words, and ends with a single concrete next step.\n' +
318
- 'Never promise a refund without escalating. Sign off as "The Acme team."';
319
-
320
- var SAMPLES = [
321
- {
322
- label: 'SAMPLE 1',
323
- planClass: 'free',
324
- data: {
325
- customer_name: 'Priya N.',
326
- plan_tier: 'Free',
327
- ticket_subject: 'Where did my board go?',
328
- ticket_body: 'Hi — I made a board yesterday called "Spring launch" with my coworker and today I can’t find it anywhere. I’m new to Acme and I’m not sure if I deleted it by accident or if I’m just looking in the wrong place. Can you help?',
329
- tone: 'warm and patient'
330
- }
331
- },
332
- {
333
- label: 'SAMPLE 2',
334
- planClass: 'team',
335
- data: {
336
- customer_name: 'Marcus D.',
337
- plan_tier: 'Team',
338
- ticket_subject: 'Sync keeps dropping comments',
339
- ticket_body: 'This is the third time this week. I leave comments on cards from my laptop, switch to my phone on the train, and they’re gone. My team thinks I’m ignoring them. We pay for 14 seats and this is genuinely making us look bad to a client.',
340
- tone: 'direct and apologetic'
341
- }
342
- },
343
- {
344
- label: 'SAMPLE 3',
345
- planClass: 'studio',
346
- data: {
347
- customer_name: 'Lena K.',
348
- plan_tier: 'Studio',
349
- ticket_subject: 'Update billing entity on next invoice',
350
- ticket_body: 'Hey folks! We just spun up a new holding company (ACME Corp Oy) and finance would love the May invoice to use that name + our new VAT ID, FI00000000. No rush at all, just want it sorted before the cycle runs. Thanks a million.',
351
- tone: 'brisk and friendly'
352
- }
353
- }
354
- ];
355
-
356
- var KNOWN_SLOTS = ['customer_name', 'plan_tier', 'ticket_subject', 'ticket_body', 'tone'];
357
- var SLOT_RE = /\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g;
358
-
359
- // ---------------------------------------------------------------------------
360
- // DOM refs
361
- // ---------------------------------------------------------------------------
362
-
363
- var editor = document.getElementById('editor');
364
- var counterEl = document.getElementById('counter');
365
- var samplesEl = document.getElementById('samples');
366
- var legendEl = document.getElementById('legendChips');
367
- var copyBtn = document.getElementById('copyBtn');
368
- var resetBtn = document.getElementById('resetBtn');
369
-
370
- // ---------------------------------------------------------------------------
371
- // Helpers
372
- // ---------------------------------------------------------------------------
373
-
374
- function escapeHtml(s) {
375
- return s
376
- .replace(/&/g, '&amp;')
377
- .replace(/</g, '&lt;')
378
- .replace(/>/g, '&gt;')
379
- .replace(/"/g, '&quot;');
380
- }
381
-
382
- // Read plain text out of the contenteditable. We rely on white-space:pre-wrap
383
- // and only ever insert text + inline <span>s, so textContent is faithful as
384
- // long as the browser hasn't injected block elements or <br>s. Normalize both.
385
- function getPlainText() {
386
- var out = '';
387
- var walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
388
- var node;
389
- while ((node = walker.nextNode())) {
390
- if (node.nodeType === Node.TEXT_NODE) {
391
- out += node.nodeValue;
392
- } else if (node.nodeType === Node.ELEMENT_NODE) {
393
- var tag = node.tagName;
394
- if (tag === 'BR') out += '\n';
395
- // Some browsers wrap new lines in <div>; emit a newline before a div
396
- // that isn't the first child and doesn't already start after one.
397
- if (tag === 'DIV' && node !== editor.firstChild && out.length && out[out.length - 1] !== '\n') {
398
- out += '\n';
399
- }
400
- }
401
- }
402
- return out;
403
- }
404
-
405
- // ---- Caret save/restore by character offset ----
406
-
407
- function getCaretOffset() {
408
- var sel = window.getSelection();
409
- if (!sel || sel.rangeCount === 0) return null;
410
- var range = sel.getRangeAt(0);
411
- if (!editor.contains(range.startContainer)) return null;
412
-
413
- var pre = range.cloneRange();
414
- pre.selectNodeContents(editor);
415
- pre.setEnd(range.startContainer, range.startOffset);
416
-
417
- // Count characters by walking text nodes within the pre-range. We mirror
418
- // getPlainText's newline handling so offsets line up.
419
- var frag = pre.cloneContents();
420
- var off = 0;
421
- var walker = document.createTreeWalker(frag, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT, null);
422
- var node, lastChar = '';
423
- var first = true;
424
- while ((node = walker.nextNode())) {
425
- if (node.nodeType === Node.TEXT_NODE) {
426
- off += node.nodeValue.length;
427
- if (node.nodeValue.length) lastChar = node.nodeValue[node.nodeValue.length - 1];
428
- } else {
429
- if (node.tagName === 'BR') { off += 1; lastChar = '\n'; }
430
- if (node.tagName === 'DIV' && !first && lastChar !== '\n') { off += 1; lastChar = '\n'; }
431
- }
432
- first = false;
433
- }
434
- return off;
435
- }
436
-
437
- function setCaretOffset(offset) {
438
- if (offset == null) return;
439
- var sel = window.getSelection();
440
- if (!sel) return;
441
-
442
- var walker = document.createTreeWalker(editor, NodeFilter.SHOW_TEXT, null);
443
- var node;
444
- var remaining = offset;
445
- var lastNode = null;
446
-
447
- while ((node = walker.nextNode())) {
448
- lastNode = node;
449
- var len = node.nodeValue.length;
450
- if (remaining <= len) {
451
- var range = document.createRange();
452
- range.setStart(node, remaining);
453
- range.collapse(true);
454
- sel.removeAllRanges();
455
- sel.addRange(range);
456
- return;
457
- }
458
- remaining -= len;
459
- }
460
-
461
- // Past the end — place at end of last text node, or at end of editor.
462
- var range2 = document.createRange();
463
- if (lastNode) {
464
- range2.setStart(lastNode, lastNode.nodeValue.length);
465
- } else {
466
- range2.selectNodeContents(editor);
467
- range2.collapse(false);
468
- }
469
- range2.collapse(true);
470
- sel.removeAllRanges();
471
- sel.addRange(range2);
472
- }
473
-
474
- // ---- Highlighting ----
475
-
476
- function highlightHtml(text) {
477
- var html = '';
478
- var last = 0;
479
- SLOT_RE.lastIndex = 0;
480
- var m;
481
- while ((m = SLOT_RE.exec(text)) !== null) {
482
- html += escapeHtml(text.slice(last, m.index));
483
- var name = m[1];
484
- var cls = KNOWN_SLOTS.indexOf(name) !== -1 ? 'slot' : 'slot warn';
485
- html += '<span class="' + cls + '">' + escapeHtml(m[0]) + '</span>';
486
- last = m.index + m[0].length;
487
- }
488
- html += escapeHtml(text.slice(last));
489
- return html;
490
- }
491
-
492
- function renderEditor(text, caret) {
493
- editor.innerHTML = highlightHtml(text);
494
- if (caret != null && document.activeElement === editor) {
495
- setCaretOffset(caret);
496
- }
497
- }
498
-
499
- // ---- Preview ----
500
-
501
- function fillHtml(text, data) {
502
- var html = '';
503
- var last = 0;
504
- SLOT_RE.lastIndex = 0;
505
- var m;
506
- while ((m = SLOT_RE.exec(text)) !== null) {
507
- html += escapeHtml(text.slice(last, m.index));
508
- var name = m[1];
509
- if (Object.prototype.hasOwnProperty.call(data, name)) {
510
- html += '<span class="filled">' + escapeHtml(String(data[name])) + '</span>';
511
- } else {
512
- html += '<span class="missing">' + escapeHtml(m[0]) + '</span>';
513
- }
514
- last = m.index + m[0].length;
515
- }
516
- html += escapeHtml(text.slice(last));
517
- return html;
518
- }
519
-
520
- var sampleNodes = [];
521
-
522
- function buildSamples() {
523
- samplesEl.innerHTML = '';
524
- sampleNodes = [];
525
- SAMPLES.forEach(function (s) {
526
- var card = document.createElement('div');
527
- card.className = 'sample';
528
-
529
- var head = document.createElement('div');
530
- head.className = 'sample-head';
531
-
532
- var badge = document.createElement('span');
533
- badge.className = 'badge';
534
- badge.textContent = s.label;
535
-
536
- var cust = document.createElement('span');
537
- cust.className = 'cust';
538
- cust.textContent = s.data.customer_name;
539
-
540
- var spacer = document.createElement('span');
541
- spacer.className = 'spacer';
542
-
543
- var plan = document.createElement('span');
544
- plan.className = 'plan ' + s.planClass;
545
- plan.textContent = s.data.plan_tier;
546
-
547
- head.appendChild(badge);
548
- head.appendChild(cust);
549
- head.appendChild(spacer);
550
- head.appendChild(plan);
551
-
552
- var body = document.createElement('div');
553
- body.className = 'rendered';
554
-
555
- card.appendChild(head);
556
- card.appendChild(body);
557
- samplesEl.appendChild(card);
558
- sampleNodes.push({ body: body, data: s.data });
559
- });
560
- samplesEl.style.display = 'flex';
561
- samplesEl.style.flexDirection = 'column';
562
- samplesEl.style.gap = '14px';
563
- }
564
-
565
- function buildLegend() {
566
- legendEl.innerHTML = '';
567
- KNOWN_SLOTS.forEach(function (name) {
568
- var c = document.createElement('span');
569
- c.className = 'chip';
570
- c.textContent = '{{' + name + '}}';
571
- legendEl.appendChild(c);
572
- });
573
- }
574
-
575
- // ---- Counter ----
576
-
577
- function updateCounter(text) {
578
- var chars = text.length;
579
- var tokens = Math.round(chars / 4.2);
580
- counterEl.textContent = chars + ' chars · ~' + tokens + ' tokens';
581
- }
582
-
583
- // ---- Master refresh ----
584
-
585
- function refresh(opts) {
586
- opts = opts || {};
587
- var text = opts.text != null ? opts.text : getPlainText();
588
- var caret = opts.preserveCaret ? getCaretOffset() : null;
589
-
590
- renderEditor(text, caret);
591
- updateCounter(text);
592
- sampleNodes.forEach(function (sn) {
593
- sn.body.innerHTML = fillHtml(text, sn.data);
594
- });
595
- }
596
-
597
- // ---------------------------------------------------------------------------
598
- // Events
599
- // ---------------------------------------------------------------------------
600
-
601
- var raf = null;
602
- editor.addEventListener('input', function () {
603
- if (raf) cancelAnimationFrame(raf);
604
- raf = requestAnimationFrame(function () {
605
- raf = null;
606
- refresh({ preserveCaret: true });
607
- });
608
- });
609
-
610
- // Force plain-text paste so we never inherit foreign HTML.
611
- editor.addEventListener('paste', function (e) {
612
- e.preventDefault();
613
- var txt = (e.clipboardData || window.clipboardData).getData('text/plain');
614
- var sel = window.getSelection();
615
- if (!sel || !sel.rangeCount) return;
616
- var range = sel.getRangeAt(0);
617
- range.deleteContents();
618
- range.insertNode(document.createTextNode(txt));
619
- range.collapse(false);
620
- sel.removeAllRanges();
621
- sel.addRange(range);
622
- refresh({ preserveCaret: true });
623
- });
624
-
625
- // Insert literal newline on Enter so we don't get nested <div>s.
626
- editor.addEventListener('keydown', function (e) {
627
- if (e.key === 'Enter') {
628
- e.preventDefault();
629
- var sel = window.getSelection();
630
- if (!sel || !sel.rangeCount) return;
631
- var range = sel.getRangeAt(0);
632
- range.deleteContents();
633
- var nl = document.createTextNode('\n');
634
- range.insertNode(nl);
635
- range.setStartAfter(nl);
636
- range.collapse(true);
637
- sel.removeAllRanges();
638
- sel.addRange(range);
639
- refresh({ preserveCaret: true });
640
- }
641
- });
642
-
643
- var copyTimer = null;
644
- copyBtn.addEventListener('click', function () {
645
- var text = getPlainText();
646
- var done = function () {
647
- copyBtn.textContent = 'Copied ✓';
648
- copyBtn.classList.add('copied');
649
- if (copyTimer) clearTimeout(copyTimer);
650
- copyTimer = setTimeout(function () {
651
- copyBtn.textContent = 'Copy prompt';
652
- copyBtn.classList.remove('copied');
653
- }, 1200);
654
- };
655
- if (navigator.clipboard && navigator.clipboard.writeText) {
656
- navigator.clipboard.writeText(text).then(done, function () {
657
- fallbackCopy(text); done();
658
- });
659
- } else {
660
- fallbackCopy(text); done();
661
- }
662
- });
663
-
664
- function fallbackCopy(text) {
665
- var ta = document.createElement('textarea');
666
- ta.value = text;
667
- ta.style.position = 'fixed';
668
- ta.style.left = '-9999px';
669
- document.body.appendChild(ta);
670
- ta.select();
671
- try { document.execCommand('copy'); } catch (e) { /* ignore */ }
672
- document.body.removeChild(ta);
673
- }
674
-
675
- resetBtn.addEventListener('click', function () {
676
- refresh({ text: DEFAULT_TEMPLATE });
677
- });
678
-
679
- // ---------------------------------------------------------------------------
680
- // Boot
681
- // ---------------------------------------------------------------------------
682
-
683
- buildLegend();
684
- buildSamples();
685
- refresh({ text: DEFAULT_TEMPLATE });
686
- })();
687
- </script>