@bendyline/gilde 0.1.85 → 0.1.87

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 (106) hide show
  1. package/data/craftbook-templates/index.json +1 -1
  2. package/data/craftbook-templates/raw-index.json +1 -1
  3. package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/craftbook.json +226 -0
  4. package/data/craftbook-templates/ta/tailor-resume/versions/1.0.5/test.json +79 -0
  5. package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/manifest.json +24 -0
  6. package/data/knowledge-catalogs/ca/caselaw-alaska-part-0001/versions/2026.10.1/manifest.json +658 -0
  7. package/data/knowledge-catalogs/ca/caselaw-us-347-349/manifest.json +25 -0
  8. package/data/knowledge-catalogs/ca/caselaw-us-347-349/versions/2026.10.5/manifest.json +38 -0
  9. package/data/knowledge-catalogs/index.json +1 -1
  10. package/data/knowledge-catalogs/raw-index.json +1 -1
  11. package/data/project-types/ch/checkers/versions/1.2.1/about.md +9 -0
  12. package/data/project-types/ch/checkers/versions/1.2.1/game.json +77 -0
  13. package/data/project-types/ch/checkers/versions/1.2.1/manifest.json +168 -0
  14. package/data/project-types/ch/checkers/versions/1.2.1/mission.md +9 -0
  15. package/data/project-types/ch/checkers/versions/1.2.1/pages/board/index.html +1158 -0
  16. package/data/project-types/ch/chess/versions/1.0.4/about.md +9 -0
  17. package/data/project-types/ch/chess/versions/1.0.4/game.json +127 -0
  18. package/data/project-types/ch/chess/versions/1.0.4/manifest.json +182 -0
  19. package/data/project-types/ch/chess/versions/1.0.4/mission.md +8 -0
  20. package/data/project-types/ch/chess/versions/1.0.4/pages/board/index.html +851 -0
  21. package/data/project-types/de/design-scheme/versions/1.0.2/about.md +6 -0
  22. package/data/project-types/de/design-scheme/versions/1.0.2/manifest.json +70 -0
  23. package/data/project-types/de/design-scheme/versions/1.0.2/mission.md +3 -0
  24. package/data/project-types/de/design-scheme/versions/1.0.2/pages/gallery/index.html +212 -0
  25. package/data/project-types/de/design-scheme/versions/1.0.2/schemes/index.json +9 -0
  26. package/data/project-types/de/design-scheme/versions/1.0.2/schemes/warm-dusk.json +4 -0
  27. package/data/project-types/ev/event-planner/versions/1.0.2/about.md +7 -0
  28. package/data/project-types/ev/event-planner/versions/1.0.2/budget.json +5 -0
  29. package/data/project-types/ev/event-planner/versions/1.0.2/guests.json +10 -0
  30. package/data/project-types/ev/event-planner/versions/1.0.2/manifest.json +171 -0
  31. package/data/project-types/ev/event-planner/versions/1.0.2/mission.md +6 -0
  32. package/data/project-types/ev/event-planner/versions/1.0.2/pages/dashboard/index.html +283 -0
  33. package/data/project-types/fi/fitness-coach/versions/1.0.2/about.md +7 -0
  34. package/data/project-types/fi/fitness-coach/versions/1.0.2/manifest.json +126 -0
  35. package/data/project-types/fi/fitness-coach/versions/1.0.2/mission.md +7 -0
  36. package/data/project-types/fi/fitness-coach/versions/1.0.2/pages/dashboard/index.html +273 -0
  37. package/data/project-types/fi/fitness-coach/versions/1.0.2/training.json +6 -0
  38. package/data/project-types/fl/flashcards/versions/1.1.1/about.md +8 -0
  39. package/data/project-types/fl/flashcards/versions/1.1.1/deck.json +7 -0
  40. package/data/project-types/fl/flashcards/versions/1.1.1/manifest.json +168 -0
  41. package/data/project-types/fl/flashcards/versions/1.1.1/mission.md +6 -0
  42. package/data/project-types/fl/flashcards/versions/1.1.1/pages/review/index.html +477 -0
  43. package/data/project-types/fr/freelance-office/versions/1.0.2/about.md +6 -0
  44. package/data/project-types/fr/freelance-office/versions/1.0.2/invoices.json +9 -0
  45. package/data/project-types/fr/freelance-office/versions/1.0.2/manifest.json +143 -0
  46. package/data/project-types/fr/freelance-office/versions/1.0.2/mission.md +6 -0
  47. package/data/project-types/fr/freelance-office/versions/1.0.2/pages/dashboard/index.html +258 -0
  48. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/about.md +7 -0
  49. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/donations.json +7 -0
  50. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/manifest.json +127 -0
  51. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/mission.md +6 -0
  52. package/data/project-types/fu/fundraiser-hq/versions/1.0.2/pages/dashboard/index.html +261 -0
  53. package/data/project-types/go/go/versions/1.0.2/about.md +9 -0
  54. package/data/project-types/go/go/versions/1.0.2/game.json +103 -0
  55. package/data/project-types/go/go/versions/1.0.2/manifest.json +168 -0
  56. package/data/project-types/go/go/versions/1.0.2/mission.md +8 -0
  57. package/data/project-types/go/go/versions/1.0.2/pages/board/index.html +855 -0
  58. package/data/project-types/ho/household-budget/versions/1.0.3/about.md +6 -0
  59. package/data/project-types/ho/household-budget/versions/1.0.3/ledger.json +5 -0
  60. package/data/project-types/ho/household-budget/versions/1.0.3/manifest.json +164 -0
  61. package/data/project-types/ho/household-budget/versions/1.0.3/mission.md +5 -0
  62. package/data/project-types/ho/household-budget/versions/1.0.3/pages/dashboard/index.html +340 -0
  63. package/data/project-types/ho/household-manual/versions/1.0.2/about.md +7 -0
  64. package/data/project-types/ho/household-manual/versions/1.0.2/manifest.json +153 -0
  65. package/data/project-types/ho/household-manual/versions/1.0.2/mission.md +8 -0
  66. package/data/project-types/ho/household-manual/versions/1.0.2/pages/dashboard/index.html +258 -0
  67. package/data/project-types/ho/household-manual/versions/1.0.2/systems.json +8 -0
  68. package/data/project-types/index.json +1 -1
  69. package/data/project-types/jo/job-hunt/versions/1.0.2/about.md +10 -0
  70. package/data/project-types/jo/job-hunt/versions/1.0.2/activity.json +4 -0
  71. package/data/project-types/jo/job-hunt/versions/1.0.2/manifest.json +213 -0
  72. package/data/project-types/jo/job-hunt/versions/1.0.2/mission.md +9 -0
  73. package/data/project-types/jo/job-hunt/versions/1.0.2/pages/dashboard/index.html +430 -0
  74. package/data/project-types/jo/job-hunt/versions/1.0.2/pipeline.json +6 -0
  75. package/data/project-types/la/language-trainer/versions/1.0.2/about.md +6 -0
  76. package/data/project-types/la/language-trainer/versions/1.0.2/manifest.json +80 -0
  77. package/data/project-types/la/language-trainer/versions/1.0.2/mission.md +4 -0
  78. package/data/project-types/la/language-trainer/versions/1.0.2/pages/dashboard/index.html +268 -0
  79. package/data/project-types/la/language-trainer/versions/1.0.2/progress.json +7 -0
  80. package/data/project-types/li/life-binder/versions/1.0.3/about.md +7 -0
  81. package/data/project-types/li/life-binder/versions/1.0.3/documents.json +145 -0
  82. package/data/project-types/li/life-binder/versions/1.0.3/manifest.json +138 -0
  83. package/data/project-types/li/life-binder/versions/1.0.3/mission.md +6 -0
  84. package/data/project-types/li/life-binder/versions/1.0.3/pages/dashboard/index.html +248 -0
  85. package/data/project-types/me/meal-planner/versions/1.0.2/about.md +6 -0
  86. package/data/project-types/me/meal-planner/versions/1.0.2/manifest.json +160 -0
  87. package/data/project-types/me/meal-planner/versions/1.0.2/menu.json +5 -0
  88. package/data/project-types/me/meal-planner/versions/1.0.2/mission.md +5 -0
  89. package/data/project-types/me/meal-planner/versions/1.0.2/pages/dashboard/index.html +248 -0
  90. package/data/project-types/me/meal-planner/versions/1.0.2/pantry.json +9 -0
  91. package/data/project-types/no/novel-writing-room/versions/1.0.2/about.md +7 -0
  92. package/data/project-types/no/novel-writing-room/versions/1.0.2/chapters.json +10 -0
  93. package/data/project-types/no/novel-writing-room/versions/1.0.2/manifest.json +158 -0
  94. package/data/project-types/no/novel-writing-room/versions/1.0.2/mission.md +7 -0
  95. package/data/project-types/no/novel-writing-room/versions/1.0.2/pages/dashboard/index.html +264 -0
  96. package/data/project-types/no/novel-writing-room/versions/1.0.2/sessions.json +6 -0
  97. package/data/project-types/raw-index.json +1 -1
  98. package/data/project-types/tr/trip-planner/versions/1.0.2/about.md +7 -0
  99. package/data/project-types/tr/trip-planner/versions/1.0.2/journal.json +4 -0
  100. package/data/project-types/tr/trip-planner/versions/1.0.2/manifest.json +194 -0
  101. package/data/project-types/tr/trip-planner/versions/1.0.2/mission.md +4 -0
  102. package/data/project-types/tr/trip-planner/versions/1.0.2/packing.json +8 -0
  103. package/data/project-types/tr/trip-planner/versions/1.0.2/pages/dashboard/index.html +308 -0
  104. package/data/project-types/tr/trip-planner/versions/1.0.2/trip.json +7 -0
  105. package/package.json +1 -1
  106. package/schemas/project-type-version.schema.json +13 -0
