@bendyline/gilde 0.1.6 → 0.1.8

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 (115) hide show
  1. package/README.md +17 -0
  2. package/data/chat-models/ge/{gemma4-e2b-q8 → gemma4-e2b-q4}/manifest.json +28 -31
  3. package/data/chat-models/ge/gemma4-e2b-q4/versions/1.2.0/manifest.json +71 -0
  4. package/data/chat-models/ge/{gemma4-e4b-q8 → gemma4-e4b-q4}/manifest.json +28 -26
  5. package/data/chat-models/ge/gemma4-e4b-q4/versions/1.2.0/manifest.json +76 -0
  6. package/data/chat-models/index.json +1 -1
  7. package/data/project-types/ca/caregiving-binder/versions/1.0.1/about.md +8 -0
  8. package/data/project-types/ca/caregiving-binder/versions/1.0.1/care.json +4 -0
  9. package/data/project-types/ca/caregiving-binder/versions/1.0.1/manifest.json +177 -0
  10. package/data/project-types/ca/caregiving-binder/versions/1.0.1/meds.json +8 -0
  11. package/data/project-types/ca/caregiving-binder/versions/1.0.1/mission.md +8 -0
  12. package/data/project-types/ca/caregiving-binder/versions/1.0.1/pages/dashboard/index.html +167 -0
  13. package/data/project-types/ch/checkers/versions/1.1.2/about.md +9 -0
  14. package/data/project-types/ch/checkers/versions/1.1.2/game.json +77 -0
  15. package/data/project-types/ch/checkers/versions/1.1.2/manifest.json +166 -0
  16. package/data/project-types/ch/checkers/versions/1.1.2/mission.md +9 -0
  17. package/data/project-types/ch/checkers/versions/1.1.2/pages/board/index.html +1091 -0
  18. package/data/project-types/ch/chess/versions/1.0.2/about.md +9 -0
  19. package/data/project-types/ch/chess/versions/1.0.2/game.json +127 -0
  20. package/data/project-types/ch/chess/versions/1.0.2/manifest.json +180 -0
  21. package/data/project-types/ch/chess/versions/1.0.2/mission.md +8 -0
  22. package/data/project-types/ch/chess/versions/1.0.2/pages/board/index.html +627 -0
  23. package/data/project-types/ch/chess/versions/1.0.3/about.md +9 -0
  24. package/data/project-types/ch/chess/versions/1.0.3/game.json +127 -0
  25. package/data/project-types/ch/chess/versions/1.0.3/manifest.json +180 -0
  26. package/data/project-types/ch/chess/versions/1.0.3/mission.md +8 -0
  27. package/data/project-types/ch/chess/versions/1.0.3/pages/board/index.html +769 -0
  28. package/data/project-types/de/design-scheme/versions/1.0.1/about.md +6 -0
  29. package/data/project-types/de/design-scheme/versions/1.0.1/manifest.json +69 -0
  30. package/data/project-types/de/design-scheme/versions/1.0.1/mission.md +3 -0
  31. package/data/project-types/de/design-scheme/versions/1.0.1/pages/gallery/index.html +105 -0
  32. package/data/project-types/de/design-scheme/versions/1.0.1/schemes/index.json +9 -0
  33. package/data/project-types/de/design-scheme/versions/1.0.1/schemes/warm-dusk.json +4 -0
  34. package/data/project-types/ev/event-planner/versions/1.0.1/about.md +7 -0
  35. package/data/project-types/ev/event-planner/versions/1.0.1/budget.json +5 -0
  36. package/data/project-types/ev/event-planner/versions/1.0.1/guests.json +10 -0
  37. package/data/project-types/ev/event-planner/versions/1.0.1/manifest.json +170 -0
  38. package/data/project-types/ev/event-planner/versions/1.0.1/mission.md +6 -0
  39. package/data/project-types/ev/event-planner/versions/1.0.1/pages/dashboard/index.html +186 -0
  40. package/data/project-types/fi/fitness-coach/versions/1.0.1/about.md +7 -0
  41. package/data/project-types/fi/fitness-coach/versions/1.0.1/manifest.json +125 -0
  42. package/data/project-types/fi/fitness-coach/versions/1.0.1/mission.md +7 -0
  43. package/data/project-types/fi/fitness-coach/versions/1.0.1/pages/dashboard/index.html +172 -0
  44. package/data/project-types/fi/fitness-coach/versions/1.0.1/training.json +6 -0
  45. package/data/project-types/fl/flashcards/versions/1.0.1/about.md +8 -0
  46. package/data/project-types/fl/flashcards/versions/1.0.1/deck.json +7 -0
  47. package/data/project-types/fl/flashcards/versions/1.0.1/manifest.json +167 -0
  48. package/data/project-types/fl/flashcards/versions/1.0.1/mission.md +6 -0
  49. package/data/project-types/fl/flashcards/versions/1.0.1/pages/review/index.html +396 -0
  50. package/data/project-types/fr/freelance-office/versions/1.0.1/about.md +6 -0
  51. package/data/project-types/fr/freelance-office/versions/1.0.1/invoices.json +9 -0
  52. package/data/project-types/fr/freelance-office/versions/1.0.1/manifest.json +142 -0
  53. package/data/project-types/fr/freelance-office/versions/1.0.1/mission.md +6 -0
  54. package/data/project-types/fr/freelance-office/versions/1.0.1/pages/dashboard/index.html +157 -0
  55. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/about.md +7 -0
  56. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/donations.json +7 -0
  57. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/manifest.json +126 -0
  58. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/mission.md +6 -0
  59. package/data/project-types/fu/fundraiser-hq/versions/1.0.1/pages/dashboard/index.html +160 -0
  60. package/data/project-types/go/go/versions/1.0.1/about.md +9 -0
  61. package/data/project-types/go/go/versions/1.0.1/game.json +103 -0
  62. package/data/project-types/go/go/versions/1.0.1/manifest.json +166 -0
  63. package/data/project-types/go/go/versions/1.0.1/mission.md +8 -0
  64. package/data/project-types/go/go/versions/1.0.1/pages/board/index.html +778 -0
  65. package/data/project-types/ho/household-budget/versions/1.0.1/about.md +6 -0
  66. package/data/project-types/ho/household-budget/versions/1.0.1/ledger.json +5 -0
  67. package/data/project-types/ho/household-budget/versions/1.0.1/manifest.json +163 -0
  68. package/data/project-types/ho/household-budget/versions/1.0.1/mission.md +6 -0
  69. package/data/project-types/ho/household-budget/versions/1.0.1/pages/dashboard/index.html +233 -0
  70. package/data/project-types/ho/household-manual/versions/1.0.1/about.md +7 -0
  71. package/data/project-types/ho/household-manual/versions/1.0.1/manifest.json +152 -0
  72. package/data/project-types/ho/household-manual/versions/1.0.1/mission.md +8 -0
  73. package/data/project-types/ho/household-manual/versions/1.0.1/pages/dashboard/index.html +157 -0
  74. package/data/project-types/ho/household-manual/versions/1.0.1/systems.json +8 -0
  75. package/data/project-types/index.json +1 -1
  76. package/data/project-types/jo/job-hunt/versions/1.0.1/about.md +10 -0
  77. package/data/project-types/jo/job-hunt/versions/1.0.1/activity.json +4 -0
  78. package/data/project-types/jo/job-hunt/versions/1.0.1/manifest.json +212 -0
  79. package/data/project-types/jo/job-hunt/versions/1.0.1/mission.md +9 -0
  80. package/data/project-types/jo/job-hunt/versions/1.0.1/pages/dashboard/index.html +327 -0
  81. package/data/project-types/jo/job-hunt/versions/1.0.1/pipeline.json +6 -0
  82. package/data/project-types/la/language-trainer/versions/1.0.1/about.md +6 -0
  83. package/data/project-types/la/language-trainer/versions/1.0.1/manifest.json +79 -0
  84. package/data/project-types/la/language-trainer/versions/1.0.1/mission.md +4 -0
  85. package/data/project-types/la/language-trainer/versions/1.0.1/pages/dashboard/index.html +153 -0
  86. package/data/project-types/la/language-trainer/versions/1.0.1/progress.json +7 -0
  87. package/data/project-types/li/life-binder/versions/1.0.1/about.md +7 -0
  88. package/data/project-types/li/life-binder/versions/1.0.1/documents.json +145 -0
  89. package/data/project-types/li/life-binder/versions/1.0.1/manifest.json +137 -0
  90. package/data/project-types/li/life-binder/versions/1.0.1/mission.md +7 -0
  91. package/data/project-types/li/life-binder/versions/1.0.1/pages/dashboard/index.html +147 -0
  92. package/data/project-types/me/meal-planner/versions/1.0.1/about.md +6 -0
  93. package/data/project-types/me/meal-planner/versions/1.0.1/manifest.json +159 -0
  94. package/data/project-types/me/meal-planner/versions/1.0.1/menu.json +5 -0
  95. package/data/project-types/me/meal-planner/versions/1.0.1/mission.md +5 -0
  96. package/data/project-types/me/meal-planner/versions/1.0.1/pages/dashboard/index.html +152 -0
  97. package/data/project-types/me/meal-planner/versions/1.0.1/pantry.json +9 -0
  98. package/data/project-types/no/novel-writing-room/versions/1.0.1/about.md +7 -0
  99. package/data/project-types/no/novel-writing-room/versions/1.0.1/chapters.json +10 -0
  100. package/data/project-types/no/novel-writing-room/versions/1.0.1/manifest.json +157 -0
  101. package/data/project-types/no/novel-writing-room/versions/1.0.1/mission.md +7 -0
  102. package/data/project-types/no/novel-writing-room/versions/1.0.1/pages/dashboard/index.html +165 -0
  103. package/data/project-types/no/novel-writing-room/versions/1.0.1/sessions.json +6 -0
  104. package/data/project-types/tr/trip-planner/versions/1.0.1/about.md +7 -0
  105. package/data/project-types/tr/trip-planner/versions/1.0.1/journal.json +4 -0
  106. package/data/project-types/tr/trip-planner/versions/1.0.1/manifest.json +193 -0
  107. package/data/project-types/tr/trip-planner/versions/1.0.1/mission.md +4 -0
  108. package/data/project-types/tr/trip-planner/versions/1.0.1/packing.json +8 -0
  109. package/data/project-types/tr/trip-planner/versions/1.0.1/pages/dashboard/index.html +215 -0
  110. package/data/project-types/tr/trip-planner/versions/1.0.1/trip.json +7 -0
  111. package/package.json +4 -2
  112. /package/data/chat-models/ge/{gemma4-e2b-q8 → gemma4-e2b-q4}/versions/1.1.0/manifest.json +0 -0
  113. /package/data/chat-models/ge/{gemma4-e2b-q8 → gemma4-e2b-q4}/versions/1.1.2/manifest.json +0 -0
  114. /package/data/chat-models/ge/{gemma4-e4b-q8 → gemma4-e4b-q4}/versions/1.1.0/manifest.json +0 -0
  115. /package/data/chat-models/ge/{gemma4-e4b-q8 → gemma4-e4b-q4}/versions/1.1.2/manifest.json +0 -0
