@bendyline/gilde 0.1.7 → 0.1.9
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.
- package/README.md +19 -0
- package/data/chat-models/de/deepseek-v4-flash-284b-mxfp4/manifest.json +119 -0
- package/data/chat-models/de/deepseek-v4-flash-284b-mxfp4/versions/1.0.0/manifest.json +25 -0
- package/data/chat-models/de/deepseek-v4-flash-284b-q2/manifest.json +58 -12
- package/data/chat-models/de/deepseek-v4-flash-284b-q2/versions/1.1.0/manifest.json +25 -0
- package/data/chat-models/de/deepseek-v4-flash-284b-q2q4/manifest.json +120 -0
- package/data/chat-models/de/deepseek-v4-flash-284b-q2q4/versions/1.0.0/manifest.json +25 -0
- package/data/chat-models/de/deepseek-v4-flash-284b-q4/manifest.json +53 -7
- package/data/chat-models/de/deepseek-v4-flash-284b-q4/versions/1.1.0/manifest.json +223 -0
- package/data/chat-models/ge/gemma4-e4b-q4/manifest.json +7 -1
- package/data/chat-models/index.json +1 -1
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/about.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/care.json +4 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/manifest.json +177 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/meds.json +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/mission.md +8 -0
- package/data/project-types/ca/caregiving-binder/versions/1.0.1/pages/dashboard/index.html +167 -0
- package/data/project-types/ch/checkers/versions/1.1.2/about.md +9 -0
- package/data/project-types/ch/checkers/versions/1.1.2/game.json +77 -0
- package/data/project-types/ch/checkers/versions/1.1.2/manifest.json +166 -0
- package/data/project-types/ch/checkers/versions/1.1.2/mission.md +9 -0
- package/data/project-types/ch/checkers/versions/1.1.2/pages/board/index.html +1091 -0
- package/data/project-types/ch/chess/versions/1.0.2/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.2/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.2/manifest.json +180 -0
- package/data/project-types/ch/chess/versions/1.0.2/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.2/pages/board/index.html +627 -0
- package/data/project-types/ch/chess/versions/1.0.3/about.md +9 -0
- package/data/project-types/ch/chess/versions/1.0.3/game.json +127 -0
- package/data/project-types/ch/chess/versions/1.0.3/manifest.json +180 -0
- package/data/project-types/ch/chess/versions/1.0.3/mission.md +8 -0
- package/data/project-types/ch/chess/versions/1.0.3/pages/board/index.html +769 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/about.md +6 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/manifest.json +69 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/mission.md +3 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/pages/gallery/index.html +105 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/schemes/index.json +9 -0
- package/data/project-types/de/design-scheme/versions/1.0.1/schemes/warm-dusk.json +4 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/about.md +7 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/budget.json +5 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/guests.json +10 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/manifest.json +170 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/mission.md +6 -0
- package/data/project-types/ev/event-planner/versions/1.0.1/pages/dashboard/index.html +186 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/about.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/manifest.json +125 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/mission.md +7 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/pages/dashboard/index.html +172 -0
- package/data/project-types/fi/fitness-coach/versions/1.0.1/training.json +6 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/about.md +8 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/deck.json +7 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/manifest.json +167 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fl/flashcards/versions/1.0.1/pages/review/index.html +396 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/about.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/invoices.json +9 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/manifest.json +142 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fr/freelance-office/versions/1.0.1/pages/dashboard/index.html +157 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/about.md +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/donations.json +7 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/manifest.json +126 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/mission.md +6 -0
- package/data/project-types/fu/fundraiser-hq/versions/1.0.1/pages/dashboard/index.html +160 -0
- package/data/project-types/go/go/versions/1.0.1/about.md +9 -0
- package/data/project-types/go/go/versions/1.0.1/game.json +103 -0
- package/data/project-types/go/go/versions/1.0.1/manifest.json +166 -0
- package/data/project-types/go/go/versions/1.0.1/mission.md +8 -0
- package/data/project-types/go/go/versions/1.0.1/pages/board/index.html +778 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/about.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/ledger.json +5 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/manifest.json +163 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/mission.md +6 -0
- package/data/project-types/ho/household-budget/versions/1.0.1/pages/dashboard/index.html +233 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/about.md +7 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/manifest.json +152 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/mission.md +8 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/pages/dashboard/index.html +157 -0
- package/data/project-types/ho/household-manual/versions/1.0.1/systems.json +8 -0
- package/data/project-types/index.json +1 -1
- package/data/project-types/jo/job-hunt/versions/1.0.1/about.md +10 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/activity.json +4 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/manifest.json +212 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/mission.md +9 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/pages/dashboard/index.html +327 -0
- package/data/project-types/jo/job-hunt/versions/1.0.1/pipeline.json +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/about.md +6 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/manifest.json +79 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/mission.md +4 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/pages/dashboard/index.html +153 -0
- package/data/project-types/la/language-trainer/versions/1.0.1/progress.json +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/about.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/documents.json +145 -0
- package/data/project-types/li/life-binder/versions/1.0.1/manifest.json +137 -0
- package/data/project-types/li/life-binder/versions/1.0.1/mission.md +7 -0
- package/data/project-types/li/life-binder/versions/1.0.1/pages/dashboard/index.html +147 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/about.md +6 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/manifest.json +159 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/menu.json +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/mission.md +5 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/pages/dashboard/index.html +152 -0
- package/data/project-types/me/meal-planner/versions/1.0.1/pantry.json +9 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/about.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/chapters.json +10 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/manifest.json +157 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/mission.md +7 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/pages/dashboard/index.html +165 -0
- package/data/project-types/no/novel-writing-room/versions/1.0.1/sessions.json +6 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/about.md +7 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/journal.json +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/manifest.json +193 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/mission.md +4 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/packing.json +8 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/pages/dashboard/index.html +215 -0
- package/data/project-types/tr/trip-planner/versions/1.0.1/trip.json +7 -0
- package/package.json +5 -2
|
@@ -0,0 +1,147 @@
|
|
|
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>Life binder</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
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
|
|
17
|
+
--accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
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; }
|
|
22
|
+
main { max-width: 620px; margin: 0 auto; }
|
|
23
|
+
h1 { font-family: Georgia, 'PT Serif', 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; margin: 0 0 0.15rem; }
|
|
24
|
+
.sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.9rem; }
|
|
25
|
+
.meter { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; margin-bottom: 0.75rem; }
|
|
26
|
+
.meter .line { display: flex; justify-content: space-between; font-size: 0.85rem; margin-bottom: 0.4rem; }
|
|
27
|
+
.bar { background: var(--track); border-radius: 4px; height: 10px; overflow: hidden; }
|
|
28
|
+
.bar > span { display: block; height: 100%; background: var(--good); border-radius: 4px; transition: width 0.3s ease; }
|
|
29
|
+
.list { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.5rem 0; }
|
|
30
|
+
.row { display: grid; grid-template-columns: 1.4rem 1fr auto; gap: 0.6rem; align-items: baseline; padding: 0.45rem 1rem; font-size: 0.9rem; }
|
|
31
|
+
.row + .row { border-top: 1px solid var(--track); }
|
|
32
|
+
.mark { font-weight: 700; text-align: center; }
|
|
33
|
+
.row.present .mark { color: var(--good); }
|
|
34
|
+
.row.missing .mark { color: var(--warn); }
|
|
35
|
+
.row .where { color: var(--muted); font-size: 0.78rem; }
|
|
36
|
+
.chip { display: inline-block; border: 1px solid var(--warn); color: var(--warn); border-radius: 4px; padding: 0 0.3rem; font-size: 0.7rem; line-height: 1.4; }
|
|
37
|
+
.privacy { color: var(--muted); font-size: 0.78rem; text-align: center; margin-top: 0.75rem; }
|
|
38
|
+
+ .demo-banner {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: space-between;
|
|
42
|
+
gap: 0.75rem;
|
|
43
|
+
margin: 0 0 0.85rem;
|
|
44
|
+
padding: 0.5rem 0.65rem;
|
|
45
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
background: color-mix(in srgb, var(--card, var(--surface, #fffdf8)) 88%, var(--accent, #8a5a2b));
|
|
48
|
+
color: var(--ink, #2b2620);
|
|
49
|
+
font-size: 0.8rem;
|
|
50
|
+
}
|
|
51
|
+
.demo-banner[hidden] { display: none; }
|
|
52
|
+
.demo-banner strong { font-weight: 700; }
|
|
53
|
+
.demo-reset {
|
|
54
|
+
flex: none;
|
|
55
|
+
padding: 0.25rem 0.5rem;
|
|
56
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
background: var(--card, var(--surface, #fffdf8));
|
|
59
|
+
color: var(--ink, #2b2620);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
font: inherit;
|
|
62
|
+
}
|
|
63
|
+
.demo-reset:hover { border-color: var(--ink, #2b2620); }
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
<main>
|
|
68
|
+
+ <div class="demo-banner" data-gezel-demo-banner hidden>
|
|
69
|
+
<span><strong>Demo mode</strong> — sample data stays in this page.</span>
|
|
70
|
+
<button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
|
|
71
|
+
</div>
|
|
72
|
+
<h1>Life binder</h1>
|
|
73
|
+
<p class="sub" id="sub">Loading the checklist…</p>
|
|
74
|
+
<div class="meter">
|
|
75
|
+
<div class="line"><span id="m-label">—</span><span id="m-count">—</span></div>
|
|
76
|
+
<div class="bar"><span id="m-bar" style="width: 0%"></span></div>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="list" id="list"></div>
|
|
79
|
+
<p class="privacy">Locations, never contents. Everything stays on this machine.</p>
|
|
80
|
+
</main>
|
|
81
|
+
<script>
|
|
82
|
+
(function () {
|
|
83
|
+
'use strict';
|
|
84
|
+
var parts = location.pathname.split('/').filter(Boolean);
|
|
85
|
+
var typeIdx = parts.indexOf('type');
|
|
86
|
+
var capability = typeIdx > 0 ? parts[typeIdx - 1] : null;
|
|
87
|
+
var projectId = typeIdx >= 0 ? parts[typeIdx + 1] : null;
|
|
88
|
+
var failedPolls = 0;
|
|
89
|
+
var YEAR = 365 * 24 * 60 * 60 * 1000;
|
|
90
|
+
var demoMode = !capability || !projectId;
|
|
91
|
+
var demoBinder = { records: [
|
|
92
|
+
{ id: 'doc-1', status: 'present', updatedAt: new Date(Date.now() - 40 * 86400000).toISOString(), fields: { name: 'Will', location: 'Blue fire box', reviewedAt: new Date(Date.now() - 40 * 86400000).toISOString() } },
|
|
93
|
+
{ id: 'doc-2', status: 'present', updatedAt: new Date(Date.now() - 420 * 86400000).toISOString(), fields: { name: 'Home insurance', location: 'Blue fire box', reviewedAt: new Date(Date.now() - 420 * 86400000).toISOString() } },
|
|
94
|
+
{ id: 'doc-3', status: 'missing', updatedAt: new Date().toISOString(), fields: { name: 'Emergency contacts' } },
|
|
95
|
+
{ id: 'doc-4', status: 'present', updatedAt: new Date(Date.now() - 20 * 86400000).toISOString(), fields: { name: 'Passport copies', location: 'Travel folder' } },
|
|
96
|
+
{ id: 'doc-5', status: 'missing', updatedAt: new Date().toISOString(), fields: { name: 'Pet care instructions' } },
|
|
97
|
+
] };
|
|
98
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
99
|
+
|
|
100
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
101
|
+
|
|
102
|
+
function render(binder) {
|
|
103
|
+
var records = Array.isArray(binder.records) ? binder.records : [];
|
|
104
|
+
var present = records.filter(function (r) { return r.status === 'present'; });
|
|
105
|
+
var now = Date.now();
|
|
106
|
+
document.getElementById('sub').textContent =
|
|
107
|
+
present.length === records.length && records.length > 0
|
|
108
|
+
? 'Everything in place. Well done.'
|
|
109
|
+
: (records.length - present.length) + ' gap(s) to close.';
|
|
110
|
+
document.getElementById('m-label').textContent = 'In place';
|
|
111
|
+
document.getElementById('m-count').textContent = present.length + ' of ' + records.length;
|
|
112
|
+
document.getElementById('m-bar').style.width =
|
|
113
|
+
(records.length ? Math.round((present.length / records.length) * 100) : 0) + '%';
|
|
114
|
+
|
|
115
|
+
document.getElementById('list').innerHTML = records
|
|
116
|
+
.slice()
|
|
117
|
+
.sort(function (a, b) { return a.status === b.status ? 0 : a.status === 'missing' ? -1 : 1; })
|
|
118
|
+
.map(function (r) {
|
|
119
|
+
var f = r.fields || {};
|
|
120
|
+
var reviewed = typeof f.reviewedAt === 'string' ? Date.parse(f.reviewedAt) : Date.parse(r.updatedAt);
|
|
121
|
+
var stale = r.status === 'present' && isFinite(reviewed) && now - reviewed > YEAR;
|
|
122
|
+
return (
|
|
123
|
+
'<div class="row ' + r.status + '"><span class="mark">' + (r.status === 'present' ? '+' : '-') + '</span>' +
|
|
124
|
+
'<span>' + esc(f.name || r.id) +
|
|
125
|
+
(f.location ? '<span class="where"> — ' + esc(f.location) + '</span>' : '') + '</span>' +
|
|
126
|
+
'<span>' + (stale ? '<span class="chip">review due</span>' : '') + '</span></div>'
|
|
127
|
+
);
|
|
128
|
+
})
|
|
129
|
+
.join('');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function poll() {
|
|
133
|
+
if (demoMode) { render(demoBinder); return Promise.resolve(); }
|
|
134
|
+
if (!capability || !projectId) return Promise.resolve();
|
|
135
|
+
return fetch('/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/documents.json', { cache: 'no-store' })
|
|
136
|
+
.then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); })
|
|
137
|
+
.then(function (next) { failedPolls = 0; render(next); })
|
|
138
|
+
.catch(function () {
|
|
139
|
+
failedPolls += 1;
|
|
140
|
+
if (failedPolls === 3 && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
poll().then(function () { setInterval(function () { void poll(); }, 6000); });
|
|
144
|
+
})();
|
|
145
|
+
</script>
|
|
146
|
+
</body>
|
|
147
|
+
</html>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
The weekly answer to "what's for dinner?" — planned once, for {{household}}.
|
|
2
|
+
Your Maaltijdplanner builds the week's menu from what's in the pantry, learns
|
|
3
|
+
what the table actually likes, and keeps the grocery run short.
|
|
4
|
+
|
|
5
|
+
The **Output** tab shows the week at a glance: every dinner, tonight
|
|
6
|
+
highlighted, and the grocery gap.
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
{
|
|
2
|
+
"schemaVersion": 1,
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"releasedAt": "2026-08-01T00:00:00Z",
|
|
5
|
+
"meesterManaged": false,
|
|
6
|
+
"params": {
|
|
7
|
+
"type": "object",
|
|
8
|
+
"properties": {
|
|
9
|
+
"household": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"title": "Who eats here?",
|
|
12
|
+
"description": "e.g. 'two adults, two picky kids', 'just me, vegetarian'.",
|
|
13
|
+
"default": "the household"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"required": [
|
|
17
|
+
"household"
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
"nameTemplate": "Meal Planner",
|
|
21
|
+
"aboutTemplate": "about.md",
|
|
22
|
+
"missionTemplate": "mission.md",
|
|
23
|
+
"gezels": [
|
|
24
|
+
{
|
|
25
|
+
"templateId": "maaltijdplanner",
|
|
26
|
+
"voorman": true
|
|
27
|
+
}
|
|
28
|
+
],
|
|
29
|
+
"tools": [
|
|
30
|
+
{
|
|
31
|
+
"name": "set_menu",
|
|
32
|
+
"description": "Set a day's dinner (monday..sunday).",
|
|
33
|
+
"script": "kitchen-store",
|
|
34
|
+
"inputs": {
|
|
35
|
+
"type": "object",
|
|
36
|
+
"properties": {
|
|
37
|
+
"day": {
|
|
38
|
+
"type": "string"
|
|
39
|
+
},
|
|
40
|
+
"meal": {
|
|
41
|
+
"type": "string"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
"required": [
|
|
45
|
+
"day",
|
|
46
|
+
"meal"
|
|
47
|
+
]
|
|
48
|
+
},
|
|
49
|
+
"bind": {
|
|
50
|
+
"action": "set_menu"
|
|
51
|
+
}
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
"name": "pantry_add",
|
|
55
|
+
"description": "Add a pantry staple (level stocked, low, or out).",
|
|
56
|
+
"script": "kitchen-store",
|
|
57
|
+
"inputs": {
|
|
58
|
+
"type": "object",
|
|
59
|
+
"properties": {
|
|
60
|
+
"name": {
|
|
61
|
+
"type": "string"
|
|
62
|
+
},
|
|
63
|
+
"level": {
|
|
64
|
+
"type": "string",
|
|
65
|
+
"enum": [
|
|
66
|
+
"stocked",
|
|
67
|
+
"low",
|
|
68
|
+
"out"
|
|
69
|
+
]
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
"required": [
|
|
73
|
+
"name"
|
|
74
|
+
]
|
|
75
|
+
},
|
|
76
|
+
"bind": {
|
|
77
|
+
"action": "pantry_add"
|
|
78
|
+
}
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"name": "pantry_level",
|
|
82
|
+
"description": "Update a staple's level as the kitchen changes.",
|
|
83
|
+
"script": "kitchen-store",
|
|
84
|
+
"inputs": {
|
|
85
|
+
"type": "object",
|
|
86
|
+
"properties": {
|
|
87
|
+
"id": {
|
|
88
|
+
"type": "string"
|
|
89
|
+
},
|
|
90
|
+
"name": {
|
|
91
|
+
"type": "string"
|
|
92
|
+
},
|
|
93
|
+
"level": {
|
|
94
|
+
"type": "string",
|
|
95
|
+
"enum": [
|
|
96
|
+
"stocked",
|
|
97
|
+
"low",
|
|
98
|
+
"out"
|
|
99
|
+
]
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
"required": [
|
|
103
|
+
"level"
|
|
104
|
+
]
|
|
105
|
+
},
|
|
106
|
+
"bind": {
|
|
107
|
+
"action": "pantry_level"
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
"name": "kitchen_status",
|
|
112
|
+
"description": "Tonight's dinner, the week's coverage, and the grocery gap.",
|
|
113
|
+
"script": "kitchen-store",
|
|
114
|
+
"inputs": {
|
|
115
|
+
"type": "object",
|
|
116
|
+
"properties": {}
|
|
117
|
+
},
|
|
118
|
+
"bind": {
|
|
119
|
+
"action": "status"
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
"name": "grocery_list",
|
|
124
|
+
"description": "The shopping run: everything low or out.",
|
|
125
|
+
"script": "kitchen-store",
|
|
126
|
+
"inputs": {
|
|
127
|
+
"type": "object",
|
|
128
|
+
"properties": {}
|
|
129
|
+
},
|
|
130
|
+
"bind": {
|
|
131
|
+
"action": "grocery_list"
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
],
|
|
135
|
+
"craftbooks": [
|
|
136
|
+
"weekly-menu-plan"
|
|
137
|
+
],
|
|
138
|
+
"schedules": [],
|
|
139
|
+
"pages": {
|
|
140
|
+
"entry": "dashboard/index.html",
|
|
141
|
+
"reads": [
|
|
142
|
+
{
|
|
143
|
+
"source": "workspace",
|
|
144
|
+
"path": "menu.json"
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
"source": "workspace",
|
|
148
|
+
"path": "pantry.json"
|
|
149
|
+
}
|
|
150
|
+
]
|
|
151
|
+
},
|
|
152
|
+
"workspaceSeed": [
|
|
153
|
+
"menu.json",
|
|
154
|
+
"pantry.json"
|
|
155
|
+
],
|
|
156
|
+
"scripts": {
|
|
157
|
+
"kitchen-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'kitchen-store',\n description:\n \"Read and update the week's menu (menu.json) and the pantry (pantry.json). Call action:'status' for today's dinner + the grocery gap; 'set_menu' day by day; 'pantry_add' / 'pantry_level' as staples change; 'grocery_list' for the run.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'set_menu', 'pantry_add', 'pantry_level', or 'grocery_list'.\",\n required: true,\n },\n day: {\n type: 'string',\n description: \"Day for set_menu: monday..sunday.\",\n },\n meal: { type: 'string', description: 'The dinner for that day.' },\n name: { type: 'string', description: 'Pantry item name.' },\n level: { type: 'string', description: \"Pantry level: 'stocked', 'low', or 'out'.\" },\n id: { type: 'string', description: \"Pantry item id, e.g. 'item-3'.\" },\n },\n outputs: {\n planned: { type: 'number', description: 'Days with a planned dinner.' },\n groceries: { type: 'number', description: 'Items on the grocery list.' },\n summary: { type: 'string', description: 'The kitchen picture.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── kitchen-rules-start ──\n// Pure derivations: the weekly menu shape and the grocery gap (pantry\n// items marked low or out).\n\nvar DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];\n\nfunction normalizeDay(day) {\n if (typeof day !== 'string') return null;\n var d = day.toLowerCase();\n return DAYS.indexOf(d) >= 0 ? d : null;\n}\n\nfunction todayName(nowMs) {\n // getUTCDay: 0=Sunday.\n var idx = new Date(nowMs).getUTCDay();\n return DAYS[(idx + 6) % 7];\n}\n\nfunction groceryGap(records) {\n return records.filter(function (r) {\n return r.status === 'low' || r.status === 'out';\n });\n}\n\nfunction kitchenLine(menu, records, nowMs) {\n var today = todayName(nowMs);\n var tonight = menu.days && menu.days[today];\n var gap = groceryGap(records);\n var planned = DAYS.filter(function (d) {\n return menu.days && menu.days[d];\n }).length;\n return (\n (tonight ? \"Tonight: \" + tonight + '.' : 'Nothing planned for tonight.') +\n ' ' +\n planned +\n ' of 7 dinners planned. Grocery list: ' +\n (gap.length\n ? gap\n .map(function (r) {\n return (r.fields && r.fields.name) || r.id;\n })\n .join(', ')\n : 'empty')\n );\n}\n// ── kitchen-rules-end ──\n\nconst MENU_FILE = 'menu.json';\nconst pantry = rosterStore(gezel.fs, 'pantry.json', {\n stages: ['stocked', 'low', 'out'],\n idPrefix: 'item',\n});\n\ninterface Menu {\n version: number;\n days: Record<string, string>;\n updatedAt: string;\n}\n\nasync function loadMenu(): Promise<Menu> {\n try {\n const parsed = JSON.parse(await gezel.fs.read(MENU_FILE)) as Partial<Menu>;\n return {\n version: 1,\n days: parsed.days && typeof parsed.days === 'object' ? (parsed.days as Record<string, string>) : {},\n updatedAt: new Date().toISOString(),\n };\n } catch {\n return { version: 1, days: {}, updatedAt: new Date().toISOString() };\n }\n}\n\nasync function saveMenu(menu: Menu): Promise<void> {\n menu.updatedAt = new Date().toISOString();\n await gezel.fs.write(MENU_FILE, `${JSON.stringify(menu, null, 2)}\\n`);\n}\n\nasync function resolveItem(): Promise<string> {\n if (input.id) return input.id;\n if (input.name) {\n const wanted = input.name.toLowerCase();\n const matches = (await pantry.list()).filter(\n (r) => String(r.fields.name ?? '').toLowerCase() === wanted,\n );\n if (matches.length === 1) return matches[0]!.id;\n }\n throw new Error(\"Pass 'id' (e.g. 'item-3') or an exact pantry item 'name'.\");\n}\n\nconst menu = await loadMenu();\n\nif (input.action === 'set_menu') {\n const day = normalizeDay(input.day);\n if (!day || !input.meal) throw new Error(\"set_menu needs 'day' (monday..sunday) and 'meal'.\");\n menu.days[day] = input.meal;\n await saveMenu(menu);\n} else if (input.action === 'pantry_add') {\n if (!input.name) throw new Error(\"pantry_add needs 'name'.\");\n const level = input.level && ['stocked', 'low', 'out'].includes(input.level) ? input.level : 'stocked';\n await pantry.add({ status: level, fields: { name: input.name } });\n} else if (input.action === 'pantry_level') {\n if (!input.level || !['stocked', 'low', 'out'].includes(input.level)) {\n throw new Error(\"pantry_level needs level 'stocked', 'low', or 'out'.\");\n }\n await pantry.transition(await resolveItem(), input.level);\n} else if (input.action !== 'status' && input.action !== 'grocery_list') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, set_menu, pantry_add, pantry_level, or grocery_list.`,\n );\n}\n\nconst records = await pantry.list();\nconst gap = groceryGap(records);\nconst listing =\n input.action === 'grocery_list'\n ? gap\n .map((r) => `${String(r.fields.name ?? r.id)} (${r.status})`)\n .join('\\n') || 'The grocery list is empty.'\n : '';\n\ngezel.output({\n planned: DAYS.filter((d) => menu.days[d]).length,\n groceries: gap.length,\n summary: listing || kitchenLine(menu, records, Date.now()),\n});\n"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
- The week's dinners get planned in one sitting, weeknight-easy skewed, from
|
|
2
|
+
what's already in the pantry.
|
|
3
|
+
- The grocery list is the derived gap — grouped, short, one run.
|
|
4
|
+
- Taste memory compounds: loved dishes return, vetoed ones never sneak back.
|
|
5
|
+
- Waste goes down: what's low or turning gets cooked first.
|
|
@@ -0,0 +1,152 @@
|
|
|
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>Meal planner</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
|
+
@media (prefers-color-scheme: dark) {
|
|
15
|
+
:root {
|
|
16
|
+
--bg: #1c1a17; --card: #262320; --ink: #efe9e0; --muted: #a59a8c;
|
|
17
|
+
--accent: #e0894a; --track: #3a352f; --good: #7fae8d; --warn: #d98a63;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
* { box-sizing: border-box; }
|
|
21
|
+
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; }
|
|
22
|
+
main { max-width: 680px; margin: 0 auto; }
|
|
23
|
+
h1 { font-family: Georgia, 'PT Serif', 'Times New Roman', serif; font-size: 1.35rem; font-weight: 600; margin: 0 0 0.15rem; }
|
|
24
|
+
.sub { color: var(--muted); font-size: 0.9rem; margin: 0 0 0.9rem; }
|
|
25
|
+
.week { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.4rem 0; margin-bottom: 0.75rem; }
|
|
26
|
+
.day { display: grid; grid-template-columns: 6.5rem 1fr; gap: 0.6rem; padding: 0.4rem 1rem; font-size: 0.9rem; }
|
|
27
|
+
.day + .day { border-top: 1px solid var(--track); }
|
|
28
|
+
.day .name { color: var(--muted); text-transform: capitalize; }
|
|
29
|
+
.day.today { background: color-mix(in srgb, var(--accent) 8%, transparent); }
|
|
30
|
+
.day.today .name { color: var(--accent); font-weight: 600; }
|
|
31
|
+
.day .meal.unplanned { color: var(--muted); font-style: italic; }
|
|
32
|
+
.panel { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.85rem 1rem; font-size: 0.9rem; }
|
|
33
|
+
.panel h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.5rem; }
|
|
34
|
+
.panel ul { margin: 0; padding-left: 1.05rem; }
|
|
35
|
+
.panel li { margin: 0.2rem 0; }
|
|
36
|
+
.panel .lvl { color: var(--warn); font-size: 0.78rem; }
|
|
37
|
+
.empty { color: var(--muted); font-style: italic; }
|
|
38
|
+
+ .demo-banner {
|
|
39
|
+
display: flex;
|
|
40
|
+
align-items: center;
|
|
41
|
+
justify-content: space-between;
|
|
42
|
+
gap: 0.75rem;
|
|
43
|
+
margin: 0 0 0.85rem;
|
|
44
|
+
padding: 0.5rem 0.65rem;
|
|
45
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
46
|
+
border-radius: 6px;
|
|
47
|
+
background: color-mix(in srgb, var(--card, var(--surface, #fffdf8)) 88%, var(--accent, #8a5a2b));
|
|
48
|
+
color: var(--ink, #2b2620);
|
|
49
|
+
font-size: 0.8rem;
|
|
50
|
+
}
|
|
51
|
+
.demo-banner[hidden] { display: none; }
|
|
52
|
+
.demo-banner strong { font-weight: 700; }
|
|
53
|
+
.demo-reset {
|
|
54
|
+
flex: none;
|
|
55
|
+
padding: 0.25rem 0.5rem;
|
|
56
|
+
border: 1px solid var(--accent, #8a5a2b);
|
|
57
|
+
border-radius: 6px;
|
|
58
|
+
background: var(--card, var(--surface, #fffdf8));
|
|
59
|
+
color: var(--ink, #2b2620);
|
|
60
|
+
cursor: pointer;
|
|
61
|
+
font: inherit;
|
|
62
|
+
}
|
|
63
|
+
.demo-reset:hover { border-color: var(--ink, #2b2620); }
|
|
64
|
+
</style>
|
|
65
|
+
</head>
|
|
66
|
+
<body>
|
|
67
|
+
<main>
|
|
68
|
+
+ <div class="demo-banner" data-gezel-demo-banner hidden>
|
|
69
|
+
<span><strong>Demo mode</strong> — sample data stays in this page.</span>
|
|
70
|
+
<button type="button" class="demo-reset" onclick="location.reload()">Reset demo</button>
|
|
71
|
+
</div>
|
|
72
|
+
<h1>Meal planner</h1>
|
|
73
|
+
<p class="sub" id="sub">Loading the week…</p>
|
|
74
|
+
<div class="week" id="week"></div>
|
|
75
|
+
<div class="panel">
|
|
76
|
+
<h2>Grocery list</h2>
|
|
77
|
+
<ul id="groceries"><li class="empty">Nothing needed — the pantry is holding.</li></ul>
|
|
78
|
+
</div>
|
|
79
|
+
</main>
|
|
80
|
+
<script>
|
|
81
|
+
(function () {
|
|
82
|
+
'use strict';
|
|
83
|
+
var parts = location.pathname.split('/').filter(Boolean);
|
|
84
|
+
var typeIdx = parts.indexOf('type');
|
|
85
|
+
var capability = typeIdx > 0 ? parts[typeIdx - 1] : null;
|
|
86
|
+
var projectId = typeIdx >= 0 ? parts[typeIdx + 1] : null;
|
|
87
|
+
var failedPolls = 0;
|
|
88
|
+
var DAYS = ['monday', 'tuesday', 'wednesday', 'thursday', 'friday', 'saturday', 'sunday'];
|
|
89
|
+
var demoMode = !capability || !projectId;
|
|
90
|
+
var demoMenu = { days: {
|
|
91
|
+
monday: 'Roasted tomato pasta',
|
|
92
|
+
tuesday: 'Miso salmon bowls',
|
|
93
|
+
wednesday: 'Chickpea stew and bread',
|
|
94
|
+
thursday: 'Leftovers',
|
|
95
|
+
friday: 'Mushroom tacos',
|
|
96
|
+
saturday: 'Dinner with friends',
|
|
97
|
+
} };
|
|
98
|
+
var demoPantry = { records: [
|
|
99
|
+
{ id: 'food-1', status: 'low', fields: { name: 'Olive oil' } },
|
|
100
|
+
{ id: 'food-2', status: 'out', fields: { name: 'Limes' } },
|
|
101
|
+
{ id: 'food-3', status: 'low', fields: { name: 'Brown rice' } },
|
|
102
|
+
] };
|
|
103
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
104
|
+
|
|
105
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
106
|
+
function today() { return DAYS[(new Date().getUTCDay() + 6) % 7]; }
|
|
107
|
+
|
|
108
|
+
function fetchJson(file) {
|
|
109
|
+
return fetch('/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/' + file, { cache: 'no-store' })
|
|
110
|
+
.then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); });
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function render(menu, pantry) {
|
|
114
|
+
var days = (menu && menu.days) || {};
|
|
115
|
+
var planned = DAYS.filter(function (d) { return days[d]; }).length;
|
|
116
|
+
var tonight = days[today()];
|
|
117
|
+
document.getElementById('sub').textContent =
|
|
118
|
+
(tonight ? 'Tonight: ' + tonight + ' · ' : 'Nothing planned tonight · ') + planned + ' of 7 planned';
|
|
119
|
+
|
|
120
|
+
document.getElementById('week').innerHTML = DAYS.map(function (d) {
|
|
121
|
+
return '<div class="day' + (d === today() ? ' today' : '') + '"><span class="name">' + d + '</span>' +
|
|
122
|
+
(days[d] ? '<span class="meal">' + esc(days[d]) + '</span>' : '<span class="meal unplanned">not planned</span>') + '</div>';
|
|
123
|
+
}).join('');
|
|
124
|
+
|
|
125
|
+
var gap = ((pantry && pantry.records) || []).filter(function (r) { return r.status === 'low' || r.status === 'out'; });
|
|
126
|
+
if (gap.length) {
|
|
127
|
+
document.getElementById('groceries').innerHTML = gap.map(function (r) {
|
|
128
|
+
return '<li>' + esc((r.fields && r.fields.name) || r.id) + ' <span class="lvl">(' + r.status + ')</span></li>';
|
|
129
|
+
}).join('');
|
|
130
|
+
} else {
|
|
131
|
+
document.getElementById('groceries').innerHTML = '<li class="empty">Nothing needed — the pantry is holding.</li>';
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function poll() {
|
|
136
|
+
if (demoMode) { render(demoMenu, demoPantry); return Promise.resolve(); }
|
|
137
|
+
if (!capability || !projectId) return Promise.resolve();
|
|
138
|
+
return Promise.all([
|
|
139
|
+
fetchJson('menu.json').catch(function () { return { days: {} }; }),
|
|
140
|
+
fetchJson('pantry.json').catch(function () { return { records: [] }; }),
|
|
141
|
+
])
|
|
142
|
+
.then(function (results) { failedPolls = 0; render(results[0], results[1]); })
|
|
143
|
+
.catch(function () {
|
|
144
|
+
failedPolls += 1;
|
|
145
|
+
if (failedPolls === 3 && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
poll().then(function () { setInterval(function () { void poll(); }, 6000); });
|
|
149
|
+
})();
|
|
150
|
+
</script>
|
|
151
|
+
</body>
|
|
152
|
+
</html>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The writing room for **{{title}}**, aiming at {{weeklyWordGoal}} words a week.
|
|
2
|
+
Your Schrijfmaat logs every session, keeps the chapter board moving — outline,
|
|
3
|
+
drafting, revised, done — and reads pages like a friend who wants the book to
|
|
4
|
+
exist. The book is yours; the rhythm is shared.
|
|
5
|
+
|
|
6
|
+
The **Output** tab is the manuscript board: total words, this week's pace, and
|
|
7
|
+
where every chapter stands.
|