@@ -0,0 +1,261 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="gezel-demo" content="standalone" />
7
+ <title>Fundraiser HQ</title>
8
+ <style>
9
+ :root {
10
+ color-scheme: light dark;
11
+ --bg: #faf7f2; --card: #fff; --ink: #2b2620; --muted: #7a7166;
12
+ --accent: #b5651d; --track: #ece5da; --good: #4a7c59; --warn: #a4552e;
13
+ }
14
+ :root[data-theme='dark'] {
15
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
16
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
17
+ }
18
+ @media (prefers-color-scheme: dark) {
19
+ :root:not([data-theme='light']) {
20
+ --bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
21
+ --accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
22
+ }
23
+ }
24
+ * { box-sizing: border-box; }
25
+ body { margin: 0; font: 15px/1.5 system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif; background: var(--bg); color: var(--ink); padding: 1.5rem 1.25rem; }
26
+ main { max-width: 620px; margin: 0 auto; }
27
+ h1 { font-family: Georgia, 'PT Serif', 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; margin: 0 0 0.15rem; }
28
+ .sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.9rem; }
29
+ .thermo { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 1rem; margin-bottom: 0.75rem; }
30
+ .thermo .raised { font-size: 1.9rem; font-weight: 700; color: var(--good); font-variant-numeric: tabular-nums; }
31
+ .thermo .goal { color: var(--muted); font-size: 0.9rem; }
32
+ .bar { background: var(--track); border-radius: 4px; height: 14px; overflow: hidden; margin-top: 0.6rem; }
33
+ .bar > span { display: block; height: 100%; background: var(--good); border-radius: 4px; transition: width 0.4s ease; }
34
+ .cols { display: grid; grid-template-columns: 1fr 1fr; gap: 0.6rem; }
35
+ .panel { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; font-size: 0.9rem; }
36
+ .panel h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.5rem; }
37
+ .panel ul { margin: 0; padding-left: 1.05rem; }
38
+ .panel li { margin: 0.25rem 0; }
39
+ .panel .amt { font-weight: 600; }
40
+ .panel .who { color: var(--muted); }
41
+ .owed li { color: var(--warn); }
42
+ .empty { color: var(--muted); font-style: italic; }
43
+ @media (max-width: 640px) { .cols { grid-template-columns: 1fr; } }
44
+ .demo-banner {
45
+ display: flex;
46
+ align-items: center;
47
+ justify-content: space-between;
48
+ gap: 0.75rem;
49
+ margin: 0 0 0.85rem;
50
+ padding: 0.5rem 0.65rem;
51
+ border: 1px solid var(--accent);
52
+ border-radius: 6px;
53
+ background: color-mix(in srgb, var(--card) 88%, var(--accent));
54
+ color: var(--ink);
55
+ font-size: 0.8rem;
56
+ }
57
+ .demo-banner[hidden] { display: none; }
58
+ .demo-banner strong { font-weight: 700; }
59
+ .demo-reset {
60
+ flex: none;
61
+ padding: 0.25rem 0.5rem;
62
+ border: 1px solid var(--accent);
63
+ border-radius: 6px;
64
+ background: var(--card);
65
+ color: var(--ink);
66
+ cursor: pointer;
67
+ font: inherit;
68
+ }
69
+ .demo-reset:hover { border-color: var(--ink); }
70
+ </style>
71
+ </head>
72
+ <body>
73
+ <main>
74
+ <div class="demo-banner" data-gezel-demo-banner hidden>
75
+ <span><strong>Demo mode</strong> — sample data stays in this page.</span>
76
+ <button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
77
+ </div>
78
+ <h1 id="title">Fundraiser HQ</h1>
79
+ <p class="sub" id="sub">Loading the drive…</p>
80
+ <div class="thermo">
81
+ <div class="raised" id="raised">—</div>
82
+ <div class="goal" id="goal"></div>
83
+ <div class="bar"><span id="bar" style="width: 0%"></span></div>
84
+ </div>
85
+ <div class="cols">
86
+ <div class="panel">
87
+ <h2>Latest gifts</h2>
88
+ <ul id="gifts"><li class="empty">No gifts yet — the first one starts the story.</li></ul>
89
+ </div>
90
+ <div class="panel">
91
+ <h2>Thank-yous owed</h2>
92
+ <ul class="owed" id="owed"><li class="empty">All caught up.</li></ul>
93
+ </div>
94
+ </div>
95
+ </main>
96
+ <script>
97
+ /* gezel-page-demo-stub v1 */
98
+ /*
99
+ * The canonical demo stub for Gezel type pages. MIT License.
100
+ *
101
+ * Paste this file's bytes verbatim into a <script> block ahead of the
102
+ * page's own code, then call makeDemoGezel({ page, tools, data }) with the
103
+ * page's demo handlers. Served by Gezel, the real window.gezel is already
104
+ * injected and the stub steps aside; opened as a raw file (the live demos
105
+ * on gezelgilde.com), it installs a window.gezel whose tools and data are
106
+ * the page-supplied handler maps — one code path in every mode. `tools`
107
+ * and `data` map a tool name / declared read path to a function; data
108
+ * handlers back read() and list(), and url() returns the handler's value
109
+ * or passes a data: URI through. See docs/page-authoring.md.
110
+ */
111
+ (function () {
112
+ 'use strict';
113
+ window.makeDemoGezel = function (opts) {
114
+ if (window.gezel) return window.gezel;
115
+ var page = (opts && opts.page) || {};
116
+ var tools = (opts && opts.tools) || {};
117
+ var data = (opts && opts.data) || {};
118
+ var themeListeners = [];
119
+ var media = window.matchMedia ? window.matchMedia('(prefers-color-scheme: dark)') : null;
120
+ var ui = {
121
+ theme: { mode: media && media.matches ? 'dark' : 'light' },
122
+ onTheme: function (cb) {
123
+ themeListeners.push(cb);
124
+ return function () {
125
+ var at = themeListeners.indexOf(cb);
126
+ if (at >= 0) themeListeners.splice(at, 1);
127
+ };
128
+ },
129
+ };
130
+ if (media && media.addEventListener) {
131
+ media.addEventListener('change', function (ev) {
132
+ ui.theme = { mode: ev.matches ? 'dark' : 'light' };
133
+ themeListeners.slice().forEach(function (cb) { cb(ui.theme); });
134
+ });
135
+ }
136
+ function fail(code, message) {
137
+ var err = new Error(message);
138
+ err.code = code;
139
+ return Promise.reject(err);
140
+ }
141
+ function run(map, key, arg) {
142
+ if (!Object.prototype.hasOwnProperty.call(map, key)) return null;
143
+ return Promise.resolve().then(function () { return map[key](arg); });
144
+ }
145
+ window.gezel = {
146
+ page: {
147
+ api: 1,
148
+ projectId: 'demo',
149
+ source: 'type',
150
+ entry: page.entry || 'index.html',
151
+ typeName: page.typeName || 'demo',
152
+ params: page.params || {},
153
+ mode: 'demo',
154
+ },
155
+ tools: {
156
+ list: function () { return Promise.resolve(Object.keys(tools)); },
157
+ invoke: function (name, input) {
158
+ var call = run(tools, name, input || {});
159
+ if (!call) return fail('not-allowed', "The demo does not handle the tool '" + name + "'.");
160
+ return call.then(function (output) { return { output: output }; });
161
+ },
162
+ },
163
+ data: {
164
+ read: function (path, o) {
165
+ return run(data, path, o || {}) || fail('not-allowed', "The demo has no data for '" + path + "'.");
166
+ },
167
+ list: function (path, o) {
168
+ return run(data, path, o || {}) || Promise.resolve([]);
169
+ },
170
+ watch: function () { return function () {}; },
171
+ url: function (path) {
172
+ if (Object.prototype.hasOwnProperty.call(data, path)) return String(data[path]({}));
173
+ return typeof path === 'string' && path.indexOf('data:') === 0 ? path : '';
174
+ },
175
+ },
176
+ ui: ui,
177
+ refresh: function () {},
178
+ };
179
+ return window.gezel;
180
+ };
181
+ })();
182
+ </script>
183
+ <script>
184
+ (function () {
185
+ 'use strict';
186
+
187
+ // ── demo drive (used only when the page opens as a raw file) ──
188
+ var demoDonations = {
189
+ cause: 'The neighborhood reading garden',
190
+ goalCents: 1000000,
191
+ entries: [
192
+ { amountCents: 12500, data: { donor: 'Mara', thanked: true } },
193
+ { amountCents: 50000, data: { donor: 'Canal Street Books', thanked: true } },
194
+ { amountCents: 7500, data: { donor: 'Anonymous', thanked: false } },
195
+ { amountCents: 25000, data: { donor: 'The De Vries family', thanked: false } },
196
+ { amountCents: 100000, data: { donor: 'Veld Works', thanked: true } },
197
+ ],
198
+ };
199
+
200
+ makeDemoGezel({
201
+ page: { entry: 'dashboard/index.html', typeName: 'Fundraiser HQ', params: { cause: 'a good cause', goalCents: 100000 } },
202
+ data: {
203
+ 'donations.json': function () { return demoDonations; },
204
+ },
205
+ });
206
+
207
+ // ── one code path from here: window.gezel exists in every mode ──
208
+ var demoMode = gezel.page.mode === 'demo';
209
+
210
+ function applyTheme(mode) {
211
+ document.documentElement.setAttribute('data-theme', mode === 'dark' ? 'dark' : 'light');
212
+ }
213
+ applyTheme(gezel.ui.theme.mode);
214
+ gezel.ui.onTheme(function (theme) { applyTheme(theme.mode); });
215
+
216
+ if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
217
+
218
+ function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
219
+ function fmt(cents) { var abs = Math.abs(cents); return (cents < 0 ? '-' : '') + Math.floor(abs / 100) + '.' + String(abs % 100).padStart(2, '0'); }
220
+
221
+ function render(doc) {
222
+ var entries = Array.isArray(doc.entries) ? doc.entries : [];
223
+ var goal = Number(doc.goalCents);
224
+ if (!isFinite(goal) || goal <= 0) goal = 0;
225
+ var raised = 0;
226
+ entries.forEach(function (e) { raised += e.amountCents; });
227
+ if (doc.cause) document.getElementById('title').textContent = 'Fundraiser: ' + doc.cause;
228
+ document.getElementById('sub').textContent = entries.length + ' gift(s) so far';
229
+ document.getElementById('raised').textContent = fmt(raised);
230
+ document.getElementById('goal').textContent = goal > 0 ? 'of ' + fmt(goal) + ' — ' + Math.min(100, Math.round((raised / goal) * 100)) + ' percent there' : '';
231
+ document.getElementById('bar').style.width = (goal > 0 ? Math.min(100, Math.round((raised / goal) * 100)) : raised > 0 ? 100 : 0) + '%';
232
+
233
+ var recent = entries.slice(-6).reverse();
234
+ if (recent.length) {
235
+ document.getElementById('gifts').innerHTML = recent.map(function (e) {
236
+ return '<li><span class="amt">' + fmt(e.amountCents) + '</span> <span class="who">from ' + esc((e.data && e.data.donor) || 'anonymous') + '</span></li>';
237
+ }).join('');
238
+ }
239
+ var owed = entries.filter(function (e) { return !e.data || e.data.thanked !== true; });
240
+ if (owed.length) {
241
+ document.getElementById('owed').innerHTML = owed.slice(0, 8).map(function (e) {
242
+ return '<li>' + esc((e.data && e.data.donor) || 'anonymous') + ' (' + fmt(e.amountCents) + ')</li>';
243
+ }).join('');
244
+ } else {
245
+ document.getElementById('owed').innerHTML = '<li class="empty">All caught up.</li>';
246
+ }
247
+ }
248
+
249
+ function loadDrive() {
250
+ return gezel.data
251
+ .read('donations.json')
252
+ .then(function (next) { if (next) render(next); })
253
+ .catch(function () {});
254
+ }
255
+
256
+ loadDrive();
257
+ gezel.data.watch('donations.json', function () { loadDrive(); });
258
+ })();
259
+ </script>
260
+ </body>
261
+ </html>
@@ -0,0 +1,9 @@
1
+ A standing Go table. You place black stones on the board in the **Output**
2
+ tab; your Go-speler plays white — reading the exact position, responding on the
3
+ board, and keeping the conversation {{personality}} and unhurried.
4
+
5
+ Choose a quick 9×9 game, a roomier 13×13 board, or the full 19×19 goban. The
6
+ Go-speler plays as an **{{playStyle}}**: opponents keep their reading private,
7
+ while instructors offer one light teaching observation at a time. The board
8
+ enforces captures, suicide, simple ko, passing, and Chinese-style area scoring.
9
+ Two passes settle the score.
@@ -0,0 +1,103 @@
1
+ {
2
+ "version": 1,
3
+ "boardSize": 9,
4
+ "squares": ".................................................................................",
5
+ "turn": "user",
6
+ "status": "playing",
7
+ "komi": 6.5,
8
+ "consecutivePasses": 0,
9
+ "previousPosition": null,
10
+ "legalMoves": [
11
+ "A9",
12
+ "B9",
13
+ "C9",
14
+ "D9",
15
+ "E9",
16
+ "F9",
17
+ "G9",
18
+ "H9",
19
+ "J9",
20
+ "A8",
21
+ "B8",
22
+ "C8",
23
+ "D8",
24
+ "E8",
25
+ "F8",
26
+ "G8",
27
+ "H8",
28
+ "J8",
29
+ "A7",
30
+ "B7",
31
+ "C7",
32
+ "D7",
33
+ "E7",
34
+ "F7",
35
+ "G7",
36
+ "H7",
37
+ "J7",
38
+ "A6",
39
+ "B6",
40
+ "C6",
41
+ "D6",
42
+ "E6",
43
+ "F6",
44
+ "G6",
45
+ "H6",
46
+ "J6",
47
+ "A5",
48
+ "B5",
49
+ "C5",
50
+ "D5",
51
+ "E5",
52
+ "F5",
53
+ "G5",
54
+ "H5",
55
+ "J5",
56
+ "A4",
57
+ "B4",
58
+ "C4",
59
+ "D4",
60
+ "E4",
61
+ "F4",
62
+ "G4",
63
+ "H4",
64
+ "J4",
65
+ "A3",
66
+ "B3",
67
+ "C3",
68
+ "D3",
69
+ "E3",
70
+ "F3",
71
+ "G3",
72
+ "H3",
73
+ "J3",
74
+ "A2",
75
+ "B2",
76
+ "C2",
77
+ "D2",
78
+ "E2",
79
+ "F2",
80
+ "G2",
81
+ "H2",
82
+ "J2",
83
+ "A1",
84
+ "B1",
85
+ "C1",
86
+ "D1",
87
+ "E1",
88
+ "F1",
89
+ "G1",
90
+ "H1",
91
+ "J1",
92
+ "pass"
93
+ ],
94
+ "captures": {
95
+ "user": 0,
96
+ "ai": 0
97
+ },
98
+ "score": null,
99
+ "moveLog": [],
100
+ "lastMove": null,
101
+ "lastMoveThought": "",
102
+ "updatedAt": "2026-07-28T00:00:00.000Z"
103
+ }
@@ -0,0 +1,168 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.2",
4
+ "releasedAt": "2026-10-06T00:00:00Z",
5
+ "meesterManaged": false,
6
+ "mode": "solo",
7
+ "leadLabel": "Opponent",
8
+ "leanProfile": true,
9
+ "tabVisibility": {
10
+ "overview": false,
11
+ "tasks": false,
12
+ "approvals": false,
13
+ "workspace": false,
14
+ "artifacts": false,
15
+ "map": false
16
+ },
17
+ "params": {
18
+ "type": "object",
19
+ "properties": {
20
+ "personality": {
21
+ "type": "string",
22
+ "title": "Table-talk style",
23
+ "description": "How your opponent talks between moves.",
24
+ "enum": [
25
+ "peppy",
26
+ "cheeky",
27
+ "zen",
28
+ "gracious"
29
+ ],
30
+ "default": "peppy"
31
+ },
32
+ "playStyle": {
33
+ "type": "string",
34
+ "title": "How should the Go-speler play?",
35
+ "description": "An opponent keeps their strategy to themselves; an instructor offers light teaching notes.",
36
+ "enum": [
37
+ "Opponent",
38
+ "Instructor"
39
+ ],
40
+ "default": "Opponent"
41
+ }
42
+ },
43
+ "required": [
44
+ "personality",
45
+ "playStyle"
46
+ ]
47
+ },
48
+ "nameTemplate": "Go vs the Go-speler",
49
+ "aboutTemplate": "about.md",
50
+ "missionTemplate": "mission.md",
51
+ "gezels": [
52
+ {
53
+ "templateId": "go-player",
54
+ "voorman": true
55
+ }
56
+ ],
57
+ "scripts": {
58
+ "game-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\n\nexport const meta = defineScript({\n name: 'game-store',\n description:\n \"Read and update the Go game (game.json). Call action:'board' to see the position and every legal move; action:'ai_move' to play a coordinate such as D4 or 'pass' (optional moveThought); action:'state' for a light summary; action:'new_game' to reset with boardSize 9, 13, or 19 and optional komi. The engine enforces captures, suicide, simple ko, passing, and area scoring.\",\n inputs: {\n action: {\n type: 'string',\n description: \"One of 'state', 'board', 'user_move', 'ai_move', or 'new_game'.\",\n required: true,\n },\n move: {\n type: 'string',\n description: \"A board coordinate such as 'D4', or 'pass'.\",\n },\n boardSize: {\n type: 'number',\n description: 'For new_game: 9, 13, or 19. Defaults to 9.',\n },\n komi: {\n type: 'number',\n description: 'For new_game: points added to white. Defaults to 6.5.',\n },\n moveThought: {\n type: 'string',\n description: 'A short line of table talk to show on the board (max 140 chars).',\n },\n },\n outputs: {\n board: { type: 'string', description: 'ASCII board with coordinates.' },\n turn: { type: 'string', description: \"Whose move: 'user' (black) or 'ai' (white).\" },\n status: {\n type: 'string',\n description: 'playing, user_won, ai_won, or draw.',\n },\n lastMove: { type: 'string', description: \"The move just played, e.g. 'D4' or 'pass'.\" },\n legalMoves: { type: 'string', description: 'Legal placements plus pass.' },\n score: { type: 'string', description: 'Area score when the game has ended.' },\n summary: { type: 'string', description: 'Human-readable game summary.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── go-rules-start ──\n// Pure, annotation-free Go engine. Supports 9x9, 13x13, and 19x19.\n// Squares are row-major, y0 at the top. X = black/user; O = white/AI.\n// Coordinates follow Go convention and skip the letter I.\n\nvar GO_FILES = 'ABCDEFGHJKLMNOPQRST';\n\nfunction buildInitial(size) {\n return '.'.repeat(size * size);\n}\n\nfunction inBounds(x, y, size) {\n return x >= 0 && x < size && y >= 0 && y < size;\n}\n\nfunction pointName(x, y, size) {\n return GO_FILES[x] + String(size - y);\n}\n\nfunction parsePoint(name, size) {\n if (typeof name !== 'string') return null;\n var text = name.trim().toUpperCase();\n var match = /^([A-HJ-T])(\\d{1,2})$/.exec(text);\n if (!match) return null;\n var x = GO_FILES.indexOf(match[1]);\n var rank = Number(match[2]);\n if (x < 0 || x >= size || rank < 1 || rank > size) return null;\n return { x: x, y: size - rank, index: (size - rank) * size + x };\n}\n\nfunction neighbors(index, size) {\n var x = index % size;\n var y = Math.floor(index / size);\n var out = [];\n if (x > 0) out.push(index - 1);\n if (x + 1 < size) out.push(index + 1);\n if (y > 0) out.push(index - size);\n if (y + 1 < size) out.push(index + size);\n return out;\n}\n\nfunction groupAt(squares, index, size) {\n var color = squares[index];\n var stones = [];\n var liberties = {};\n var seen = {};\n var queue = [index];\n seen[index] = true;\n while (queue.length) {\n var current = queue.pop();\n stones.push(current);\n for (var next of neighbors(current, size)) {\n if (squares[next] === '.') liberties[next] = true;\n else if (squares[next] === color && !seen[next]) {\n seen[next] = true;\n queue.push(next);\n }\n }\n }\n return { stones: stones, liberties: Object.keys(liberties).map(Number) };\n}\n\nfunction replaceAt(squares, index, ch) {\n return squares.slice(0, index) + ch + squares.slice(index + 1);\n}\n\nfunction applyPlacement(squares, size, side, name, previousPosition) {\n var point = parsePoint(name, size);\n if (!point) return { error: 'bad-coordinate' };\n if (squares[point.index] !== '.') return { error: 'occupied' };\n var stone = side === 'user' ? 'X' : 'O';\n var enemy = side === 'user' ? 'O' : 'X';\n var next = replaceAt(squares, point.index, stone);\n var captures = [];\n var checked = {};\n for (var adjacent of neighbors(point.index, size)) {\n if (next[adjacent] !== enemy || checked[adjacent]) continue;\n var enemyGroup = groupAt(next, adjacent, size);\n for (var member of enemyGroup.stones) checked[member] = true;\n if (enemyGroup.liberties.length === 0) {\n for (var captured of enemyGroup.stones) {\n next = replaceAt(next, captured, '.');\n captures.push(pointName(captured % size, Math.floor(captured / size), size));\n }\n }\n }\n if (groupAt(next, point.index, size).liberties.length === 0) return { error: 'suicide' };\n if (previousPosition && next === previousPosition) return { error: 'ko' };\n return { squares: next, captures: captures };\n}\n\nfunction legalMovesFor(squares, size, side, previousPosition) {\n var moves = [];\n for (var y = 0; y < size; y += 1) {\n for (var x = 0; x < size; x += 1) {\n if (squares[y * size + x] !== '.') continue;\n var name = pointName(x, y, size);\n var result = applyPlacement(squares, size, side, name, previousPosition);\n if (!result.error) moves.push(name);\n }\n }\n moves.push('pass');\n return moves;\n}\n\nfunction scoreArea(squares, size, komi) {\n var blackStones = 0;\n var whiteStones = 0;\n var blackTerritory = 0;\n var whiteTerritory = 0;\n var seen = {};\n for (var i = 0; i < squares.length; i += 1) {\n if (squares[i] === 'X') {\n blackStones += 1;\n continue;\n }\n if (squares[i] === 'O') {\n whiteStones += 1;\n continue;\n }\n if (seen[i]) continue;\n var region = [];\n var borders = {};\n var queue = [i];\n seen[i] = true;\n while (queue.length) {\n var current = queue.pop();\n region.push(current);\n for (var next of neighbors(current, size)) {\n if (squares[next] === '.' && !seen[next]) {\n seen[next] = true;\n queue.push(next);\n } else if (squares[next] === 'X' || squares[next] === 'O') {\n borders[squares[next]] = true;\n }\n }\n }\n if (borders.X && !borders.O) blackTerritory += region.length;\n if (borders.O && !borders.X) whiteTerritory += region.length;\n }\n var black = blackStones + blackTerritory;\n var white = whiteStones + whiteTerritory + komi;\n return {\n black: black,\n white: white,\n blackStones: blackStones,\n whiteStones: whiteStones,\n blackTerritory: blackTerritory,\n whiteTerritory: whiteTerritory,\n winner: black === white ? 'draw' : black > white ? 'user' : 'ai',\n margin: Math.abs(black - white),\n };\n}\n\nfunction statusAfter(squares, size, komi, consecutivePasses) {\n if (consecutivePasses < 2) return 'playing';\n var score = scoreArea(squares, size, komi);\n return score.winner === 'draw' ? 'draw' : score.winner === 'user' ? 'user_won' : 'ai_won';\n}\n\nfunction renderAscii(squares, size) {\n var labels = [];\n for (var x = 0; x < size; x += 1) labels.push(GO_FILES[x]);\n var pad = size >= 10 ? ' ' : ' ';\n var lines = [pad + labels.join(' ')];\n for (var y = 0; y < size; y += 1) {\n var rank = String(size - y).padStart(size >= 10 ? 2 : 1, ' ');\n var row = [];\n for (var x = 0; x < size; x += 1) row.push(squares[y * size + x]);\n lines.push(rank + ' ' + row.join(' ') + ' ' + rank);\n }\n lines.push(pad + labels.join(' '));\n lines.push('X = black (user). O = white (you).');\n return lines.join('\\n');\n}\n// ── go-rules-end ──\n\nconst STATE_FILE = 'game.json';\nconst SIDE_NAMES = { user: 'black', ai: 'white' };\n\ninterface GameState {\n version: number;\n boardSize: number;\n squares: string;\n turn: 'user' | 'ai';\n status: 'playing' | 'user_won' | 'ai_won' | 'draw';\n komi: number;\n consecutivePasses: number;\n previousPosition: string | null;\n legalMoves: string[];\n captures: { user: number; ai: number };\n score: ReturnType<typeof scoreArea> | null;\n moveLog: Array<{\n by: 'user' | 'ai';\n move: string;\n captures: string[];\n at: string;\n moveThought?: string;\n }>;\n lastMove: { by: 'user' | 'ai'; move: string; captures: string[] } | null;\n lastMoveThought: string;\n updatedAt: string;\n}\n\nfunction validSize(value: unknown): number {\n const size = Number(value);\n return size === 13 || size === 19 ? size : 9;\n}\n\nfunction validKomi(value: unknown): number {\n const komi = Number(value);\n return Number.isFinite(komi) && komi >= 0 && komi <= 20 ? komi : 6.5;\n}\n\nfunction freshState(boardSize = 9, komi = 6.5): GameState {\n const size = validSize(boardSize);\n const nextKomi = validKomi(komi);\n const squares = buildInitial(size);\n return {\n version: 1,\n boardSize: size,\n squares,\n turn: 'user',\n status: 'playing',\n komi: nextKomi,\n consecutivePasses: 0,\n previousPosition: null,\n legalMoves: legalMovesFor(squares, size, 'user', null),\n captures: { user: 0, ai: 0 },\n score: null,\n moveLog: [],\n lastMove: null,\n lastMoveThought: '',\n updatedAt: new Date().toISOString(),\n };\n}\n\nasync function load(): Promise<GameState> {\n let raw: string;\n try {\n raw = await gezel.fs.read(STATE_FILE);\n } catch {\n return freshState();\n }\n const parsed = JSON.parse(raw) as Partial<GameState>;\n const size = validSize(parsed.boardSize);\n if (typeof parsed.squares !== 'string' || parsed.squares.length !== size * size) {\n throw new Error(`${STATE_FILE} is corrupt; call action:'new_game' to reset.`);\n }\n return {\n ...freshState(size, parsed.komi),\n ...parsed,\n boardSize: size,\n komi: validKomi(parsed.komi),\n } as GameState;\n}\n\nasync function persist(state: GameState): Promise<GameState> {\n state.legalMoves =\n state.status === 'playing'\n ? legalMovesFor(\n state.squares,\n state.boardSize,\n state.turn,\n state.previousPosition,\n )\n : [];\n state.updatedAt = new Date().toISOString();\n await gezel.fs.write(STATE_FILE, `${JSON.stringify(state, null, 2)}\\n`);\n return state;\n}\n\nfunction renderLegal(state: GameState): string {\n if (state.status !== 'playing') return '(game over)';\n return state.legalMoves.join(', ');\n}\n\nfunction renderScore(state: GameState): string {\n if (!state.score) return '(not scored — play continues)';\n const result =\n state.score.winner === 'draw'\n ? 'draw'\n : `${SIDE_NAMES[state.score.winner]} wins by ${state.score.margin}`;\n return `Black ${state.score.black}; white ${state.score.white} (includes ${state.komi} komi): ${result}.`;\n}\n\nfunction summarize(state: GameState): string {\n if (state.status !== 'playing') return `Game over. ${renderScore(state)}`;\n return `Move ${state.moveLog.length + 1}: ${SIDE_NAMES[state.turn]} (${state.turn}) to move on ${state.boardSize}x${state.boardSize}. Captures — black ${state.captures.user}, white ${state.captures.ai}.`;\n}\n\nfunction applyTurn(\n state: GameState,\n by: 'user' | 'ai',\n moveThought?: string,\n): GameState {\n if (state.status !== 'playing') {\n throw new Error(`The game is over (${state.status}). Call action:'new_game' to start another.`);\n }\n if (state.turn !== by) {\n throw new Error(by === 'ai' ? \"It's not your turn yet.\" : 'The opponent is still to move.');\n }\n const move = input.move?.trim() || '';\n const normalized = move.toLowerCase() === 'pass' ? 'pass' : move.toUpperCase();\n if (!state.legalMoves.includes(normalized)) {\n throw new Error(\n `Illegal move '${move}'. Legal moves for ${SIDE_NAMES[by]}: ${renderLegal(state)}`,\n );\n }\n\n let captured: string[] = [];\n const before = state.squares;\n if (normalized === 'pass') {\n state.consecutivePasses += 1;\n } else {\n const applied = applyPlacement(\n state.squares,\n state.boardSize,\n by,\n normalized,\n state.previousPosition,\n );\n if (applied.error || !applied.squares) {\n throw new Error(`Illegal move '${move}' (${applied.error || 'unknown'}).`);\n }\n state.squares = applied.squares;\n captured = applied.captures || [];\n state.captures[by] += captured.length;\n state.consecutivePasses = 0;\n }\n state.previousPosition = before;\n state.turn = (by === 'user' ? 'ai' : 'user') as 'user' | 'ai';\n state.status = statusAfter(\n state.squares,\n state.boardSize,\n state.komi,\n state.consecutivePasses,\n ) as GameState['status'];\n if (state.status !== 'playing') {\n state.score = scoreArea(state.squares, state.boardSize, state.komi);\n }\n const entry = {\n by,\n move: normalized,\n captures: captured,\n at: new Date().toISOString(),\n ...(moveThought ? { moveThought } : {}),\n };\n state.moveLog.push(entry);\n state.lastMove = { by, move: normalized, captures: captured };\n if (by === 'ai' && moveThought) state.lastMoveThought = moveThought;\n return state;\n}\n\nlet state = await load();\nlet lastMoveText = state.lastMove?.move ?? '(none)';\n\nif (input.action === 'new_game') {\n state = await persist(freshState(input.boardSize, input.komi));\n lastMoveText = '(none)';\n} else if (input.action === 'user_move' || input.action === 'ai_move') {\n const by = input.action === 'user_move' ? 'user' : 'ai';\n const moveThought = input.moveThought?.trim().slice(0, 140);\n state = await persist(applyTurn(state, by, moveThought));\n lastMoveText = state.lastMove?.move ?? '(none)';\n} else if (input.action !== 'state' && input.action !== 'board') {\n throw new Error(`Unknown action '${input.action}'. Use state, board, user_move, ai_move, or new_game.`);\n}\n\ngezel.output({\n board:\n `${renderAscii(state.squares, state.boardSize)}\\nTurn: ${SIDE_NAMES[state.turn]} (${state.turn}).\\n` +\n `Komi: ${state.komi}. Consecutive passes: ${state.consecutivePasses}.\\n` +\n `Legal moves: ${renderLegal(state)}`,\n turn: state.turn,\n status: state.status,\n lastMove: lastMoveText,\n legalMoves: renderLegal(state),\n score: renderScore(state),\n summary: summarize(state),\n});\n"
59
+ },
60
+ "tools": [
61
+ {
62
+ "name": "get_board",
63
+ "description": "See the authoritative Go position, board size, captures, whose turn it is, and every legal move.",
64
+ "script": "game-store",
65
+ "inputs": {
66
+ "type": "object",
67
+ "properties": {}
68
+ },
69
+ "bind": {
70
+ "action": "board"
71
+ }
72
+ },
73
+ {
74
+ "name": "make_move",
75
+ "description": "Play your white move. Pass a coordinate such as 'D4', or 'pass', plus optional moveThought.",
76
+ "script": "game-store",
77
+ "inputs": {
78
+ "type": "object",
79
+ "properties": {
80
+ "move": {
81
+ "type": "string",
82
+ "description": "A legal coordinate such as 'D4', or 'pass'."
83
+ },
84
+ "moveThought": {
85
+ "type": "string",
86
+ "description": "A short line of table talk (max 140 chars)."
87
+ }
88
+ },
89
+ "required": [
90
+ "move"
91
+ ]
92
+ },
93
+ "bind": {
94
+ "action": "ai_move"
95
+ }
96
+ },
97
+ {
98
+ "name": "user_move",
99
+ "description": "The user's black move or pass, played from the Go board.",
100
+ "script": "game-store",
101
+ "inputs": {
102
+ "type": "object",
103
+ "properties": {
104
+ "move": {
105
+ "type": "string"
106
+ }
107
+ },
108
+ "required": [
109
+ "move"
110
+ ]
111
+ },
112
+ "bind": {
113
+ "action": "user_move"
114
+ },
115
+ "reaction": {
116
+ "gezel": "go-player",
117
+ "hideSeed": true,
118
+ "standalone": true,
119
+ "prompt": "Your opponent played {{output.lastMove}}. Status: {{output.status}}. Board now:\n{{output.board}}\nThe board and Legal moves line come directly from the Go engine and are always correct. Do not re-derive captures, liberties, ko, scoring, or legality, and do not invent a coordinate outside the legal list. Spend your thought on choosing among the listed moves.\nStyle: {{playStyle}}. In Opponent style, do not volunteer advice or reveal the user’s available moves, weak groups, threats, or your plan; comment only about your own move or the atmosphere. In Instructor style, offer at most one brief teaching observation without choosing the user’s move. Answer strategy questions when asked.\nAct on this latest position. If the game is over, do not move — give one gracious line about the result and offer a new game. Otherwise choose a strong legal move (or pass when strategically sound) and play it with make_move, including a short {{personality}} moveThought. If make_move rejects it, choose one move verbatim from the legal list in the error and retry. After moving, reply with ONE short line of table talk."
120
+ }
121
+ },
122
+ {
123
+ "name": "new_game",
124
+ "description": "Reset the Go board. Choose 9, 13, or 19 lines and optional komi; defaults to 9×9 with 6.5 komi.",
125
+ "script": "game-store",
126
+ "inputs": {
127
+ "type": "object",
128
+ "properties": {
129
+ "boardSize": {
130
+ "type": "number",
131
+ "enum": [
132
+ 9,
133
+ 13,
134
+ 19
135
+ ]
136
+ },
137
+ "komi": {
138
+ "type": "number",
139
+ "minimum": 0,
140
+ "maximum": 20
141
+ }
142
+ }
143
+ },
144
+ "bind": {
145
+ "action": "new_game"
146
+ }
147
+ }
148
+ ],
149
+ "craftbooks": [],
150
+ "schedules": [],
151
+ "pages": {
152
+ "entry": "board/index.html",
153
+ "api": 1,
154
+ "reads": [
155
+ {
156
+ "source": "workspace",
157
+ "path": "game.json"
158
+ }
159
+ ],
160
+ "tools": [
161
+ "user_move",
162
+ "new_game"
163
+ ]
164
+ },
165
+ "workspaceSeed": [
166
+ "game.json"
167
+ ]
168
+ }
@@ -0,0 +1,8 @@
1
+ - Keep a Go board ready with black moving first, and make 9×9, 13×13, and
2
+ 19×19 rematches easy to start.
3
+ - The Go-speler answers every user placement or pass with a legal white turn.
4
+ - Let the engine settle captures, suicide, simple ko, passing, komi, and
5
+ Chinese-style area scoring.
6
+ - Keep table talk {{personality}} and kind, matching the chosen {{playStyle}}:
7
+ Opponents do not leak reading or hints; Instructors teach lightly.
8
+ - Treat both passes and the final score calmly, then offer another game.