@@ -0,0 +1,127 @@
1
+ {
2
+ "version": 1,
3
+ "squares": "rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR",
4
+ "turn": "user",
5
+ "status": "playing",
6
+ "castling": "KQkq",
7
+ "enPassant": null,
8
+ "halfmoveClock": 0,
9
+ "fullmoveNumber": 1,
10
+ "positionHistory": [
11
+ "rnbqkbnrpppppppp................................PPPPPPPPRNBQKBNR|user|KQkq|-"
12
+ ],
13
+ "legalMoves": [
14
+ {
15
+ "from": "a2",
16
+ "to": "a3",
17
+ "capture": false
18
+ },
19
+ {
20
+ "from": "a2",
21
+ "to": "a4",
22
+ "capture": false,
23
+ "doublePawn": true
24
+ },
25
+ {
26
+ "from": "b2",
27
+ "to": "b3",
28
+ "capture": false
29
+ },
30
+ {
31
+ "from": "b2",
32
+ "to": "b4",
33
+ "capture": false,
34
+ "doublePawn": true
35
+ },
36
+ {
37
+ "from": "c2",
38
+ "to": "c3",
39
+ "capture": false
40
+ },
41
+ {
42
+ "from": "c2",
43
+ "to": "c4",
44
+ "capture": false,
45
+ "doublePawn": true
46
+ },
47
+ {
48
+ "from": "d2",
49
+ "to": "d3",
50
+ "capture": false
51
+ },
52
+ {
53
+ "from": "d2",
54
+ "to": "d4",
55
+ "capture": false,
56
+ "doublePawn": true
57
+ },
58
+ {
59
+ "from": "e2",
60
+ "to": "e3",
61
+ "capture": false
62
+ },
63
+ {
64
+ "from": "e2",
65
+ "to": "e4",
66
+ "capture": false,
67
+ "doublePawn": true
68
+ },
69
+ {
70
+ "from": "f2",
71
+ "to": "f3",
72
+ "capture": false
73
+ },
74
+ {
75
+ "from": "f2",
76
+ "to": "f4",
77
+ "capture": false,
78
+ "doublePawn": true
79
+ },
80
+ {
81
+ "from": "g2",
82
+ "to": "g3",
83
+ "capture": false
84
+ },
85
+ {
86
+ "from": "g2",
87
+ "to": "g4",
88
+ "capture": false,
89
+ "doublePawn": true
90
+ },
91
+ {
92
+ "from": "h2",
93
+ "to": "h3",
94
+ "capture": false
95
+ },
96
+ {
97
+ "from": "h2",
98
+ "to": "h4",
99
+ "capture": false,
100
+ "doublePawn": true
101
+ },
102
+ {
103
+ "from": "b1",
104
+ "to": "a3",
105
+ "capture": false
106
+ },
107
+ {
108
+ "from": "b1",
109
+ "to": "c3",
110
+ "capture": false
111
+ },
112
+ {
113
+ "from": "g1",
114
+ "to": "f3",
115
+ "capture": false
116
+ },
117
+ {
118
+ "from": "g1",
119
+ "to": "h3",
120
+ "capture": false
121
+ }
122
+ ],
123
+ "moveLog": [],
124
+ "lastMove": null,
125
+ "lastMoveThought": "",
126
+ "updatedAt": "2026-07-28T00:00:00.000Z"
127
+ }
@@ -0,0 +1,180 @@
1
+ {
2
+ "schemaVersion": 1,
3
+ "version": "1.0.3",
4
+ "releasedAt": "2026-08-01T00: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 Schaakspeler 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": "Chess vs the Schaakspeler",
49
+ "aboutTemplate": "about.md",
50
+ "missionTemplate": "mission.md",
51
+ "gezels": [
52
+ {
53
+ "templateId": "chess-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 chess game (game.json). Call action:'board' to see the position and every legal move; action:'ai_move' to play your move (from/to, optional promotion and moveThought); action:'state' for a light summary; action:'new_game' to reset. Illegal moves fail with the engine's legal-move list.\",\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 from: { type: 'string', description: \"Origin square, e.g. 'e7'.\" },\n to: { type: 'string', description: \"Destination square, e.g. 'e5'.\" },\n promotion: {\n type: 'string',\n description: \"Promotion piece: 'q', 'r', 'b', or 'n'. Defaults to queen.\",\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' (white) or 'ai' (black).\" },\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. 'e2-e4'.\" },\n legalMoves: { type: 'string', description: 'Rendered legal moves for the side to move.' },\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// ── chess-rules-start ──\n// Pure, annotation-free chess engine. Squares are a 64-character row-major\n// string: y0 is rank 8, x0 is file a. Uppercase pieces are white (the user);\n// lowercase pieces are black (the AI).\n\nvar FILES = 'abcdefgh';\nvar PROMOTIONS = ['q', 'r', 'b', 'n'];\n\nfunction sqName(x, y) {\n return FILES[x] + String(8 - y);\n}\n\nfunction parseSq(name) {\n if (typeof name !== 'string' || name.length !== 2) return null;\n var x = FILES.indexOf(name[0].toLowerCase());\n var rank = Number(name[1]);\n if (x < 0 || !(rank >= 1 && rank <= 8)) return null;\n return { x: x, y: 8 - rank };\n}\n\nfunction inBounds(x, y) {\n return x >= 0 && x < 8 && y >= 0 && y < 8;\n}\n\nfunction pieceAt(squares, x, y) {\n return squares[y * 8 + x];\n}\n\nfunction withPiece(squares, x, y, ch) {\n var i = y * 8 + x;\n return squares.slice(0, i) + ch + squares.slice(i + 1);\n}\n\nfunction sideOf(ch) {\n if (!ch || ch === '.') return null;\n return ch === ch.toUpperCase() ? 'user' : 'ai';\n}\n\nfunction otherSide(side) {\n return side === 'user' ? 'ai' : 'user';\n}\n\nfunction kindOf(ch) {\n return ch && ch !== '.' ? ch.toLowerCase() : '';\n}\n\nfunction buildInitial() {\n return 'rnbqkbnr' + 'pppppppp' + '.'.repeat(32) + 'PPPPPPPP' + 'RNBQKBNR';\n}\n\nfunction findKing(squares, side) {\n var needle = side === 'user' ? 'K' : 'k';\n var i = squares.indexOf(needle);\n return i < 0 ? null : { x: i % 8, y: Math.floor(i / 8) };\n}\n\nfunction isAttacked(squares, x, y, bySide) {\n var pawn = bySide === 'user' ? 'P' : 'p';\n var pawnY = y - (bySide === 'user' ? -1 : 1);\n for (var pdx of [-1, 1]) {\n var px = x - pdx;\n if (inBounds(px, pawnY) && pieceAt(squares, px, pawnY) === pawn) return true;\n }\n\n var knight = bySide === 'user' ? 'N' : 'n';\n var knightSteps = [\n [-2, -1],\n [-2, 1],\n [-1, -2],\n [-1, 2],\n [1, -2],\n [1, 2],\n [2, -1],\n [2, 1],\n ];\n for (var n of knightSteps) {\n if (inBounds(x + n[0], y + n[1]) && pieceAt(squares, x + n[0], y + n[1]) === knight) {\n return true;\n }\n }\n\n var rays = [\n [-1, 0, 'rq'],\n [1, 0, 'rq'],\n [0, -1, 'rq'],\n [0, 1, 'rq'],\n [-1, -1, 'bq'],\n [-1, 1, 'bq'],\n [1, -1, 'bq'],\n [1, 1, 'bq'],\n ];\n for (var ray of rays) {\n var rx = x + ray[0];\n var ry = y + ray[1];\n while (inBounds(rx, ry)) {\n var seen = pieceAt(squares, rx, ry);\n if (seen !== '.') {\n if (sideOf(seen) === bySide && ray[2].indexOf(kindOf(seen)) >= 0) return true;\n break;\n }\n rx += ray[0];\n ry += ray[1];\n }\n }\n\n var king = bySide === 'user' ? 'K' : 'k';\n for (var ky = -1; ky <= 1; ky += 1) {\n for (var kx = -1; kx <= 1; kx += 1) {\n if (\n (kx !== 0 || ky !== 0) &&\n inBounds(x + kx, y + ky) &&\n pieceAt(squares, x + kx, y + ky) === king\n ) {\n return true;\n }\n }\n }\n return false;\n}\n\nfunction inCheck(squares, side) {\n var king = findKing(squares, side);\n return !king || isAttacked(squares, king.x, king.y, otherSide(side));\n}\n\nfunction pushMove(out, fromX, fromY, toX, toY, extra) {\n out.push(\n Object.assign(\n {\n from: sqName(fromX, fromY),\n to: sqName(toX, toY),\n capture: false,\n },\n extra || {},\n ),\n );\n}\n\nfunction pseudoMovesFor(squares, side, castling, enPassant) {\n var out = [];\n for (var y = 0; y < 8; y += 1) {\n for (var x = 0; x < 8; x += 1) {\n var ch = pieceAt(squares, x, y);\n if (sideOf(ch) !== side) continue;\n var kind = kindOf(ch);\n\n if (kind === 'p') {\n var dir = side === 'user' ? -1 : 1;\n var startY = side === 'user' ? 6 : 1;\n var promotionY = side === 'user' ? 0 : 7;\n var oneY = y + dir;\n if (inBounds(x, oneY) && pieceAt(squares, x, oneY) === '.') {\n if (oneY === promotionY) {\n for (var promotion of PROMOTIONS) {\n pushMove(out, x, y, x, oneY, { promotion: promotion });\n }\n } else {\n pushMove(out, x, y, x, oneY);\n var twoY = y + dir * 2;\n if (y === startY && pieceAt(squares, x, twoY) === '.') {\n pushMove(out, x, y, x, twoY, { doublePawn: true });\n }\n }\n }\n for (var dx of [-1, 1]) {\n var cx = x + dx;\n var cy = y + dir;\n if (!inBounds(cx, cy)) continue;\n var target = pieceAt(squares, cx, cy);\n var targetName = sqName(cx, cy);\n var isEp = targetName === enPassant && target === '.';\n if ((target !== '.' && sideOf(target) === otherSide(side)) || isEp) {\n if (cy === promotionY) {\n for (var capturePromotion of PROMOTIONS) {\n pushMove(out, x, y, cx, cy, {\n capture: true,\n promotion: capturePromotion,\n enPassant: isEp,\n });\n }\n } else {\n pushMove(out, x, y, cx, cy, { capture: true, enPassant: isEp });\n }\n }\n }\n continue;\n }\n\n if (kind === 'n') {\n var jumps = [\n [-2, -1],\n [-2, 1],\n [-1, -2],\n [-1, 2],\n [1, -2],\n [1, 2],\n [2, -1],\n [2, 1],\n ];\n for (var jump of jumps) {\n var nx = x + jump[0];\n var ny = y + jump[1];\n if (!inBounds(nx, ny) || sideOf(pieceAt(squares, nx, ny)) === side) continue;\n pushMove(out, x, y, nx, ny, { capture: pieceAt(squares, nx, ny) !== '.' });\n }\n continue;\n }\n\n if (kind === 'b' || kind === 'r' || kind === 'q') {\n var dirs = [];\n if (kind === 'b' || kind === 'q') dirs.push([-1, -1], [-1, 1], [1, -1], [1, 1]);\n if (kind === 'r' || kind === 'q') dirs.push([-1, 0], [1, 0], [0, -1], [0, 1]);\n for (var d of dirs) {\n var sx = x + d[0];\n var sy = y + d[1];\n while (inBounds(sx, sy)) {\n var occupant = pieceAt(squares, sx, sy);\n if (sideOf(occupant) === side) break;\n pushMove(out, x, y, sx, sy, { capture: occupant !== '.' });\n if (occupant !== '.') break;\n sx += d[0];\n sy += d[1];\n }\n }\n continue;\n }\n\n if (kind === 'k') {\n for (var kdy = -1; kdy <= 1; kdy += 1) {\n for (var kdx = -1; kdx <= 1; kdx += 1) {\n if (kdx === 0 && kdy === 0) continue;\n var ktx = x + kdx;\n var kty = y + kdy;\n if (!inBounds(ktx, kty) || sideOf(pieceAt(squares, ktx, kty)) === side) continue;\n pushMove(out, x, y, ktx, kty, { capture: pieceAt(squares, ktx, kty) !== '.' });\n }\n }\n\n var homeY = side === 'user' ? 7 : 0;\n var kingChar = side === 'user' ? 'K' : 'k';\n var rookChar = side === 'user' ? 'R' : 'r';\n var enemy = otherSide(side);\n if (x === 4 && y === homeY && ch === kingChar && !isAttacked(squares, 4, homeY, enemy)) {\n var kingRight = side === 'user' ? castling.indexOf('K') >= 0 : castling.indexOf('k') >= 0;\n if (\n kingRight &&\n pieceAt(squares, 7, homeY) === rookChar &&\n pieceAt(squares, 5, homeY) === '.' &&\n pieceAt(squares, 6, homeY) === '.' &&\n !isAttacked(squares, 5, homeY, enemy) &&\n !isAttacked(squares, 6, homeY, enemy)\n ) {\n pushMove(out, x, y, 6, homeY, { castle: 'king' });\n }\n var queenRight = side === 'user' ? castling.indexOf('Q') >= 0 : castling.indexOf('q') >= 0;\n if (\n queenRight &&\n pieceAt(squares, 0, homeY) === rookChar &&\n pieceAt(squares, 1, homeY) === '.' &&\n pieceAt(squares, 2, homeY) === '.' &&\n pieceAt(squares, 3, homeY) === '.' &&\n !isAttacked(squares, 3, homeY, enemy) &&\n !isAttacked(squares, 2, homeY, enemy)\n ) {\n pushMove(out, x, y, 2, homeY, { castle: 'queen' });\n }\n }\n }\n }\n }\n return out;\n}\n\nfunction applyUnchecked(squares, move, side) {\n var from = parseSq(move.from);\n var to = parseSq(move.to);\n var ch = pieceAt(squares, from.x, from.y);\n var next = withPiece(squares, from.x, from.y, '.');\n if (move.enPassant) {\n next = withPiece(next, to.x, from.y, '.');\n }\n if (move.castle === 'king') {\n next = withPiece(next, 7, from.y, '.');\n next = withPiece(next, 5, from.y, side === 'user' ? 'R' : 'r');\n } else if (move.castle === 'queen') {\n next = withPiece(next, 0, from.y, '.');\n next = withPiece(next, 3, from.y, side === 'user' ? 'R' : 'r');\n }\n if (move.promotion) ch = side === 'user' ? move.promotion.toUpperCase() : move.promotion;\n return withPiece(next, to.x, to.y, ch);\n}\n\nfunction legalMovesFor(squares, side, castling, enPassant) {\n return pseudoMovesFor(squares, side, castling || '', enPassant || null).filter(function (move) {\n return !inCheck(applyUnchecked(squares, move, side), side);\n });\n}\n\nfunction nextCastling(castling, squares, move) {\n var rights = castling || '';\n var ch = pieceAt(squares, parseSq(move.from).x, parseSq(move.from).y);\n if (ch === 'K') rights = rights.replace(/K|Q/g, '');\n if (ch === 'k') rights = rights.replace(/k|q/g, '');\n if (move.from === 'a1' || move.to === 'a1') rights = rights.replace('Q', '');\n if (move.from === 'h1' || move.to === 'h1') rights = rights.replace('K', '');\n if (move.from === 'a8' || move.to === 'a8') rights = rights.replace('q', '');\n if (move.from === 'h8' || move.to === 'h8') rights = rights.replace('k', '');\n return rights;\n}\n\nfunction nextEnPassant(move, side) {\n if (!move.doublePawn) return null;\n var from = parseSq(move.from);\n var to = parseSq(move.to);\n return sqName(from.x, (from.y + to.y) / 2);\n}\n\nfunction isInsufficientMaterial(squares) {\n var pieces = [];\n for (var i = 0; i < squares.length; i += 1) {\n var ch = squares[i];\n if (ch !== '.' && kindOf(ch) !== 'k') pieces.push({ ch: ch, x: i % 8, y: Math.floor(i / 8) });\n }\n if (pieces.length === 0) return true;\n if (pieces.length === 1 && (kindOf(pieces[0].ch) === 'b' || kindOf(pieces[0].ch) === 'n')) {\n return true;\n }\n if (\n pieces.length > 0 &&\n pieces.every(function (p) {\n return kindOf(p.ch) === 'b';\n })\n ) {\n var color = (pieces[0].x + pieces[0].y) % 2;\n return pieces.every(function (p) {\n return (p.x + p.y) % 2 === color;\n });\n }\n return false;\n}\n\nfunction positionKey(squares, side, castling, enPassant) {\n var effectiveEnPassant =\n enPassant &&\n legalMovesFor(squares, side, castling, enPassant).some(function (move) {\n return move.enPassant;\n })\n ? enPassant\n : '-';\n return [squares, side, castling || '-', effectiveEnPassant].join('|');\n}\n\nfunction statusAfter(squares, sideToMove, castling, enPassant) {\n if (isInsufficientMaterial(squares)) return 'draw';\n var moves = legalMovesFor(squares, sideToMove, castling, enPassant);\n if (moves.length > 0) return 'playing';\n if (!inCheck(squares, sideToMove)) return 'draw';\n return sideToMove === 'user' ? 'ai_won' : 'user_won';\n}\n\nfunction describeMove(move) {\n if (move.castle === 'king') return move.from + '-' + move.to + ' (O-O)';\n if (move.castle === 'queen') return move.from + '-' + move.to + ' (O-O-O)';\n var text = move.from + (move.capture ? 'x' : '-') + move.to;\n if (move.promotion) text += '=' + move.promotion.toUpperCase();\n if (move.enPassant) text += ' e.p.';\n return text;\n}\n\nfunction renderAscii(squares) {\n var lines = [' a b c d e f g h'];\n for (var y = 0; y < 8; y += 1) {\n var row = String(8 - y);\n for (var x = 0; x < 8; x += 1) row += ' ' + pieceAt(squares, x, y);\n lines.push(row + ' ' + String(8 - y));\n }\n lines.push(' a b c d e f g h');\n lines.push('Uppercase = white (user). Lowercase = black (you).');\n return lines.join('\\n');\n}\n// ── chess-rules-end ──\n\nconst STATE_FILE = 'game.json';\nconst SIDE_NAMES = { user: 'white', ai: 'black' };\n\ninterface ChessMove {\n from: string;\n to: string;\n capture: boolean;\n promotion?: string;\n doublePawn?: boolean;\n enPassant?: boolean;\n castle?: string;\n}\n\ninterface GameState {\n version: number;\n squares: string;\n turn: 'user' | 'ai';\n status: 'playing' | 'user_won' | 'ai_won' | 'draw';\n castling: string;\n enPassant: string | null;\n halfmoveClock: number;\n fullmoveNumber: number;\n positionHistory: string[];\n legalMoves: ChessMove[];\n moveLog: Array<{\n by: 'user' | 'ai';\n from: string;\n to: string;\n promotion?: string;\n notation: string;\n at: string;\n moveThought?: string;\n }>;\n lastMove: {\n by: 'user' | 'ai';\n from: string;\n to: string;\n promotion?: string;\n notation: string;\n } | null;\n lastMoveThought: string;\n updatedAt: string;\n}\n\nfunction freshState(): GameState {\n const squares = buildInitial();\n const castling = 'KQkq';\n return {\n version: 1,\n squares,\n turn: 'user',\n status: 'playing',\n castling,\n enPassant: null,\n halfmoveClock: 0,\n fullmoveNumber: 1,\n positionHistory: [positionKey(squares, 'user', castling, null)],\n legalMoves: legalMovesFor(squares, 'user', castling, 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 if (typeof parsed.squares !== 'string' || parsed.squares.length !== 64) {\n throw new Error(`${STATE_FILE} is corrupt; call action:'new_game' to reset.`);\n }\n return {\n ...freshState(),\n ...parsed,\n castling: typeof parsed.castling === 'string' ? parsed.castling : '',\n enPassant: parsed.enPassant ?? null,\n } as GameState;\n}\n\nasync function persist(state: GameState): Promise<GameState> {\n state.legalMoves =\n state.status === 'playing'\n ? legalMovesFor(state.squares, state.turn, state.castling, state.enPassant)\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.map(describeMove).join(', ') || '(none)';\n}\n\nfunction summarize(state: GameState): string {\n if (state.status === 'user_won') return 'Checkmate — the user (white) won.';\n if (state.status === 'ai_won') return 'Checkmate — you (black) won.';\n if (state.status === 'draw') return 'Game over — draw.';\n const check = inCheck(state.squares, state.turn) ? ' The king is in check.' : '';\n return `Move ${state.fullmoveNumber}: ${SIDE_NAMES[state.turn]} (${state.turn}) to move.${check}`;\n}\n\nfunction applyTurn(\n state: GameState,\n by: 'user' | 'ai',\n moveThought?: string,\n): { state: GameState; move: ChessMove } {\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 if (!input.from || !input.to) throw new Error(\"Pass 'from' and 'to' squares.\");\n const wantedPromotion = (input.promotion || 'q').toLowerCase();\n const candidates = state.legalMoves.filter(\n (move) =>\n move.from === input.from!.toLowerCase() &&\n move.to === input.to!.toLowerCase(),\n );\n const move =\n candidates.find((candidate) => (candidate.promotion || 'q') === wantedPromotion) ??\n candidates.find((candidate) => !candidate.promotion);\n if (!move) {\n throw new Error(\n `Illegal move ${input.from}-${input.to}. Legal moves for ${SIDE_NAMES[by]}: ${renderLegal(state)}`,\n );\n }\n\n const movingPiece = pieceAt(\n state.squares,\n parseSq(move.from)!.x,\n parseSq(move.from)!.y,\n );\n const oldSquares = state.squares;\n state.castling = nextCastling(state.castling, oldSquares, move);\n state.squares = applyUnchecked(oldSquares, move, by);\n state.enPassant = nextEnPassant(move, by);\n state.halfmoveClock =\n kindOf(movingPiece) === 'p' || move.capture ? 0 : state.halfmoveClock + 1;\n if (by === 'ai') state.fullmoveNumber += 1;\n state.turn = otherSide(by) as 'user' | 'ai';\n const key = positionKey(state.squares, state.turn, state.castling, state.enPassant);\n state.positionHistory.push(key);\n state.status = statusAfter(\n state.squares,\n state.turn,\n state.castling,\n state.enPassant,\n ) as GameState['status'];\n if (\n state.status === 'playing' &&\n (state.halfmoveClock >= 100 ||\n state.positionHistory.filter((candidate) => candidate === key).length >= 3)\n ) {\n state.status = 'draw';\n }\n\n const notation = describeMove(move);\n const entry = {\n by,\n from: move.from,\n to: move.to,\n ...(move.promotion ? { promotion: move.promotion } : {}),\n notation,\n at: new Date().toISOString(),\n ...(moveThought ? { moveThought } : {}),\n };\n state.moveLog.push(entry);\n state.lastMove = {\n by,\n from: move.from,\n to: move.to,\n ...(move.promotion ? { promotion: move.promotion } : {}),\n notation,\n };\n if (by === 'ai' && moveThought) state.lastMoveThought = moveThought;\n return { state, move };\n}\n\nlet state = await load();\nlet lastMoveText = state.lastMove?.notation ?? '(none)';\n\nif (input.action === 'new_game') {\n state = await persist(freshState());\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 const applied = applyTurn(state, by, moveThought);\n state = await persist(applied.state);\n lastMoveText = describeMove(applied.move);\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)}\\nTurn: ${SIDE_NAMES[state.turn]} (${state.turn}).` +\n `${inCheck(state.squares, state.turn) && state.status === 'playing' ? ' CHECK.' : ''}\\n` +\n `Legal moves: ${renderLegal(state)}`,\n turn: state.turn,\n status: state.status,\n lastMove: lastMoveText,\n legalMoves: renderLegal(state),\n summary: summarize(state),\n});\n"
59
+ },
60
+ "tools": [
61
+ {
62
+ "name": "get_board",
63
+ "description": "See the authoritative chess position, 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 black move. Pass from/to; for promotion optionally choose 'q', 'r', 'b', or 'n'. Include a short moveThought for the board.",
76
+ "script": "game-store",
77
+ "inputs": {
78
+ "type": "object",
79
+ "properties": {
80
+ "from": {
81
+ "type": "string",
82
+ "description": "Origin square, e.g. 'e7'."
83
+ },
84
+ "to": {
85
+ "type": "string",
86
+ "description": "Destination square, e.g. 'e5'."
87
+ },
88
+ "promotion": {
89
+ "type": "string",
90
+ "enum": [
91
+ "q",
92
+ "r",
93
+ "b",
94
+ "n"
95
+ ],
96
+ "description": "Promotion piece. Defaults to queen."
97
+ },
98
+ "moveThought": {
99
+ "type": "string",
100
+ "description": "A short line of table talk (max 140 chars)."
101
+ }
102
+ },
103
+ "required": [
104
+ "from",
105
+ "to"
106
+ ]
107
+ },
108
+ "bind": {
109
+ "action": "ai_move"
110
+ }
111
+ },
112
+ {
113
+ "name": "user_move",
114
+ "description": "The user's white move, played from the chess board.",
115
+ "script": "game-store",
116
+ "inputs": {
117
+ "type": "object",
118
+ "properties": {
119
+ "from": {
120
+ "type": "string"
121
+ },
122
+ "to": {
123
+ "type": "string"
124
+ },
125
+ "promotion": {
126
+ "type": "string",
127
+ "enum": [
128
+ "q",
129
+ "r",
130
+ "b",
131
+ "n"
132
+ ]
133
+ }
134
+ },
135
+ "required": [
136
+ "from",
137
+ "to"
138
+ ]
139
+ },
140
+ "bind": {
141
+ "action": "user_move"
142
+ },
143
+ "reaction": {
144
+ "gezel": "chess-player",
145
+ "hideSeed": true,
146
+ "prompt": "Your opponent played {{output.lastMove}}. Status: {{output.status}}. Board now:\n{{output.board}}\nThe board and Legal moves line come directly from the chess engine and are always correct. Do not re-derive the position, dispute a listed move, or invent a move outside that list. Spend your thought on choosing among the listed legal moves.\nStyle: {{playStyle}}. In Opponent style, do not volunteer advice or reveal the user’s available moves, threats, tactics, 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 and offer a new game. Otherwise choose a strong legal move and play it with make_move, including a short {{personality}} moveThought that follows the selected style. 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."
147
+ }
148
+ },
149
+ {
150
+ "name": "new_game",
151
+ "description": "Reset the chess board to the standard opening position.",
152
+ "script": "game-store",
153
+ "inputs": {
154
+ "type": "object",
155
+ "properties": {}
156
+ },
157
+ "bind": {
158
+ "action": "new_game"
159
+ }
160
+ }
161
+ ],
162
+ "craftbooks": [],
163
+ "schedules": [],
164
+ "pages": {
165
+ "entry": "board/index.html",
166
+ "reads": [
167
+ {
168
+ "source": "workspace",
169
+ "path": "game.json"
170
+ }
171
+ ],
172
+ "tools": [
173
+ "user_move",
174
+ "new_game"
175
+ ]
176
+ },
177
+ "workspaceSeed": [
178
+ "game.json"
179
+ ]
180
+ }
@@ -0,0 +1,8 @@
1
+ - Keep a standard chess position ready, with white moving first and a rematch one
2
+ press away.
3
+ - The Schaakspeler answers every user move with a legal black move of their own.
4
+ - Let the engine settle check, mate, stalemate, promotion, castling, en passant,
5
+ repetition, and fifty-move draws.
6
+ - Keep table talk {{personality}} and kind, matching the chosen {{playStyle}}:
7
+ Opponents do not leak plans or hints; Instructors teach lightly.
8
+ - End wins, losses, and draws graciously, with an easy offer of another game.