@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,157 @@
|
|
|
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
|
+
"title": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"title": "Working title",
|
|
12
|
+
"description": "The novel, as it is known today. Change it any time.",
|
|
13
|
+
"default": "Untitled"
|
|
14
|
+
},
|
|
15
|
+
"weeklyWordGoal": {
|
|
16
|
+
"type": "number",
|
|
17
|
+
"title": "Words per week",
|
|
18
|
+
"description": "The weekly writing rhythm to aim for.",
|
|
19
|
+
"default": 5000
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"title",
|
|
24
|
+
"weeklyWordGoal"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"nameTemplate": "Novel: {{title}}",
|
|
28
|
+
"aboutTemplate": "about.md",
|
|
29
|
+
"missionTemplate": "mission.md",
|
|
30
|
+
"gezels": [
|
|
31
|
+
{
|
|
32
|
+
"templateId": "schrijfmaat",
|
|
33
|
+
"voorman": true
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"tools": [
|
|
37
|
+
{
|
|
38
|
+
"name": "log_session",
|
|
39
|
+
"description": "Log a writing session (words written) the moment it is reported.",
|
|
40
|
+
"script": "manuscript-store",
|
|
41
|
+
"inputs": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"words": {
|
|
45
|
+
"type": "number"
|
|
46
|
+
},
|
|
47
|
+
"note": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": [
|
|
52
|
+
"words"
|
|
53
|
+
]
|
|
54
|
+
},
|
|
55
|
+
"bind": {
|
|
56
|
+
"action": "log_session"
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"name": "add_chapter",
|
|
61
|
+
"description": "Put a chapter on the board (starts in outline).",
|
|
62
|
+
"script": "manuscript-store",
|
|
63
|
+
"inputs": {
|
|
64
|
+
"type": "object",
|
|
65
|
+
"properties": {
|
|
66
|
+
"title": {
|
|
67
|
+
"type": "string"
|
|
68
|
+
},
|
|
69
|
+
"summary": {
|
|
70
|
+
"type": "string"
|
|
71
|
+
}
|
|
72
|
+
},
|
|
73
|
+
"required": [
|
|
74
|
+
"title"
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
"bind": {
|
|
78
|
+
"action": "add_chapter"
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"name": "chapter_stage",
|
|
83
|
+
"description": "Move a chapter between stages: 'outline', 'drafting', 'revised', 'done'.",
|
|
84
|
+
"script": "manuscript-store",
|
|
85
|
+
"inputs": {
|
|
86
|
+
"type": "object",
|
|
87
|
+
"properties": {
|
|
88
|
+
"id": {
|
|
89
|
+
"type": "string"
|
|
90
|
+
},
|
|
91
|
+
"stage": {
|
|
92
|
+
"type": "string"
|
|
93
|
+
},
|
|
94
|
+
"note": {
|
|
95
|
+
"type": "string"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
"required": [
|
|
99
|
+
"id",
|
|
100
|
+
"stage"
|
|
101
|
+
]
|
|
102
|
+
},
|
|
103
|
+
"bind": {
|
|
104
|
+
"action": "chapter_stage"
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
"name": "novel_status",
|
|
109
|
+
"description": "Total words, this week against the goal, the chapter shape.",
|
|
110
|
+
"script": "manuscript-store",
|
|
111
|
+
"inputs": {
|
|
112
|
+
"type": "object",
|
|
113
|
+
"properties": {}
|
|
114
|
+
},
|
|
115
|
+
"bind": {
|
|
116
|
+
"action": "status"
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"name": "outline",
|
|
121
|
+
"description": "Every chapter with its stage and summary.",
|
|
122
|
+
"script": "manuscript-store",
|
|
123
|
+
"inputs": {
|
|
124
|
+
"type": "object",
|
|
125
|
+
"properties": {}
|
|
126
|
+
},
|
|
127
|
+
"bind": {
|
|
128
|
+
"action": "outline"
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
],
|
|
132
|
+
"craftbooks": [
|
|
133
|
+
"copy-review",
|
|
134
|
+
"ebook-compile"
|
|
135
|
+
],
|
|
136
|
+
"schedules": [],
|
|
137
|
+
"pages": {
|
|
138
|
+
"entry": "dashboard/index.html",
|
|
139
|
+
"reads": [
|
|
140
|
+
{
|
|
141
|
+
"source": "workspace",
|
|
142
|
+
"path": "sessions.json"
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
"source": "workspace",
|
|
146
|
+
"path": "chapters.json"
|
|
147
|
+
}
|
|
148
|
+
]
|
|
149
|
+
},
|
|
150
|
+
"workspaceSeed": [
|
|
151
|
+
"sessions.json",
|
|
152
|
+
"chapters.json"
|
|
153
|
+
],
|
|
154
|
+
"scripts": {
|
|
155
|
+
"manuscript-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { logStore, rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'manuscript-store',\n description:\n \"Read and update the manuscript record (sessions.json + chapters.json). Call action:'status' for total words, weekly pace, and the chapter board; 'log_session' after every writing session (words written); 'add_chapter' / 'chapter_stage' to keep the board honest; 'outline' to list every chapter.\",\n inputs: {\n action: {\n type: 'string',\n description: \"One of 'status', 'log_session', 'add_chapter', 'chapter_stage', or 'outline'.\",\n required: true,\n },\n words: { type: 'number', description: 'Words written this session (log_session).' },\n note: { type: 'string', description: 'Anything worth remembering.' },\n title: { type: 'string', description: 'Chapter title (add_chapter).' },\n summary: { type: 'string', description: 'One-line chapter summary (add_chapter).' },\n id: { type: 'string', description: \"Chapter id, e.g. 'ch-3' (chapter_stage).\" },\n stage: {\n type: 'string',\n description: \"Target stage: 'outline', 'drafting', 'revised', or 'done' (chapter_stage).\",\n },\n },\n outputs: {\n totalWords: { type: 'number', description: 'Words logged across all sessions.' },\n thisWeekWords: { type: 'number', description: 'Words logged this week (Monday UTC start).' },\n chapters: { type: 'number', description: 'Chapters on the board.' },\n summary: { type: 'string', description: 'The manuscript picture.' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── novel-rules-start ──\n// Pure derivations over logStore-shaped writing sessions (kind 'writing',\n// data {words}) and rosterStore-shaped chapters (stages outline → drafting\n// → revised → done). Weeks start Monday 00:00 UTC; nowIso is a parameter\n// so the rules stay clock-independent and testable.\n\nfunction sessionWords(e) {\n var words = e && e.data ? Number(e.data.words) : NaN;\n return isFinite(words) && words > 0 ? Math.round(words) : 0;\n}\n\nfunction totalWords(events) {\n return events.reduce(function (sum, e) {\n return sum + sessionWords(e);\n }, 0);\n}\n\nfunction weekStartMs(nowIso) {\n var day = new Date(nowIso.slice(0, 10) + 'T00:00:00Z');\n var sinceMonday = (day.getUTCDay() + 6) % 7;\n return day.getTime() - sinceMonday * 86400000;\n}\n\nfunction thisWeekWords(events, nowIso) {\n var start = weekStartMs(nowIso);\n return totalWords(\n events.filter(function (e) {\n return Date.parse(e.at) >= start;\n }),\n );\n}\n\nfunction chapterShape(records) {\n var byStage = { outline: 0, drafting: 0, revised: 0, done: 0 };\n records.forEach(function (r) {\n if (byStage[r.status] !== undefined) byStage[r.status] += 1;\n });\n return byStage;\n}\n\nfunction novelLine(events, records, nowIso, weeklyWordGoal) {\n var total = totalWords(events);\n var week = thisWeekWords(events, nowIso);\n var shape = chapterShape(records);\n var goal = weeklyWordGoal > 0 ? weeklyWordGoal : 0;\n return (\n total +\n ' words logged; ' +\n week +\n (goal > 0 ? ' of ' + goal : '') +\n ' this week. Chapters: ' +\n shape.outline +\n ' outlined, ' +\n shape.drafting +\n ' drafting, ' +\n shape.revised +\n ' revised, ' +\n shape.done +\n ' done.'\n );\n}\n// ── novel-rules-end ──\n\nconst sessions = logStore(gezel.fs, 'sessions.json');\nconst chapters = rosterStore(gezel.fs, 'chapters.json', {\n stages: ['outline', 'drafting', 'revised', 'done'],\n idPrefix: 'ch',\n});\n\nasync function readWeeklyWordGoal(): Promise<number> {\n try {\n const parsed = JSON.parse(await gezel.fs.read('sessions.json')) as { weeklyWordGoal?: unknown };\n const goal = Number(parsed.weeklyWordGoal);\n return Number.isFinite(goal) && goal > 0 ? Math.round(goal) : 0;\n } catch {\n return 0;\n }\n}\n\nif (input.action === 'log_session') {\n const words = Number(input.words);\n if (!Number.isFinite(words) || words <= 0) {\n throw new Error(\"log_session needs a positive 'words' count.\");\n }\n await sessions.append({\n kind: 'writing',\n ...(input.note ? { note: input.note } : {}),\n data: { words: Math.round(words) },\n });\n} else if (input.action === 'add_chapter') {\n if (!input.title) throw new Error(\"add_chapter needs a 'title'.\");\n await chapters.add({\n fields: { title: input.title, ...(input.summary ? { summary: input.summary } : {}) },\n ...(input.note ? { note: input.note } : {}),\n });\n} else if (input.action === 'chapter_stage') {\n if (!input.id || !input.stage) {\n throw new Error(\"chapter_stage needs 'id' (e.g. 'ch-3') and 'stage'.\");\n }\n await chapters.transition(input.id, input.stage, input.note ? { note: input.note } : undefined);\n} else if (input.action !== 'status' && input.action !== 'outline') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, log_session, add_chapter, chapter_stage, or outline.`,\n );\n}\n\nconst events = await sessions.list();\nconst records = await chapters.list();\nconst weeklyWordGoal = await readWeeklyWordGoal();\nconst nowIso = new Date().toISOString();\nconst listing =\n input.action === 'outline'\n ? records\n .map(\n (r) =>\n `${r.id} [${r.status}] ${String(r.fields.title ?? '')}${r.fields.summary ? ` — ${String(r.fields.summary)}` : ''}`,\n )\n .join('\\n') || 'No chapters on the board yet.'\n : '';\n\ngezel.output({\n totalWords: totalWords(events),\n thisWeekWords: thisWeekWords(events, nowIso),\n chapters: records.length,\n summary: listing || novelLine(events, records, nowIso, weeklyWordGoal),\n});\n"
|
|
156
|
+
}
|
|
157
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
- Finish {{title}} — draft by draft, at a rhythm of about {{weeklyWordGoal}}
|
|
2
|
+
words a week.
|
|
3
|
+
- Every writing session gets logged, even the hundred-word days; the total and
|
|
4
|
+
the pace come from the record.
|
|
5
|
+
- The chapter board always tells the truth: outline, drafting, revised, done.
|
|
6
|
+
- Feedback serves the draft: what works first, then the one or two things that
|
|
7
|
+
would help most — never a rewrite in the writer's voice.
|
|
@@ -0,0 +1,165 @@
|
|
|
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>Novel Writing Room</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;
|
|
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;
|
|
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
|
+
.pace { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 1rem; margin-bottom: 0.75rem; }
|
|
26
|
+
.pace .total { font-size: 1.9rem; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
27
|
+
.pace .goal { color: var(--muted); font-size: 0.9rem; }
|
|
28
|
+
.bar { background: var(--track); border-radius: 4px; height: 12px; overflow: hidden; margin-top: 0.6rem; }
|
|
29
|
+
.bar > span { display: block; height: 100%; background: var(--good); border-radius: 4px; transition: width 0.4s ease; }
|
|
30
|
+
.board { display: grid; grid-template-columns: repeat(4, 1fr); gap: 0.6rem; }
|
|
31
|
+
.col { background: var(--card); border: 1px solid var(--track); border-radius: 10px; padding: 0.7rem 0.8rem; min-height: 5.5rem; }
|
|
32
|
+
.col h2 { font-size: 0.72rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--muted); margin: 0 0 0.45rem; }
|
|
33
|
+
.col h2 .count { color: var(--ink); }
|
|
34
|
+
.card { border: 1px solid var(--track); border-radius: 6px; padding: 0.35rem 0.5rem; margin: 0.3rem 0; font-size: 0.85rem; }
|
|
35
|
+
.card .id { color: var(--muted); font-size: 0.72rem; font-variant-numeric: tabular-nums; }
|
|
36
|
+
.empty { color: var(--muted); font-style: italic; font-size: 0.85rem; }
|
|
37
|
+
@media (max-width: 640px) { .board { grid-template-columns: 1fr 1fr; } }
|
|
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 id="title">Novel Writing Room</h1>
|
|
73
|
+
<p class="sub" id="sub">Loading the manuscript…</p>
|
|
74
|
+
<div class="pace">
|
|
75
|
+
<div class="total" id="total">—</div>
|
|
76
|
+
<div class="goal" id="goal">words logged</div>
|
|
77
|
+
<div class="bar"><span id="bar" style="width: 0%"></span></div>
|
|
78
|
+
</div>
|
|
79
|
+
<div class="board" id="board"></div>
|
|
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 STAGES = ['outline', 'drafting', 'revised', 'done'];
|
|
90
|
+
var demoMode = !capability || !projectId;
|
|
91
|
+
var demoSessions = {
|
|
92
|
+
title: 'The Glass Cartographer',
|
|
93
|
+
weeklyWordGoal: 5000,
|
|
94
|
+
events: [
|
|
95
|
+
{ at: new Date(Date.now() - 6 * 86400000).toISOString(), data: { words: 850 } },
|
|
96
|
+
{ at: new Date(Date.now() - 3 * 86400000).toISOString(), data: { words: 1200 } },
|
|
97
|
+
{ at: new Date().toISOString(), data: { words: 640 } },
|
|
98
|
+
],
|
|
99
|
+
};
|
|
100
|
+
var demoChapters = { records: [
|
|
101
|
+
{ id: 'ch-01', status: 'done', fields: { title: 'The map that changed overnight' } },
|
|
102
|
+
{ id: 'ch-02', status: 'revised', fields: { title: 'A city folded into glass' } },
|
|
103
|
+
{ id: 'ch-03', status: 'drafting', fields: { title: 'The ferryman’s bargain' } },
|
|
104
|
+
{ id: 'ch-04', status: 'outline', fields: { title: 'North of the last road' } },
|
|
105
|
+
{ id: 'ch-05', status: 'outline', fields: { title: 'The unfinished coast' } },
|
|
106
|
+
] };
|
|
107
|
+
if (demoMode) document.querySelector('[data-gezel-demo-banner]').hidden = false;
|
|
108
|
+
|
|
109
|
+
function esc(s) { return String(s).replace(/[&<>"]/g, function (c) { return { '&': '&', '<': '<', '>': '>', '"': '"' }[c]; }); }
|
|
110
|
+
function fmtNum(n) { return String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ','); }
|
|
111
|
+
function weekStartMs(nowIso) {
|
|
112
|
+
var d = new Date(nowIso.slice(0, 10) + 'T00:00:00Z');
|
|
113
|
+
return d.getTime() - ((d.getUTCDay() + 6) % 7) * 86400000;
|
|
114
|
+
}
|
|
115
|
+
function wordsOf(e) {
|
|
116
|
+
var w = e && e.data ? Number(e.data.words) : NaN;
|
|
117
|
+
return isFinite(w) && w > 0 ? Math.round(w) : 0;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function render(sessions, chapters) {
|
|
121
|
+
var events = Array.isArray(sessions.events) ? sessions.events : [];
|
|
122
|
+
var records = Array.isArray(chapters.records) ? chapters.records : [];
|
|
123
|
+
var goal = Number(sessions.weeklyWordGoal);
|
|
124
|
+
if (!isFinite(goal) || goal <= 0) goal = 0;
|
|
125
|
+
var total = 0;
|
|
126
|
+
events.forEach(function (e) { total += wordsOf(e); });
|
|
127
|
+
var start = weekStartMs(new Date().toISOString());
|
|
128
|
+
var week = 0;
|
|
129
|
+
events.forEach(function (e) { if (Date.parse(e.at) >= start) week += wordsOf(e); });
|
|
130
|
+
|
|
131
|
+
if (sessions.title) document.getElementById('title').textContent = sessions.title;
|
|
132
|
+
document.getElementById('sub').textContent = records.length + ' chapter(s) on the board, ' + events.length + ' session(s) logged';
|
|
133
|
+
document.getElementById('total').textContent = fmtNum(total);
|
|
134
|
+
document.getElementById('goal').textContent = 'words logged — ' + fmtNum(week) + (goal > 0 ? ' of ' + fmtNum(goal) : '') + ' this week';
|
|
135
|
+
document.getElementById('bar').style.width = (goal > 0 ? Math.min(100, Math.round((week / goal) * 100)) : week > 0 ? 100 : 0) + '%';
|
|
136
|
+
|
|
137
|
+
document.getElementById('board').innerHTML = STAGES.map(function (stage) {
|
|
138
|
+
var here = records.filter(function (r) { return r.status === stage; });
|
|
139
|
+
var cards = here.map(function (r) {
|
|
140
|
+
return '<div class="card"><span class="id">' + esc(r.id) + '</span> ' + esc(String((r.fields && r.fields.title) || '')) + '</div>';
|
|
141
|
+
}).join('');
|
|
142
|
+
return '<div class="col"><h2>' + stage + ' <span class="count">' + here.length + '</span></h2>' +
|
|
143
|
+
(cards || '<div class="empty">' + (stage === 'outline' ? 'Ask your Schrijfmaat to put chapters on the board.' : 'None yet.') + '</div>') + '</div>';
|
|
144
|
+
}).join('');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function poll() {
|
|
148
|
+
if (demoMode) { render(demoSessions, demoChapters); return Promise.resolve(); }
|
|
149
|
+
if (!capability || !projectId) return Promise.resolve();
|
|
150
|
+
var base = '/preview/' + encodeURIComponent(capability) + '/workspace/' + encodeURIComponent(projectId) + '/';
|
|
151
|
+
return Promise.all([
|
|
152
|
+
fetch(base + 'sessions.json', { cache: 'no-store' }).then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); }),
|
|
153
|
+
fetch(base + 'chapters.json', { cache: 'no-store' }).then(function (r) { if (!r.ok) throw new Error('poll ' + r.status); return r.json(); }),
|
|
154
|
+
])
|
|
155
|
+
.then(function (both) { failedPolls = 0; render(both[0], both[1]); })
|
|
156
|
+
.catch(function () {
|
|
157
|
+
failedPolls += 1;
|
|
158
|
+
if (failedPolls === 3 && window.parent !== window) window.parent.postMessage({ __gezelPageRefresh: true }, '*');
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
poll().then(function () { setInterval(function () { void poll(); }, 5000); });
|
|
162
|
+
})();
|
|
163
|
+
</script>
|
|
164
|
+
</body>
|
|
165
|
+
</html>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
The trip bench for **{{destination}}**, departing {{start}}. Your Reisleider
|
|
2
|
+
plans each day around one anchor with room to breathe, keeps confirmations and
|
|
3
|
+
addresses where they can be found, and builds a packing list that fits the trip.
|
|
4
|
+
|
|
5
|
+
The **Output** tab shows the countdown, the itinerary as it fills in, and the
|
|
6
|
+
packing list — tick items off right on the board. During the trip, a line a day
|
|
7
|
+
in the journal; afterward, it's the souvenir.
|
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
"destination": {
|
|
10
|
+
"type": "string",
|
|
11
|
+
"title": "Where to?",
|
|
12
|
+
"description": "e.g. 'Kyoto', 'the Scottish Highlands'.",
|
|
13
|
+
"default": "somewhere wonderful"
|
|
14
|
+
},
|
|
15
|
+
"start": {
|
|
16
|
+
"type": "string",
|
|
17
|
+
"title": "Departure date",
|
|
18
|
+
"description": "e.g. 2026-09-12. A rough date is fine.",
|
|
19
|
+
"default": "2026-12-01"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"required": [
|
|
23
|
+
"destination",
|
|
24
|
+
"start"
|
|
25
|
+
]
|
|
26
|
+
},
|
|
27
|
+
"nameTemplate": "Trip: {{destination}}",
|
|
28
|
+
"aboutTemplate": "about.md",
|
|
29
|
+
"missionTemplate": "mission.md",
|
|
30
|
+
"gezels": [
|
|
31
|
+
{
|
|
32
|
+
"templateId": "reisleider",
|
|
33
|
+
"voorman": true
|
|
34
|
+
}
|
|
35
|
+
],
|
|
36
|
+
"tools": [
|
|
37
|
+
{
|
|
38
|
+
"name": "plan_day",
|
|
39
|
+
"description": "Set a day's plan (one anchor, options, slack).",
|
|
40
|
+
"script": "trip-store",
|
|
41
|
+
"inputs": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"properties": {
|
|
44
|
+
"date": {
|
|
45
|
+
"type": "string"
|
|
46
|
+
},
|
|
47
|
+
"plan": {
|
|
48
|
+
"type": "string"
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"required": [
|
|
52
|
+
"date",
|
|
53
|
+
"plan"
|
|
54
|
+
]
|
|
55
|
+
},
|
|
56
|
+
"bind": {
|
|
57
|
+
"action": "plan_day"
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
"name": "pack_add",
|
|
62
|
+
"description": "Add an item to the packing list.",
|
|
63
|
+
"script": "trip-store",
|
|
64
|
+
"inputs": {
|
|
65
|
+
"type": "object",
|
|
66
|
+
"properties": {
|
|
67
|
+
"item": {
|
|
68
|
+
"type": "string"
|
|
69
|
+
}
|
|
70
|
+
},
|
|
71
|
+
"required": [
|
|
72
|
+
"item"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"bind": {
|
|
76
|
+
"action": "pack_add"
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
{
|
|
80
|
+
"name": "pack_check",
|
|
81
|
+
"description": "Mark an item packed.",
|
|
82
|
+
"script": "trip-store",
|
|
83
|
+
"inputs": {
|
|
84
|
+
"type": "object",
|
|
85
|
+
"properties": {
|
|
86
|
+
"id": {
|
|
87
|
+
"type": "string"
|
|
88
|
+
},
|
|
89
|
+
"item": {
|
|
90
|
+
"type": "string"
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
"bind": {
|
|
95
|
+
"action": "pack_check"
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"name": "journal_entry",
|
|
100
|
+
"description": "Capture a trip moment — a line a day.",
|
|
101
|
+
"script": "trip-store",
|
|
102
|
+
"inputs": {
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {
|
|
105
|
+
"note": {
|
|
106
|
+
"type": "string"
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
"required": [
|
|
110
|
+
"note"
|
|
111
|
+
]
|
|
112
|
+
},
|
|
113
|
+
"bind": {
|
|
114
|
+
"action": "journal_entry"
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
{
|
|
118
|
+
"name": "trip_status",
|
|
119
|
+
"description": "Countdown, days planned, and the packing state.",
|
|
120
|
+
"script": "trip-store",
|
|
121
|
+
"inputs": {
|
|
122
|
+
"type": "object",
|
|
123
|
+
"properties": {}
|
|
124
|
+
},
|
|
125
|
+
"bind": {
|
|
126
|
+
"action": "status"
|
|
127
|
+
}
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
"name": "itinerary",
|
|
131
|
+
"description": "Read the full day-by-day itinerary.",
|
|
132
|
+
"script": "trip-store",
|
|
133
|
+
"inputs": {
|
|
134
|
+
"type": "object",
|
|
135
|
+
"properties": {}
|
|
136
|
+
},
|
|
137
|
+
"bind": {
|
|
138
|
+
"action": "itinerary"
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"name": "mark_packed",
|
|
143
|
+
"description": "The dashboard's packing checkbox.",
|
|
144
|
+
"script": "trip-store",
|
|
145
|
+
"inputs": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"id": {
|
|
149
|
+
"type": "string"
|
|
150
|
+
}
|
|
151
|
+
},
|
|
152
|
+
"required": [
|
|
153
|
+
"id"
|
|
154
|
+
]
|
|
155
|
+
},
|
|
156
|
+
"bind": {
|
|
157
|
+
"action": "pack_check"
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
],
|
|
161
|
+
"craftbooks": [
|
|
162
|
+
"pre-departure-countdown"
|
|
163
|
+
],
|
|
164
|
+
"schedules": [],
|
|
165
|
+
"pages": {
|
|
166
|
+
"entry": "dashboard/index.html",
|
|
167
|
+
"reads": [
|
|
168
|
+
{
|
|
169
|
+
"source": "workspace",
|
|
170
|
+
"path": "trip.json"
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
"source": "workspace",
|
|
174
|
+
"path": "packing.json"
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
"source": "workspace",
|
|
178
|
+
"path": "journal.json"
|
|
179
|
+
}
|
|
180
|
+
],
|
|
181
|
+
"tools": [
|
|
182
|
+
"mark_packed"
|
|
183
|
+
]
|
|
184
|
+
},
|
|
185
|
+
"workspaceSeed": [
|
|
186
|
+
"trip.json",
|
|
187
|
+
"packing.json",
|
|
188
|
+
"journal.json"
|
|
189
|
+
],
|
|
190
|
+
"scripts": {
|
|
191
|
+
"trip-store": "import { type InferredInput, defineScript, gezel } from '@bendyline/gezel-sdk';\nimport { logStore, rosterStore } from '@bendyline/gezel-sdk/stores';\n\nexport const meta = defineScript({\n name: 'trip-store',\n description:\n \"Read and update the trip: itinerary + packing (trip.json) and the journal (journal.json). Call action:'status' for the shape of the trip; 'plan_day' with date + plan; 'pack_add' / 'pack_check' for the packing list; 'journal_entry' during the trip.\",\n inputs: {\n action: {\n type: 'string',\n description:\n \"One of 'status', 'plan_day', 'pack_add', 'pack_check', 'journal_entry', or 'itinerary'.\",\n required: true,\n },\n date: { type: 'string', description: 'Day being planned, e.g. 2026-09-14.' },\n plan: { type: 'string', description: \"The day's plan (anchor + options).\" },\n item: { type: 'string', description: 'Packing item name.' },\n id: { type: 'string', description: \"Packing item id, e.g. 'pack-3'.\" },\n note: { type: 'string', description: 'Journal entry text.' },\n },\n outputs: {\n daysPlanned: { type: 'number', description: 'Days with a plan.' },\n unpacked: { type: 'number', description: 'Packing items still unpacked.' },\n summary: { type: 'string', description: 'The trip picture (or the itinerary).' },\n },\n requires: ['workspace.read', 'workspace.write'],\n} as const);\n\nconst input = gezel.input as InferredInput<typeof meta>;\n\n// ── trip-rules-start ──\n// Pure derivations over the trip doc: countdown and itinerary rendering.\n\nfunction daysUntil(dateStr, nowMs) {\n var when = Date.parse(dateStr);\n if (!isFinite(when)) return null;\n return Math.ceil((when - nowMs) / (24 * 60 * 60 * 1000));\n}\n\nfunction renderItinerary(days) {\n var dates = Object.keys(days).sort();\n if (dates.length === 0) return 'No days planned yet.';\n return dates\n .map(function (d) {\n return d + ': ' + days[d];\n })\n .join('\\n');\n}\n\nfunction tripLine(trip, unpackedCount, nowMs) {\n var start = daysUntil(trip.startDate, nowMs);\n var planned = Object.keys(trip.days || {}).length;\n var lead =\n start === null\n ? ''\n : start > 0\n ? start + ' day(s) until departure. '\n : start === 0\n ? 'Departure day! '\n : 'On the road (or home again). ';\n return (\n lead + planned + ' day(s) planned. Packing: ' +\n (unpackedCount === 0 ? 'all packed.' : unpackedCount + ' item(s) still unpacked.')\n );\n}\n// ── trip-rules-end ──\n\nconst TRIP_FILE = 'trip.json';\nconst packing = rosterStore(gezel.fs, 'packing.json', {\n stages: ['unpacked', 'packed'],\n idPrefix: 'pack',\n});\nconst journal = logStore(gezel.fs, 'journal.json');\n\ninterface Trip {\n version: number;\n destination: string;\n startDate: string;\n days: Record<string, string>;\n updatedAt: string;\n}\n\nasync function loadTrip(): Promise<Trip> {\n try {\n const parsed = JSON.parse(await gezel.fs.read(TRIP_FILE)) as Partial<Trip>;\n return {\n version: 1,\n destination: typeof parsed.destination === 'string' ? parsed.destination : '',\n startDate: typeof parsed.startDate === 'string' ? parsed.startDate : '',\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, destination: '', startDate: '', days: {}, updatedAt: new Date().toISOString() };\n }\n}\n\nasync function saveTrip(trip: Trip): Promise<void> {\n trip.updatedAt = new Date().toISOString();\n await gezel.fs.write(TRIP_FILE, `${JSON.stringify(trip, null, 2)}\\n`);\n}\n\nconst trip = await loadTrip();\n\nif (input.action === 'plan_day') {\n if (!input.date || !input.plan) throw new Error(\"plan_day needs 'date' (YYYY-MM-DD) and 'plan'.\");\n trip.days[input.date.slice(0, 10)] = input.plan;\n await saveTrip(trip);\n} else if (input.action === 'pack_add') {\n if (!input.item) throw new Error(\"pack_add needs 'item'.\");\n await packing.add({ status: 'unpacked', fields: { name: input.item } });\n} else if (input.action === 'pack_check') {\n let id = input.id;\n if (!id && input.item) {\n const wanted = input.item.toLowerCase();\n const matches = (await packing.list()).filter(\n (r) => String(r.fields.name ?? '').toLowerCase() === wanted,\n );\n if (matches.length === 1) id = matches[0]!.id;\n }\n if (!id) throw new Error(\"Pass 'id' (e.g. 'pack-3') or an exact packing 'item'.\");\n await packing.transition(id, 'packed');\n} else if (input.action === 'journal_entry') {\n if (!input.note) throw new Error(\"journal_entry needs 'note'.\");\n await journal.append({ kind: 'entry', note: input.note });\n} else if (input.action !== 'status' && input.action !== 'itinerary') {\n throw new Error(\n `Unknown action '${input.action}'. Use status, plan_day, pack_add, pack_check, journal_entry, or itinerary.`,\n );\n}\n\nconst packItems = await packing.list();\nconst unpacked = packItems.filter((r) => r.status === 'unpacked').length;\n\ngezel.output({\n daysPlanned: Object.keys(trip.days).length,\n unpacked,\n summary:\n input.action === 'itinerary' ? renderItinerary(trip.days) : tripLine(trip, unpacked, Date.now()),\n});\n"
|
|
192
|
+
}
|
|
193
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
- Every trip day has one anchor and deliberate slack — a spine, not a cage.
|
|
2
|
+
- Bookings, confirmations, and addresses are recorded the moment they exist.
|
|
3
|
+
- Packing is a five-minute sweep the night before, not a panic.
|
|
4
|
+
- The journal gets a line a day; the trip ends, the record stays.
|