omegon 0.6.0

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 (160) hide show
  1. package/.gitattributes +3 -0
  2. package/AGENTS.md +16 -0
  3. package/LICENSE +15 -0
  4. package/README.md +289 -0
  5. package/bin/pi.mjs +30 -0
  6. package/extensions/00-secrets/index.ts +1126 -0
  7. package/extensions/01-auth/auth.ts +401 -0
  8. package/extensions/01-auth/index.ts +289 -0
  9. package/extensions/auto-compact.ts +42 -0
  10. package/extensions/bootstrap/deps.ts +291 -0
  11. package/extensions/bootstrap/index.ts +811 -0
  12. package/extensions/chronos/chronos.sh +487 -0
  13. package/extensions/chronos/index.ts +148 -0
  14. package/extensions/cleave/assessment.ts +754 -0
  15. package/extensions/cleave/bridge.ts +31 -0
  16. package/extensions/cleave/conflicts.ts +250 -0
  17. package/extensions/cleave/dispatcher.ts +808 -0
  18. package/extensions/cleave/guardrails.ts +426 -0
  19. package/extensions/cleave/index.ts +3121 -0
  20. package/extensions/cleave/lifecycle-emitter.ts +20 -0
  21. package/extensions/cleave/openspec.ts +811 -0
  22. package/extensions/cleave/planner.ts +260 -0
  23. package/extensions/cleave/review.ts +579 -0
  24. package/extensions/cleave/skills.ts +355 -0
  25. package/extensions/cleave/types.ts +261 -0
  26. package/extensions/cleave/workspace.ts +861 -0
  27. package/extensions/cleave/worktree.ts +243 -0
  28. package/extensions/core-renderers.ts +253 -0
  29. package/extensions/dashboard/context-gauge.ts +58 -0
  30. package/extensions/dashboard/file-watch.ts +14 -0
  31. package/extensions/dashboard/footer.ts +1145 -0
  32. package/extensions/dashboard/git.ts +185 -0
  33. package/extensions/dashboard/index.ts +478 -0
  34. package/extensions/dashboard/memory-audit.ts +34 -0
  35. package/extensions/dashboard/overlay-data.ts +705 -0
  36. package/extensions/dashboard/overlay.ts +365 -0
  37. package/extensions/dashboard/render-utils.ts +54 -0
  38. package/extensions/dashboard/types.ts +191 -0
  39. package/extensions/dashboard/uri-helper.ts +45 -0
  40. package/extensions/debug.ts +69 -0
  41. package/extensions/defaults.ts +282 -0
  42. package/extensions/design-tree/dashboard-state.ts +161 -0
  43. package/extensions/design-tree/design-card.ts +362 -0
  44. package/extensions/design-tree/index.ts +2130 -0
  45. package/extensions/design-tree/lifecycle-emitter.ts +41 -0
  46. package/extensions/design-tree/tree.ts +1607 -0
  47. package/extensions/design-tree/types.ts +163 -0
  48. package/extensions/distill.ts +127 -0
  49. package/extensions/effort/index.ts +395 -0
  50. package/extensions/effort/tiers.ts +146 -0
  51. package/extensions/effort/types.ts +105 -0
  52. package/extensions/lib/git-state.ts +227 -0
  53. package/extensions/lib/local-models.ts +157 -0
  54. package/extensions/lib/model-preferences.ts +51 -0
  55. package/extensions/lib/model-routing.ts +720 -0
  56. package/extensions/lib/operator-fallback.ts +205 -0
  57. package/extensions/lib/operator-profile.ts +360 -0
  58. package/extensions/lib/slash-command-bridge.ts +253 -0
  59. package/extensions/lib/typebox-helpers.ts +16 -0
  60. package/extensions/local-inference/index.ts +727 -0
  61. package/extensions/mcp-bridge/README.md +220 -0
  62. package/extensions/mcp-bridge/index.ts +951 -0
  63. package/extensions/mcp-bridge/lib.ts +365 -0
  64. package/extensions/mcp-bridge/mcp.json +3 -0
  65. package/extensions/mcp-bridge/package.json +11 -0
  66. package/extensions/model-budget.ts +752 -0
  67. package/extensions/offline-driver.ts +403 -0
  68. package/extensions/openspec/archive-gate.ts +164 -0
  69. package/extensions/openspec/branch-cleanup.ts +64 -0
  70. package/extensions/openspec/dashboard-state.ts +50 -0
  71. package/extensions/openspec/index.ts +1917 -0
  72. package/extensions/openspec/lifecycle-emitter.ts +65 -0
  73. package/extensions/openspec/lifecycle-files.ts +70 -0
  74. package/extensions/openspec/lifecycle.ts +50 -0
  75. package/extensions/openspec/reconcile.ts +187 -0
  76. package/extensions/openspec/spec.ts +1385 -0
  77. package/extensions/openspec/types.ts +98 -0
  78. package/extensions/project-memory/DESIGN-global-mind.md +198 -0
  79. package/extensions/project-memory/README.md +202 -0
  80. package/extensions/project-memory/api-types.ts +382 -0
  81. package/extensions/project-memory/compaction-policy.ts +29 -0
  82. package/extensions/project-memory/core.ts +164 -0
  83. package/extensions/project-memory/embeddings.ts +230 -0
  84. package/extensions/project-memory/extraction-v2.ts +861 -0
  85. package/extensions/project-memory/factstore.ts +2177 -0
  86. package/extensions/project-memory/index.ts +3459 -0
  87. package/extensions/project-memory/injection-metrics.ts +91 -0
  88. package/extensions/project-memory/jsonl-io.ts +12 -0
  89. package/extensions/project-memory/lifecycle.ts +331 -0
  90. package/extensions/project-memory/migration.ts +293 -0
  91. package/extensions/project-memory/package.json +9 -0
  92. package/extensions/project-memory/sci-renderers.ts +7 -0
  93. package/extensions/project-memory/template.ts +103 -0
  94. package/extensions/project-memory/triggers.ts +52 -0
  95. package/extensions/project-memory/types.ts +102 -0
  96. package/extensions/render/composition/fonts/Inter-Bold.ttf +0 -0
  97. package/extensions/render/composition/fonts/Inter-Regular.ttf +0 -0
  98. package/extensions/render/composition/fonts/Tomorrow-Bold.ttf +0 -0
  99. package/extensions/render/composition/fonts/Tomorrow-Regular.ttf +0 -0
  100. package/extensions/render/composition/package-lock.json +534 -0
  101. package/extensions/render/composition/package.json +22 -0
  102. package/extensions/render/composition/render.mjs +246 -0
  103. package/extensions/render/composition/test-comp.tsx +87 -0
  104. package/extensions/render/composition/types.ts +24 -0
  105. package/extensions/render/excalidraw/UPSTREAM.md +81 -0
  106. package/extensions/render/excalidraw/elements.ts +764 -0
  107. package/extensions/render/excalidraw/index.ts +66 -0
  108. package/extensions/render/excalidraw/types.ts +223 -0
  109. package/extensions/render/excalidraw-renderer/pyproject.toml +8 -0
  110. package/extensions/render/excalidraw-renderer/render_excalidraw.py +182 -0
  111. package/extensions/render/excalidraw-renderer/render_template.html +59 -0
  112. package/extensions/render/index.ts +830 -0
  113. package/extensions/render/native-diagrams/index.ts +57 -0
  114. package/extensions/render/native-diagrams/motifs.ts +542 -0
  115. package/extensions/render/native-diagrams/raster.ts +8 -0
  116. package/extensions/render/native-diagrams/scene.ts +75 -0
  117. package/extensions/render/native-diagrams/spec.ts +204 -0
  118. package/extensions/render/native-diagrams/svg.ts +116 -0
  119. package/extensions/sci-ui.ts +304 -0
  120. package/extensions/session-log.ts +174 -0
  121. package/extensions/shared-state.ts +146 -0
  122. package/extensions/spinner-verbs.ts +91 -0
  123. package/extensions/style.ts +281 -0
  124. package/extensions/terminal-title.ts +191 -0
  125. package/extensions/tool-profile/index.ts +291 -0
  126. package/extensions/tool-profile/profiles.ts +290 -0
  127. package/extensions/types.d.ts +9 -0
  128. package/extensions/vault/index.ts +185 -0
  129. package/extensions/version-check.ts +90 -0
  130. package/extensions/view/index.ts +859 -0
  131. package/extensions/view/uri-resolver.ts +148 -0
  132. package/extensions/web-search/index.ts +182 -0
  133. package/extensions/web-search/providers.ts +121 -0
  134. package/extensions/web-ui/index.ts +110 -0
  135. package/extensions/web-ui/server.ts +265 -0
  136. package/extensions/web-ui/state.ts +462 -0
  137. package/extensions/web-ui/static/index.html +145 -0
  138. package/extensions/web-ui/types.ts +284 -0
  139. package/package.json +76 -0
  140. package/prompts/init.md +75 -0
  141. package/prompts/new-repo.md +54 -0
  142. package/prompts/oci-login.md +56 -0
  143. package/prompts/status.md +50 -0
  144. package/settings.json +4 -0
  145. package/skills/cleave/SKILL.md +218 -0
  146. package/skills/git/SKILL.md +209 -0
  147. package/skills/git/_reference/ci-validation.md +204 -0
  148. package/skills/oci/SKILL.md +338 -0
  149. package/skills/openspec/SKILL.md +346 -0
  150. package/skills/pi-extensions/SKILL.md +191 -0
  151. package/skills/pi-tui/SKILL.md +517 -0
  152. package/skills/python/SKILL.md +189 -0
  153. package/skills/rust/SKILL.md +268 -0
  154. package/skills/security/SKILL.md +206 -0
  155. package/skills/style/SKILL.md +264 -0
  156. package/skills/typescript/SKILL.md +225 -0
  157. package/skills/vault/SKILL.md +102 -0
  158. package/themes/alpharius-legacy.json +85 -0
  159. package/themes/alpharius.conf +59 -0
  160. package/themes/alpharius.json +88 -0
@@ -0,0 +1,487 @@
1
+ #!/bin/bash
2
+ # chronos.sh - Comprehensive date and time context
3
+ # Outputs structured date context to eliminate AI date calculation errors
4
+ # Cross-platform: macOS (BSD date) and Linux (GNU date)
5
+
6
+ set -uo pipefail
7
+
8
+ # Detect date command flavor
9
+ if date --version >/dev/null 2>&1; then
10
+ DATE_FLAVOR="gnu"
11
+ else
12
+ DATE_FLAVOR="bsd"
13
+ fi
14
+
15
+ # Helper: add/subtract days from a date
16
+ # Usage: date_add "2026-01-25" -3 -> outputs date 3 days earlier
17
+ date_add() {
18
+ local base_date=$1
19
+ local days=$2
20
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
21
+ date -d "$base_date $days days" "+%Y-%m-%d"
22
+ else
23
+ # BSD date: convert to epoch, add seconds, convert back
24
+ local epoch=$(date -j -f "%Y-%m-%d" "$base_date" "+%s" 2>/dev/null)
25
+ local new_epoch=$((epoch + days * 86400))
26
+ date -r "$new_epoch" "+%Y-%m-%d"
27
+ fi
28
+ }
29
+
30
+ # Helper: format date as "Mon D"
31
+ format_short() {
32
+ local ymd=$1
33
+ local month=$(echo "$ymd" | cut -d'-' -f2)
34
+ local day=$(echo "$ymd" | cut -d'-' -f3 | sed 's/^0//')
35
+ case $month in
36
+ 01) mon="Jan" ;; 02) mon="Feb" ;; 03) mon="Mar" ;;
37
+ 04) mon="Apr" ;; 05) mon="May" ;; 06) mon="Jun" ;;
38
+ 07) mon="Jul" ;; 08) mon="Aug" ;; 09) mon="Sep" ;;
39
+ 10) mon="Oct" ;; 11) mon="Nov" ;; 12) mon="Dec" ;;
40
+ esac
41
+ echo "$mon $day"
42
+ }
43
+
44
+ # Helper: get epoch from date string
45
+ date_to_epoch() {
46
+ local ymd=$1
47
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
48
+ date -d "$ymd" "+%s"
49
+ else
50
+ date -j -f "%Y-%m-%d" "$ymd" "+%s" 2>/dev/null
51
+ fi
52
+ }
53
+
54
+ # Helper: resolve a relative date expression
55
+ resolve_relative() {
56
+ local expr=$1
57
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
58
+ date -d "$expr" "+%Y-%m-%d" 2>/dev/null
59
+ else
60
+ # BSD: handle common expressions manually
61
+ case "$expr" in
62
+ *"days ago")
63
+ local n=$(echo "$expr" | grep -oE '[0-9]+')
64
+ date_add "$TODAY" "-$n"
65
+ ;;
66
+ *"days from now"|*"days ahead")
67
+ local n=$(echo "$expr" | grep -oE '[0-9]+')
68
+ date_add "$TODAY" "$n"
69
+ ;;
70
+ "yesterday")
71
+ date_add "$TODAY" "-1"
72
+ ;;
73
+ "tomorrow")
74
+ date_add "$TODAY" "1"
75
+ ;;
76
+ "next Monday"|"next monday")
77
+ local days_ahead=$(( (8 - DOW_NUM) % 7 ))
78
+ [[ $days_ahead -eq 0 ]] && days_ahead=7
79
+ date_add "$TODAY" "$days_ahead"
80
+ ;;
81
+ "next Friday"|"next friday")
82
+ local target=5
83
+ local days_ahead=$(( (target - DOW_NUM + 7) % 7 ))
84
+ [[ $days_ahead -eq 0 ]] && days_ahead=7
85
+ date_add "$TODAY" "$days_ahead"
86
+ ;;
87
+ "last Monday"|"last monday")
88
+ local days_back=$(( (DOW_NUM - 1 + 7) % 7 ))
89
+ [[ $days_back -eq 0 ]] && days_back=7
90
+ date_add "$TODAY" "-$days_back"
91
+ ;;
92
+ "last Friday"|"last friday")
93
+ local target=5
94
+ local days_back=$(( (DOW_NUM - target + 7) % 7 ))
95
+ [[ $days_back -eq 0 ]] && days_back=7
96
+ date_add "$TODAY" "-$days_back"
97
+ ;;
98
+ *"weeks ago")
99
+ local n=$(echo "$expr" | grep -oE '[0-9]+')
100
+ date_add "$TODAY" "-$((n * 7))"
101
+ ;;
102
+ *"months ago")
103
+ local n=$(echo "$expr" | grep -oE '[0-9]+')
104
+ local y=$(echo "$TODAY" | cut -d'-' -f1)
105
+ local m=$(echo "$TODAY" | cut -d'-' -f2 | sed 's/^0//')
106
+ local d=$(echo "$TODAY" | cut -d'-' -f3)
107
+ m=$((m - n))
108
+ while [[ $m -le 0 ]]; do
109
+ m=$((m + 12))
110
+ y=$((y - 1))
111
+ done
112
+ printf "%04d-%02d-%s" "$y" "$m" "$d"
113
+ ;;
114
+ *)
115
+ echo "ERROR: Cannot parse '$expr' on BSD date. Use GNU date for complex expressions." >&2
116
+ return 1
117
+ ;;
118
+ esac
119
+ fi
120
+ }
121
+
122
+ # Get today's info
123
+ TODAY=$(date "+%Y-%m-%d")
124
+ TODAY_DOW=$(date "+%A")
125
+ DOW_NUM=$(date "+%u") # 1=Monday, 7=Sunday
126
+
127
+ # ---- Subcommand functions ----
128
+
129
+ compute_week() {
130
+ DAYS_SINCE_MON=$((DOW_NUM - 1))
131
+ CURR_MON=$(date_add "$TODAY" "-$DAYS_SINCE_MON")
132
+ CURR_FRI=$(date_add "$CURR_MON" "4")
133
+
134
+ PREV_MON=$(date_add "$CURR_MON" "-7")
135
+ PREV_FRI=$(date_add "$PREV_MON" "4")
136
+
137
+ # Extract years for range formatting
138
+ CURR_MON_YEAR=$(echo "$CURR_MON" | cut -d'-' -f1)
139
+ CURR_FRI_YEAR=$(echo "$CURR_FRI" | cut -d'-' -f1)
140
+ PREV_MON_YEAR=$(echo "$PREV_MON" | cut -d'-' -f1)
141
+ PREV_FRI_YEAR=$(echo "$PREV_FRI" | cut -d'-' -f1)
142
+
143
+ # Format short dates
144
+ CURR_MON_FMT=$(format_short "$CURR_MON")
145
+ CURR_FRI_FMT=$(format_short "$CURR_FRI")
146
+ PREV_MON_FMT=$(format_short "$PREV_MON")
147
+ PREV_FRI_FMT=$(format_short "$PREV_FRI")
148
+
149
+ # Build range strings (handle year boundaries)
150
+ if [ "$CURR_MON_YEAR" = "$CURR_FRI_YEAR" ]; then
151
+ CURR_WEEK_RANGE="$CURR_MON_FMT - $CURR_FRI_FMT, $CURR_FRI_YEAR"
152
+ else
153
+ CURR_WEEK_RANGE="$CURR_MON_FMT, $CURR_MON_YEAR - $CURR_FRI_FMT, $CURR_FRI_YEAR"
154
+ fi
155
+
156
+ if [ "$PREV_MON_YEAR" = "$PREV_FRI_YEAR" ]; then
157
+ PREV_WEEK_RANGE="$PREV_MON_FMT - $PREV_FRI_FMT, $PREV_FRI_YEAR"
158
+ else
159
+ PREV_WEEK_RANGE="$PREV_MON_FMT, $PREV_MON_YEAR - $PREV_FRI_FMT, $PREV_FRI_YEAR"
160
+ fi
161
+
162
+ echo "DATE_CONTEXT:"
163
+ echo " TODAY: $TODAY ($TODAY_DOW)"
164
+ echo " CURR_WEEK_START: $CURR_MON (Monday)"
165
+ echo " CURR_WEEK_END: $CURR_FRI (Friday)"
166
+ echo " CURR_WEEK_RANGE: $CURR_WEEK_RANGE"
167
+ echo " PREV_WEEK_START: $PREV_MON (Monday)"
168
+ echo " PREV_WEEK_END: $PREV_FRI (Friday)"
169
+ echo " PREV_WEEK_RANGE: $PREV_WEEK_RANGE"
170
+ }
171
+
172
+ compute_month() {
173
+ local year=$(echo "$TODAY" | cut -d'-' -f1)
174
+ local month=$(echo "$TODAY" | cut -d'-' -f2 | sed 's/^0//')
175
+
176
+ # Current month boundaries
177
+ local curr_month_start
178
+ curr_month_start=$(printf "%04d-%02d-01" "$year" "$month")
179
+
180
+ # Next month first day, then subtract 1 day for end of current month
181
+ local next_month=$((month + 1))
182
+ local next_year=$year
183
+ if [[ $next_month -gt 12 ]]; then
184
+ next_month=1
185
+ next_year=$((year + 1))
186
+ fi
187
+ local next_month_start
188
+ next_month_start=$(printf "%04d-%02d-01" "$next_year" "$next_month")
189
+ local curr_month_end
190
+ curr_month_end=$(date_add "$next_month_start" "-1")
191
+
192
+ # Previous month boundaries
193
+ local prev_month=$((month - 1))
194
+ local prev_year=$year
195
+ if [[ $prev_month -lt 1 ]]; then
196
+ prev_month=12
197
+ prev_year=$((year - 1))
198
+ fi
199
+ local prev_month_start
200
+ prev_month_start=$(printf "%04d-%02d-01" "$prev_year" "$prev_month")
201
+ local prev_month_end
202
+ prev_month_end=$(date_add "$curr_month_start" "-1")
203
+
204
+ local curr_month_fmt
205
+ curr_month_fmt=$(format_short "$curr_month_start")
206
+ local curr_month_end_fmt
207
+ curr_month_end_fmt=$(format_short "$curr_month_end")
208
+ local prev_month_fmt
209
+ prev_month_fmt=$(format_short "$prev_month_start")
210
+ local prev_month_end_fmt
211
+ prev_month_end_fmt=$(format_short "$prev_month_end")
212
+
213
+ echo "MONTH_CONTEXT:"
214
+ echo " TODAY: $TODAY ($TODAY_DOW)"
215
+ echo " CURR_MONTH_START: $curr_month_start"
216
+ echo " CURR_MONTH_END: $curr_month_end"
217
+ echo " CURR_MONTH_RANGE: $curr_month_fmt - $curr_month_end_fmt, $year"
218
+ echo " PREV_MONTH_START: $prev_month_start"
219
+ echo " PREV_MONTH_END: $prev_month_end"
220
+ echo " PREV_MONTH_RANGE: $prev_month_fmt, $prev_year - $prev_month_end_fmt, $year"
221
+ }
222
+
223
+ compute_quarter() {
224
+ local year=$(echo "$TODAY" | cut -d'-' -f1)
225
+ local month=$(echo "$TODAY" | cut -d'-' -f2 | sed 's/^0//')
226
+
227
+ # Calendar quarter (Q1=Jan-Mar, Q2=Apr-Jun, Q3=Jul-Sep, Q4=Oct-Dec)
228
+ local quarter=$(( (month - 1) / 3 + 1 ))
229
+ local q_start_month=$(( (quarter - 1) * 3 + 1 ))
230
+ local q_end_month=$(( quarter * 3 ))
231
+ local q_start
232
+ q_start=$(printf "%04d-%02d-01" "$year" "$q_start_month")
233
+
234
+ # Quarter end: first day of next quarter minus 1
235
+ local next_q_month=$(( q_end_month + 1 ))
236
+ local next_q_year=$year
237
+ if [[ $next_q_month -gt 12 ]]; then
238
+ next_q_month=1
239
+ next_q_year=$((year + 1))
240
+ fi
241
+ local next_q_start
242
+ next_q_start=$(printf "%04d-%02d-01" "$next_q_year" "$next_q_month")
243
+ local q_end
244
+ q_end=$(date_add "$next_q_start" "-1")
245
+
246
+ # Fiscal year (Oct-Sep): FY starts in October of previous calendar year
247
+ local fy_year
248
+ local fy_start fy_end
249
+ if [[ $month -ge 10 ]]; then
250
+ fy_year=$((year + 1))
251
+ fy_start="$year-10-01"
252
+ fy_end="$fy_year-09-30"
253
+ else
254
+ fy_year=$year
255
+ fy_start="$((year - 1))-10-01"
256
+ fy_end="$year-09-30"
257
+ fi
258
+
259
+ # Fiscal quarter
260
+ local fy_month_offset
261
+ if [[ $month -ge 10 ]]; then
262
+ fy_month_offset=$((month - 10 + 1))
263
+ else
264
+ fy_month_offset=$((month + 3))
265
+ fi
266
+ local fq=$(( (fy_month_offset - 1) / 3 + 1 ))
267
+
268
+ echo "QUARTER_CONTEXT:"
269
+ echo " TODAY: $TODAY ($TODAY_DOW)"
270
+ echo " CALENDAR_QUARTER: Q$quarter $year"
271
+ echo " QUARTER_START: $q_start"
272
+ echo " QUARTER_END: $q_end"
273
+ echo " FISCAL_YEAR: FY$fy_year (Oct-Sep)"
274
+ echo " FISCAL_QUARTER: FQ$fq"
275
+ echo " FY_START: $fy_start"
276
+ echo " FY_END: $fy_end"
277
+ }
278
+
279
+ compute_relative() {
280
+ local expr=$1
281
+ local resolved
282
+ resolved=$(resolve_relative "$expr")
283
+ if [[ $? -ne 0 || -z "$resolved" ]]; then
284
+ echo "RELATIVE_DATE:"
285
+ echo " ERROR: Could not resolve '$expr'"
286
+ return 1
287
+ fi
288
+
289
+ local resolved_dow
290
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
291
+ resolved_dow=$(date -d "$resolved" "+%A")
292
+ else
293
+ local epoch=$(date_to_epoch "$resolved")
294
+ resolved_dow=$(date -r "$epoch" "+%A")
295
+ fi
296
+
297
+ echo "RELATIVE_DATE:"
298
+ echo " EXPRESSION: $expr"
299
+ echo " RESOLVED: $resolved ($resolved_dow)"
300
+ echo " TODAY: $TODAY ($TODAY_DOW)"
301
+ }
302
+
303
+ compute_iso() {
304
+ local iso_week iso_year day_of_year
305
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
306
+ iso_week=$(date -d "$TODAY" "+%V")
307
+ iso_year=$(date -d "$TODAY" "+%G")
308
+ day_of_year=$(date -d "$TODAY" "+%j")
309
+ else
310
+ iso_week=$(date -j -f "%Y-%m-%d" "$TODAY" "+%V" 2>/dev/null)
311
+ iso_year=$(date -j -f "%Y-%m-%d" "$TODAY" "+%G" 2>/dev/null || echo "$TODAY" | cut -d'-' -f1)
312
+ day_of_year=$(date -j -f "%Y-%m-%d" "$TODAY" "+%j" 2>/dev/null)
313
+ fi
314
+
315
+ echo "ISO_CONTEXT:"
316
+ echo " TODAY: $TODAY ($TODAY_DOW)"
317
+ echo " ISO_WEEK: W$iso_week"
318
+ echo " ISO_YEAR: $iso_year"
319
+ echo " ISO_WEEKDATE: $iso_year-W$iso_week-$DOW_NUM"
320
+ echo " DAY_OF_YEAR: $day_of_year"
321
+ }
322
+
323
+ compute_epoch() {
324
+ local epoch
325
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
326
+ epoch=$(date "+%s")
327
+ else
328
+ epoch=$(date "+%s")
329
+ fi
330
+ local millis="${epoch}000"
331
+
332
+ echo "EPOCH_CONTEXT:"
333
+ echo " TODAY: $TODAY ($TODAY_DOW)"
334
+ echo " UNIX_SECONDS: $epoch"
335
+ echo " UNIX_MILLIS: $millis"
336
+ }
337
+
338
+ compute_tz() {
339
+ local tz_abbrev tz_offset
340
+ tz_abbrev=$(date "+%Z")
341
+ tz_offset=$(date "+%z")
342
+
343
+ echo "TIMEZONE_CONTEXT:"
344
+ echo " TODAY: $TODAY ($TODAY_DOW)"
345
+ echo " TIMEZONE: $tz_abbrev"
346
+ echo " UTC_OFFSET: $tz_offset"
347
+ }
348
+
349
+ compute_range() {
350
+ local date1=$1
351
+ local date2=$2
352
+
353
+ # Validate date formats
354
+ if ! echo "$date1" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
355
+ echo "RANGE_ERROR: Invalid date format '$date1'. Use YYYY-MM-DD." >&2
356
+ return 1
357
+ fi
358
+ if ! echo "$date2" | grep -qE '^[0-9]{4}-[0-9]{2}-[0-9]{2}$'; then
359
+ echo "RANGE_ERROR: Invalid date format '$date2'. Use YYYY-MM-DD." >&2
360
+ return 1
361
+ fi
362
+
363
+ local epoch1 epoch2
364
+ epoch1=$(date_to_epoch "$date1")
365
+ epoch2=$(date_to_epoch "$date2")
366
+
367
+ if [[ -z "$epoch1" || -z "$epoch2" ]]; then
368
+ echo "RANGE_ERROR: Could not parse dates." >&2
369
+ return 1
370
+ fi
371
+
372
+ local diff_seconds=$((epoch2 - epoch1))
373
+ local calendar_days=$((diff_seconds / 86400))
374
+
375
+ # Count business days (Mon-Fri)
376
+ local business_days=0
377
+ local current="$date1"
378
+ local step=1
379
+ if [[ $calendar_days -lt 0 ]]; then
380
+ step=-1
381
+ calendar_days=$(( -calendar_days ))
382
+ fi
383
+
384
+ local i=0
385
+ while [[ $i -lt $calendar_days ]]; do
386
+ local dow
387
+ if [[ "$DATE_FLAVOR" == "gnu" ]]; then
388
+ dow=$(date -d "$current" "+%u")
389
+ else
390
+ local e=$(date_to_epoch "$current")
391
+ dow=$(date -r "$e" "+%u")
392
+ fi
393
+ # Count if weekday (Mon=1 through Fri=5)
394
+ if [[ $dow -ge 1 && $dow -le 5 ]]; then
395
+ business_days=$((business_days + 1))
396
+ fi
397
+ current=$(date_add "$current" "$step")
398
+ i=$((i + 1))
399
+ done
400
+
401
+ echo "RANGE_CONTEXT:"
402
+ echo " FROM: $date1"
403
+ echo " TO: $date2"
404
+ echo " CALENDAR_DAYS: $calendar_days"
405
+ echo " BUSINESS_DAYS: $business_days"
406
+ }
407
+
408
+ usage() {
409
+ echo "Usage: chronos.sh [SUBCOMMAND] [ARGS]"
410
+ echo ""
411
+ echo "Subcommands:"
412
+ echo " (none), week Week boundaries (DATE_CONTEXT block)"
413
+ echo " month Current/previous month boundaries"
414
+ echo " quarter Calendar quarter, fiscal year (Oct-Sep)"
415
+ echo " relative \"expr\" Resolve relative date expression"
416
+ echo " iso ISO week number, year, day-of-year"
417
+ echo " epoch Unix timestamp (seconds and milliseconds)"
418
+ echo " tz Timezone abbreviation and UTC offset"
419
+ echo " range D1 D2 Calendar and business days between dates"
420
+ echo " all All of the above combined"
421
+ echo ""
422
+ echo "Examples:"
423
+ echo " chronos.sh # Week context (default)"
424
+ echo " chronos.sh month # Month boundaries"
425
+ echo " chronos.sh quarter # Quarter and fiscal year"
426
+ echo " chronos.sh relative \"3 days ago\" # Resolve expression"
427
+ echo " chronos.sh range 2026-01-01 2026-02-01"
428
+ echo " chronos.sh all # Everything"
429
+ }
430
+
431
+ # ---- Main dispatch ----
432
+
433
+ case "${1:-week}" in
434
+ -h|--help)
435
+ usage
436
+ ;;
437
+ week|"")
438
+ compute_week
439
+ ;;
440
+ month)
441
+ compute_month
442
+ ;;
443
+ quarter)
444
+ compute_quarter
445
+ ;;
446
+ relative)
447
+ if [[ -z "${2:-}" ]]; then
448
+ echo "Usage: chronos.sh relative \"expression\"" >&2
449
+ exit 1
450
+ fi
451
+ compute_relative "$2"
452
+ ;;
453
+ iso)
454
+ compute_iso
455
+ ;;
456
+ epoch)
457
+ compute_epoch
458
+ ;;
459
+ tz)
460
+ compute_tz
461
+ ;;
462
+ range)
463
+ if [[ -z "${2:-}" || -z "${3:-}" ]]; then
464
+ echo "Usage: chronos.sh range YYYY-MM-DD YYYY-MM-DD" >&2
465
+ exit 1
466
+ fi
467
+ compute_range "$2" "$3"
468
+ ;;
469
+ all)
470
+ compute_week
471
+ echo ""
472
+ compute_month
473
+ echo ""
474
+ compute_quarter
475
+ echo ""
476
+ compute_iso
477
+ echo ""
478
+ compute_epoch
479
+ echo ""
480
+ compute_tz
481
+ ;;
482
+ *)
483
+ echo "Unknown subcommand: $1" >&2
484
+ usage >&2
485
+ exit 1
486
+ ;;
487
+ esac
@@ -0,0 +1,148 @@
1
+ /**
2
+ * chronos — Authoritative date and time context from system clock
3
+ *
4
+ * Registers a `chronos` tool that executes the chronos.sh script and returns
5
+ * structured date context. Eliminates AI date calculation errors by providing
6
+ * an authoritative source of truth from the system clock.
7
+ *
8
+ * Also registers a `/chronos` command for interactive use.
9
+ *
10
+ * Subcommands: week (default), month, quarter, relative, iso, epoch, tz, range, all
11
+ */
12
+
13
+ import { existsSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { StringEnum } from "../lib/typebox-helpers";
16
+ import type { ExtensionAPI } from "@cwilson613/pi-coding-agent";
17
+ import { Type } from "@sinclair/typebox";
18
+
19
+ const CHRONOS_SH = join(import.meta.dirname ?? __dirname, "chronos.sh");
20
+
21
+ const SUBCOMMANDS = ["week", "month", "quarter", "relative", "iso", "epoch", "tz", "range", "all"] as const;
22
+
23
+ export default function chronosExtension(pi: ExtensionAPI) {
24
+
25
+ // Ensure the script exists and is executable
26
+ if (!existsSync(CHRONOS_SH)) {
27
+ // Fail silently at load — the tool will report the error at call time
28
+ }
29
+
30
+ // ------------------------------------------------------------------
31
+ // chronos tool — callable by the LLM
32
+ // ------------------------------------------------------------------
33
+ pi.registerTool({
34
+ name: "chronos",
35
+ label: "Chronos",
36
+ description:
37
+ "Get authoritative date and time context from the system clock. " +
38
+ "Use before any date calculations, weekly/monthly reporting, relative date references, " +
39
+ "quarter boundaries, or epoch timestamps. Eliminates AI date calculation errors.\n\n" +
40
+ "Subcommands:\n" +
41
+ " week (default) — Current/previous week boundaries (Mon-Fri)\n" +
42
+ " month — Current/previous month boundaries\n" +
43
+ " quarter — Calendar quarter, fiscal year (Oct-Sep)\n" +
44
+ " relative — Resolve expression like '3 days ago', 'next Monday'\n" +
45
+ " iso — ISO 8601 week number, year, day-of-year\n" +
46
+ " epoch — Unix timestamp (seconds and milliseconds)\n" +
47
+ " tz — Timezone abbreviation and UTC offset\n" +
48
+ " range — Calendar and business days between two dates\n" +
49
+ " all — All of the above combined",
50
+ promptSnippet:
51
+ "Authoritative date/time from system clock — use before any date calculations, reporting, or relative date references",
52
+ promptGuidelines: [
53
+ "Call chronos before any date math, weekly/monthly reporting, relative dates, or quarter references — never calculate dates manually",
54
+ ],
55
+
56
+ parameters: Type.Object({
57
+ subcommand: Type.Optional(
58
+ StringEnum(SUBCOMMANDS, { description: "Subcommand (default: week)" })
59
+ ),
60
+ expression: Type.Optional(
61
+ Type.String({ description: "For 'relative': date expression (e.g. '3 days ago', 'next Monday')" })
62
+ ),
63
+ from_date: Type.Optional(
64
+ Type.String({ description: "For 'range': start date YYYY-MM-DD" })
65
+ ),
66
+ to_date: Type.Optional(
67
+ Type.String({ description: "For 'range': end date YYYY-MM-DD" })
68
+ ),
69
+ }),
70
+
71
+ async execute(_toolCallId, params, signal, _onUpdate, _ctx) {
72
+ if (!existsSync(CHRONOS_SH)) {
73
+ throw new Error(
74
+ `chronos.sh not found at ${CHRONOS_SH}. ` +
75
+ `Expected alongside the chronos skill.`
76
+ );
77
+ }
78
+
79
+ const sub = params.subcommand || "week";
80
+ const args = [CHRONOS_SH, sub];
81
+
82
+ if (sub === "relative") {
83
+ if (!params.expression) {
84
+ throw new Error("The 'relative' subcommand requires an 'expression' parameter (e.g. '3 days ago').");
85
+ }
86
+ args.push(params.expression);
87
+ } else if (sub === "range") {
88
+ if (!params.from_date || !params.to_date) {
89
+ throw new Error("The 'range' subcommand requires both 'from_date' and 'to_date' (YYYY-MM-DD).");
90
+ }
91
+ args.push(params.from_date, params.to_date);
92
+ }
93
+
94
+ const result = await pi.exec("bash", args, { signal, timeout: 10_000 });
95
+
96
+ if (result.code !== 0) {
97
+ throw new Error(`chronos.sh failed (exit ${result.code}):\n${result.stderr || result.stdout}`);
98
+ }
99
+
100
+ return {
101
+ content: [{ type: "text", text: result.stdout.trim() }],
102
+ details: { subcommand: sub },
103
+ };
104
+ },
105
+ });
106
+
107
+ // ------------------------------------------------------------------
108
+ // /chronos command — interactive shortcut
109
+ // ------------------------------------------------------------------
110
+ pi.registerCommand("chronos", {
111
+ description: "Show date/time context (usage: /chronos [week|month|quarter|iso|epoch|tz|all])",
112
+ getArgumentCompletions: (prefix: string) => {
113
+ const items = SUBCOMMANDS.map((s) => ({ value: s, label: s }));
114
+ const filtered = items.filter((i) => i.value.startsWith(prefix || ""));
115
+ return filtered.length > 0 ? filtered : null;
116
+ },
117
+ handler: async (args, _ctx) => {
118
+ const sub = (args || "").trim() || "week";
119
+
120
+ if (!existsSync(CHRONOS_SH)) {
121
+ pi.sendMessage({
122
+ customType: "view",
123
+ content: `❌ chronos.sh not found at \`${CHRONOS_SH}\``,
124
+ display: true,
125
+ });
126
+ return;
127
+ }
128
+
129
+ const cliArgs = [CHRONOS_SH, sub];
130
+ const result = await pi.exec("bash", cliArgs, { timeout: 10_000 });
131
+
132
+ if (result.code !== 0) {
133
+ pi.sendMessage({
134
+ customType: "view",
135
+ content: `❌ chronos.sh failed:\n\`\`\`\n${result.stderr || result.stdout}\n\`\`\``,
136
+ display: true,
137
+ });
138
+ return;
139
+ }
140
+
141
+ pi.sendMessage({
142
+ customType: "view",
143
+ content: `**Chronos**\n\n\`\`\`\n${result.stdout.trim()}\n\`\`\``,
144
+ display: true,
145
+ });
146
+ },
147
+ });
148
+ }