@hera-al/server 1.6.12 → 1.6.13
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/bundled/a2ui/SKILL.md +339 -0
- package/bundled/buongiorno/SKILL.md +151 -0
- package/bundled/council/SKILL.md +168 -0
- package/bundled/council/scripts/council.mjs +202 -0
- package/bundled/dreaming/SKILL.md +177 -0
- package/bundled/google-workspace/SKILL.md +229 -0
- package/bundled/google-workspace/scripts/auth.sh +87 -0
- package/bundled/google-workspace/scripts/calendar.sh +508 -0
- package/bundled/google-workspace/scripts/drive.sh +459 -0
- package/bundled/google-workspace/scripts/gmail.sh +452 -0
- package/bundled/humanizer/SKILL.md +488 -0
- package/bundled/librarian/SKILL.md +155 -0
- package/bundled/plasma/SKILL.md +1417 -0
- package/bundled/sera/SKILL.md +143 -0
- package/bundled/the-skill-guardian/SKILL.md +103 -0
- package/bundled/the-skill-guardian/scripts/scan.sh +314 -0
- package/bundled/unix-time/SKILL.md +58 -0
- package/bundled/wandering/SKILL.md +174 -0
- package/bundled/xai-search/SKILL.md +91 -0
- package/bundled/xai-search/scripts/search.sh +197 -0
- package/dist/a2ui/parser.d.ts +76 -0
- package/dist/a2ui/parser.js +1 -0
- package/dist/a2ui/types.d.ts +147 -0
- package/dist/a2ui/types.js +1 -0
- package/dist/a2ui/validator.d.ts +32 -0
- package/dist/a2ui/validator.js +1 -0
- package/dist/agent/agent-service.d.ts +17 -11
- package/dist/agent/agent-service.js +1 -1
- package/dist/agent/session-agent.d.ts +1 -1
- package/dist/agent/session-agent.js +1 -1
- package/dist/agent/session-error-handler.js +1 -1
- package/dist/commands/debuga2ui.d.ts +13 -0
- package/dist/commands/debuga2ui.js +1 -0
- package/dist/commands/debugdynamic.d.ts +13 -0
- package/dist/commands/debugdynamic.js +1 -0
- package/dist/commands/mcp.d.ts +6 -3
- package/dist/commands/mcp.js +1 -1
- package/dist/gateway/node-registry.d.ts +29 -1
- package/dist/gateway/node-registry.js +1 -1
- package/dist/installer/hera.js +1 -1
- package/dist/memory/concept-store.d.ts +109 -0
- package/dist/memory/concept-store.js +1 -0
- package/dist/nostromo/nostromo.js +1 -1
- package/dist/server.d.ts +3 -2
- package/dist/server.js +1 -1
- package/dist/tools/a2ui-tools.d.ts +23 -0
- package/dist/tools/a2ui-tools.js +1 -0
- package/dist/tools/concept-tools.d.ts +3 -0
- package/dist/tools/concept-tools.js +1 -0
- package/dist/tools/dynamic-ui-tools.d.ts +25 -0
- package/dist/tools/dynamic-ui-tools.js +1 -0
- package/dist/tools/node-tools.js +1 -1
- package/dist/tools/plasma-client-tools.d.ts +28 -0
- package/dist/tools/plasma-client-tools.js +1 -0
- package/installationPkg/AGENTS.md +168 -22
- package/installationPkg/SOUL.md +56 -0
- package/installationPkg/TOOLS.md +126 -0
- package/installationPkg/USER.md +54 -1
- package/installationPkg/config.example.yaml +145 -34
- package/installationPkg/default-jobs.json +77 -0
- package/package.json +3 -2
|
@@ -0,0 +1,508 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Google Calendar API client — OAuth2 REST API v3
|
|
3
|
+
# Usage: calendar.sh <command> [options]
|
|
4
|
+
#
|
|
5
|
+
# Commands:
|
|
6
|
+
# list [--max N] [--cal ID] List today's events
|
|
7
|
+
# upcoming [--days N] [--max N] [--cal ID] List upcoming events
|
|
8
|
+
# search "query" [--max N] [--cal ID] Search events by text
|
|
9
|
+
# get <eventId> [--cal ID] Get event details
|
|
10
|
+
# create --summary TITLE --start ISO --end ISO Create event
|
|
11
|
+
# update <eventId> [--summary T] [--start ISO] Update event
|
|
12
|
+
# delete <eventId> [--cal ID] Delete event
|
|
13
|
+
# calendars List available calendars
|
|
14
|
+
# today [--cal ID] Alias for list (today's events)
|
|
15
|
+
# week [--cal ID] Events for this week
|
|
16
|
+
|
|
17
|
+
set -euo pipefail
|
|
18
|
+
|
|
19
|
+
# --- Config ---
|
|
20
|
+
API_BASE="https://www.googleapis.com/calendar/v3"
|
|
21
|
+
TOKEN_FILE="${HOME}/.gmab-google-token.json"
|
|
22
|
+
|
|
23
|
+
# --- Token management (shared pattern) ---
|
|
24
|
+
get_access_token() {
|
|
25
|
+
local client_id="${GOOGLE_CLIENT_ID:-}"
|
|
26
|
+
local client_secret="${GOOGLE_CLIENT_SECRET:-}"
|
|
27
|
+
local refresh_token="${GOOGLE_REFRESH_TOKEN:-}"
|
|
28
|
+
|
|
29
|
+
if [[ -z "$refresh_token" && -f "$TOKEN_FILE" ]]; then
|
|
30
|
+
client_id=$(jq -r '.client_id // empty' "$TOKEN_FILE" 2>/dev/null || true)
|
|
31
|
+
client_secret=$(jq -r '.client_secret // empty' "$TOKEN_FILE" 2>/dev/null || true)
|
|
32
|
+
refresh_token=$(jq -r '.refresh_token // empty' "$TOKEN_FILE" 2>/dev/null || true)
|
|
33
|
+
fi
|
|
34
|
+
|
|
35
|
+
if [[ -z "$client_id" || -z "$client_secret" || -z "$refresh_token" ]]; then
|
|
36
|
+
echo "ERROR: Google credentials not configured." >&2
|
|
37
|
+
echo "Set GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN" >&2
|
|
38
|
+
echo "Or run auth.sh to set up OAuth2." >&2
|
|
39
|
+
exit 1
|
|
40
|
+
fi
|
|
41
|
+
|
|
42
|
+
if [[ -f "$TOKEN_FILE" ]]; then
|
|
43
|
+
local cached_token cached_expiry
|
|
44
|
+
cached_token=$(jq -r '.access_token // empty' "$TOKEN_FILE" 2>/dev/null || true)
|
|
45
|
+
cached_expiry=$(jq -r '.expires_at // 0' "$TOKEN_FILE" 2>/dev/null || true)
|
|
46
|
+
local now
|
|
47
|
+
now=$(date +%s)
|
|
48
|
+
if [[ -n "$cached_token" && "$cached_expiry" -gt "$now" ]]; then
|
|
49
|
+
echo "$cached_token"
|
|
50
|
+
return
|
|
51
|
+
fi
|
|
52
|
+
fi
|
|
53
|
+
|
|
54
|
+
local response
|
|
55
|
+
response=$(curl -s "https://oauth2.googleapis.com/token" \
|
|
56
|
+
-d "client_id=$client_id" \
|
|
57
|
+
-d "client_secret=$client_secret" \
|
|
58
|
+
-d "refresh_token=$refresh_token" \
|
|
59
|
+
-d "grant_type=refresh_token")
|
|
60
|
+
|
|
61
|
+
local access_token
|
|
62
|
+
access_token=$(echo "$response" | jq -r '.access_token // empty')
|
|
63
|
+
local expires_in
|
|
64
|
+
expires_in=$(echo "$response" | jq -r '.expires_in // 3600')
|
|
65
|
+
|
|
66
|
+
if [[ -z "$access_token" ]]; then
|
|
67
|
+
echo "ERROR: Failed to refresh access token" >&2
|
|
68
|
+
echo "$response" >&2
|
|
69
|
+
exit 1
|
|
70
|
+
fi
|
|
71
|
+
|
|
72
|
+
local expires_at
|
|
73
|
+
expires_at=$(($(date +%s) + expires_in - 60))
|
|
74
|
+
|
|
75
|
+
if [[ -f "$TOKEN_FILE" ]]; then
|
|
76
|
+
local tmp
|
|
77
|
+
tmp=$(jq --arg at "$access_token" --argjson ea "$expires_at" \
|
|
78
|
+
'. + {access_token: $at, expires_at: $ea}' "$TOKEN_FILE")
|
|
79
|
+
echo "$tmp" > "$TOKEN_FILE"
|
|
80
|
+
else
|
|
81
|
+
jq -n \
|
|
82
|
+
--arg ci "$client_id" \
|
|
83
|
+
--arg cs "$client_secret" \
|
|
84
|
+
--arg rt "$refresh_token" \
|
|
85
|
+
--arg at "$access_token" \
|
|
86
|
+
--argjson ea "$expires_at" \
|
|
87
|
+
'{client_id: $ci, client_secret: $cs, refresh_token: $rt, access_token: $at, expires_at: $ea}' \
|
|
88
|
+
> "$TOKEN_FILE"
|
|
89
|
+
fi
|
|
90
|
+
chmod 600 "$TOKEN_FILE"
|
|
91
|
+
|
|
92
|
+
echo "$access_token"
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
# --- API helpers ---
|
|
96
|
+
api_get() {
|
|
97
|
+
local path="$1"
|
|
98
|
+
local token
|
|
99
|
+
token=$(get_access_token)
|
|
100
|
+
curl -s -H "Authorization: Bearer $token" "${API_BASE}${path}"
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
api_post() {
|
|
104
|
+
local path="$1"
|
|
105
|
+
local data="${2:-}"
|
|
106
|
+
local token
|
|
107
|
+
token=$(get_access_token)
|
|
108
|
+
if [[ -n "$data" ]]; then
|
|
109
|
+
curl -s -H "Authorization: Bearer $token" \
|
|
110
|
+
-H "Content-Type: application/json" \
|
|
111
|
+
-d "$data" "${API_BASE}${path}"
|
|
112
|
+
else
|
|
113
|
+
curl -s -X POST -H "Authorization: Bearer $token" "${API_BASE}${path}"
|
|
114
|
+
fi
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
api_patch() {
|
|
118
|
+
local path="$1"
|
|
119
|
+
local data="$2"
|
|
120
|
+
local token
|
|
121
|
+
token=$(get_access_token)
|
|
122
|
+
curl -s -X PATCH -H "Authorization: Bearer $token" \
|
|
123
|
+
-H "Content-Type: application/json" \
|
|
124
|
+
-d "$data" "${API_BASE}${path}"
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
api_delete() {
|
|
128
|
+
local path="$1"
|
|
129
|
+
local token
|
|
130
|
+
token=$(get_access_token)
|
|
131
|
+
curl -s -X DELETE -H "Authorization: Bearer $token" "${API_BASE}${path}"
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# --- Date helpers ---
|
|
135
|
+
# Get ISO date for start of today (local timezone)
|
|
136
|
+
today_start() {
|
|
137
|
+
if date -v0H >/dev/null 2>&1; then
|
|
138
|
+
# BSD date (macOS)
|
|
139
|
+
date -u -v0H -v0M -v0S "+%Y-%m-%dT%H:%M:%SZ"
|
|
140
|
+
else
|
|
141
|
+
# GNU date
|
|
142
|
+
date -u -d "$(date +%Y-%m-%d) 00:00:00" "+%Y-%m-%dT%H:%M:%SZ"
|
|
143
|
+
fi
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
today_end() {
|
|
147
|
+
if date -v0H >/dev/null 2>&1; then
|
|
148
|
+
date -u -v23H -v59M -v59S "+%Y-%m-%dT%H:%M:%SZ"
|
|
149
|
+
else
|
|
150
|
+
date -u -d "$(date +%Y-%m-%d) 23:59:59" "+%Y-%m-%dT%H:%M:%SZ"
|
|
151
|
+
fi
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
days_from_now() {
|
|
155
|
+
local n="$1"
|
|
156
|
+
if date -v0H >/dev/null 2>&1; then
|
|
157
|
+
date -u -v+"${n}d" -v23H -v59M -v59S "+%Y-%m-%dT%H:%M:%SZ"
|
|
158
|
+
else
|
|
159
|
+
date -u -d "+${n} days 23:59:59" "+%Y-%m-%dT%H:%M:%SZ"
|
|
160
|
+
fi
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
week_end() {
|
|
164
|
+
days_from_now 7
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
# --- Format helpers ---
|
|
168
|
+
format_event() {
|
|
169
|
+
jq -r '
|
|
170
|
+
def fmt_time:
|
|
171
|
+
if .dateTime then (.dateTime | split("T") | .[1] | split("+")[0] | split("-")[0] | .[0:5])
|
|
172
|
+
elif .date then "all-day"
|
|
173
|
+
else "?"
|
|
174
|
+
end;
|
|
175
|
+
|
|
176
|
+
" \(.start | fmt_time) — \(.end | fmt_time) \(.summary // "(no title)")" +
|
|
177
|
+
(if .location then "\n 📍 \(.location)" else "" end) +
|
|
178
|
+
(if .description then "\n 📝 \(.description | .[0:100])" else "" end) +
|
|
179
|
+
"\n 🔑 \(.id)"
|
|
180
|
+
'
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
format_events_list() {
|
|
184
|
+
jq -r '
|
|
185
|
+
def fmt_time:
|
|
186
|
+
if .dateTime then (.dateTime | split("T") | .[1] | split("+")[0] | split("-")[0] | .[0:5])
|
|
187
|
+
elif .date then "all-day"
|
|
188
|
+
else "?"
|
|
189
|
+
end;
|
|
190
|
+
|
|
191
|
+
if (.items // []) | length == 0 then
|
|
192
|
+
"No events found."
|
|
193
|
+
else
|
|
194
|
+
(.items[] |
|
|
195
|
+
" \(.start | fmt_time) — \(.end | fmt_time) \(.summary // "(no title)")" +
|
|
196
|
+
(if .location then " 📍 \(.location)" else "" end) +
|
|
197
|
+
" [\(.id)]"
|
|
198
|
+
)
|
|
199
|
+
end
|
|
200
|
+
'
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# --- Commands ---
|
|
204
|
+
|
|
205
|
+
cmd_calendars() {
|
|
206
|
+
api_get "/users/me/calendarList" | jq -r '
|
|
207
|
+
.items[] |
|
|
208
|
+
" \(.summary)\t(\(.id))" +
|
|
209
|
+
(if .primary then " ⭐" else "" end)
|
|
210
|
+
'
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
cmd_list() {
|
|
214
|
+
local max=10
|
|
215
|
+
local cal="primary"
|
|
216
|
+
|
|
217
|
+
while [[ $# -gt 0 ]]; do
|
|
218
|
+
case "$1" in
|
|
219
|
+
--max) max="$2"; shift 2 ;;
|
|
220
|
+
--cal) cal="$2"; shift 2 ;;
|
|
221
|
+
*) shift ;;
|
|
222
|
+
esac
|
|
223
|
+
done
|
|
224
|
+
|
|
225
|
+
local time_min time_max
|
|
226
|
+
time_min=$(today_start)
|
|
227
|
+
time_max=$(today_end)
|
|
228
|
+
|
|
229
|
+
local encoded_min encoded_max
|
|
230
|
+
encoded_min=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$time_min'))")
|
|
231
|
+
encoded_max=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$time_max'))")
|
|
232
|
+
|
|
233
|
+
api_get "/calendars/${cal}/events?timeMin=${encoded_min}&timeMax=${encoded_max}&maxResults=${max}&singleEvents=true&orderBy=startTime" \
|
|
234
|
+
| format_events_list
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
cmd_today() {
|
|
238
|
+
cmd_list "$@"
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
cmd_upcoming() {
|
|
242
|
+
local max=10
|
|
243
|
+
local days=7
|
|
244
|
+
local cal="primary"
|
|
245
|
+
|
|
246
|
+
while [[ $# -gt 0 ]]; do
|
|
247
|
+
case "$1" in
|
|
248
|
+
--max) max="$2"; shift 2 ;;
|
|
249
|
+
--days) days="$2"; shift 2 ;;
|
|
250
|
+
--cal) cal="$2"; shift 2 ;;
|
|
251
|
+
*) shift ;;
|
|
252
|
+
esac
|
|
253
|
+
done
|
|
254
|
+
|
|
255
|
+
local time_min time_max
|
|
256
|
+
time_min=$(today_start)
|
|
257
|
+
time_max=$(days_from_now "$days")
|
|
258
|
+
|
|
259
|
+
local encoded_min encoded_max
|
|
260
|
+
encoded_min=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$time_min'))")
|
|
261
|
+
encoded_max=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$time_max'))")
|
|
262
|
+
|
|
263
|
+
api_get "/calendars/${cal}/events?timeMin=${encoded_min}&timeMax=${encoded_max}&maxResults=${max}&singleEvents=true&orderBy=startTime" \
|
|
264
|
+
| format_events_list
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
cmd_week() {
|
|
268
|
+
cmd_upcoming --days 7 "$@"
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
cmd_search() {
|
|
272
|
+
local query="${1:-}"
|
|
273
|
+
shift || true
|
|
274
|
+
|
|
275
|
+
if [[ -z "$query" ]]; then
|
|
276
|
+
echo "ERROR: Search query required" >&2
|
|
277
|
+
echo "Usage: calendar.sh search \"query\" [--max N]" >&2
|
|
278
|
+
exit 1
|
|
279
|
+
fi
|
|
280
|
+
|
|
281
|
+
local max=10
|
|
282
|
+
local cal="primary"
|
|
283
|
+
|
|
284
|
+
while [[ $# -gt 0 ]]; do
|
|
285
|
+
case "$1" in
|
|
286
|
+
--max) max="$2"; shift 2 ;;
|
|
287
|
+
--cal) cal="$2"; shift 2 ;;
|
|
288
|
+
*) shift ;;
|
|
289
|
+
esac
|
|
290
|
+
done
|
|
291
|
+
|
|
292
|
+
local encoded_query
|
|
293
|
+
encoded_query=$(python3 -c "import urllib.parse; print(urllib.parse.quote('$query'))")
|
|
294
|
+
|
|
295
|
+
api_get "/calendars/${cal}/events?q=${encoded_query}&maxResults=${max}&singleEvents=true&orderBy=startTime" \
|
|
296
|
+
| format_events_list
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
cmd_get() {
|
|
300
|
+
local event_id="${1:-}"
|
|
301
|
+
shift || true
|
|
302
|
+
|
|
303
|
+
if [[ -z "$event_id" ]]; then
|
|
304
|
+
echo "ERROR: Event ID required" >&2
|
|
305
|
+
exit 1
|
|
306
|
+
fi
|
|
307
|
+
|
|
308
|
+
local cal="primary"
|
|
309
|
+
while [[ $# -gt 0 ]]; do
|
|
310
|
+
case "$1" in
|
|
311
|
+
--cal) cal="$2"; shift 2 ;;
|
|
312
|
+
*) shift ;;
|
|
313
|
+
esac
|
|
314
|
+
done
|
|
315
|
+
|
|
316
|
+
api_get "/calendars/${cal}/events/${event_id}" | format_event
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
cmd_create() {
|
|
320
|
+
local summary="" start="" end="" location="" description="" cal="primary"
|
|
321
|
+
local all_day=false
|
|
322
|
+
|
|
323
|
+
while [[ $# -gt 0 ]]; do
|
|
324
|
+
case "$1" in
|
|
325
|
+
--summary) summary="$2"; shift 2 ;;
|
|
326
|
+
--start) start="$2"; shift 2 ;;
|
|
327
|
+
--end) end="$2"; shift 2 ;;
|
|
328
|
+
--location) location="$2"; shift 2 ;;
|
|
329
|
+
--description) description="$2"; shift 2 ;;
|
|
330
|
+
--cal) cal="$2"; shift 2 ;;
|
|
331
|
+
--all-day) all_day=true; shift ;;
|
|
332
|
+
*) shift ;;
|
|
333
|
+
esac
|
|
334
|
+
done
|
|
335
|
+
|
|
336
|
+
if [[ -z "$summary" ]]; then
|
|
337
|
+
echo "ERROR: --summary is required" >&2
|
|
338
|
+
exit 1
|
|
339
|
+
fi
|
|
340
|
+
if [[ -z "$start" ]]; then
|
|
341
|
+
echo "ERROR: --start is required (ISO 8601 datetime or YYYY-MM-DD for all-day)" >&2
|
|
342
|
+
exit 1
|
|
343
|
+
fi
|
|
344
|
+
|
|
345
|
+
# Build event JSON
|
|
346
|
+
local event_json
|
|
347
|
+
if [[ "$all_day" == true ]]; then
|
|
348
|
+
# All-day event: use date format
|
|
349
|
+
local end_date="${end:-$start}"
|
|
350
|
+
event_json=$(jq -n \
|
|
351
|
+
--arg summary "$summary" \
|
|
352
|
+
--arg start "$start" \
|
|
353
|
+
--arg e "$end_date" \
|
|
354
|
+
'{summary: $summary, start: {date: $start}, "end": {date: $e}}')
|
|
355
|
+
else
|
|
356
|
+
# Timed event: use dateTime
|
|
357
|
+
if [[ -z "$end" ]]; then
|
|
358
|
+
# Default: 1 hour after start
|
|
359
|
+
if date -v0H >/dev/null 2>&1; then
|
|
360
|
+
end=$(date -j -f "%Y-%m-%dT%H:%M:%S" "${start%%[+-]*}" -v+1H "+%Y-%m-%dT%H:%M:%S${start##*[0-9]}" 2>/dev/null || echo "$start")
|
|
361
|
+
else
|
|
362
|
+
end=$(date -d "${start} + 1 hour" --iso-8601=seconds 2>/dev/null || echo "$start")
|
|
363
|
+
fi
|
|
364
|
+
fi
|
|
365
|
+
event_json=$(jq -n \
|
|
366
|
+
--arg summary "$summary" \
|
|
367
|
+
--arg start "$start" \
|
|
368
|
+
--arg e "$end" \
|
|
369
|
+
'{summary: $summary, start: {dateTime: $start}, "end": {dateTime: $e}}')
|
|
370
|
+
fi
|
|
371
|
+
|
|
372
|
+
# Add optional fields
|
|
373
|
+
if [[ -n "$location" ]]; then
|
|
374
|
+
event_json=$(echo "$event_json" | jq --arg loc "$location" '. + {location: $loc}')
|
|
375
|
+
fi
|
|
376
|
+
if [[ -n "$description" ]]; then
|
|
377
|
+
event_json=$(echo "$event_json" | jq --arg desc "$description" '. + {description: $desc}')
|
|
378
|
+
fi
|
|
379
|
+
|
|
380
|
+
local result
|
|
381
|
+
result=$(api_post "/calendars/${cal}/events" "$event_json")
|
|
382
|
+
|
|
383
|
+
local created_id
|
|
384
|
+
created_id=$(echo "$result" | jq -r '.id // empty')
|
|
385
|
+
if [[ -n "$created_id" ]]; then
|
|
386
|
+
echo "Event created: $created_id"
|
|
387
|
+
echo "$result" | format_event
|
|
388
|
+
else
|
|
389
|
+
echo "ERROR: Failed to create event" >&2
|
|
390
|
+
echo "$result" >&2
|
|
391
|
+
exit 1
|
|
392
|
+
fi
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
cmd_update() {
|
|
396
|
+
local event_id="${1:-}"
|
|
397
|
+
shift || true
|
|
398
|
+
|
|
399
|
+
if [[ -z "$event_id" ]]; then
|
|
400
|
+
echo "ERROR: Event ID required" >&2
|
|
401
|
+
exit 1
|
|
402
|
+
fi
|
|
403
|
+
|
|
404
|
+
local summary="" start="" end="" location="" description="" cal="primary"
|
|
405
|
+
|
|
406
|
+
while [[ $# -gt 0 ]]; do
|
|
407
|
+
case "$1" in
|
|
408
|
+
--summary) summary="$2"; shift 2 ;;
|
|
409
|
+
--start) start="$2"; shift 2 ;;
|
|
410
|
+
--end) end="$2"; shift 2 ;;
|
|
411
|
+
--location) location="$2"; shift 2 ;;
|
|
412
|
+
--description) description="$2"; shift 2 ;;
|
|
413
|
+
--cal) cal="$2"; shift 2 ;;
|
|
414
|
+
*) shift ;;
|
|
415
|
+
esac
|
|
416
|
+
done
|
|
417
|
+
|
|
418
|
+
# Build patch JSON with only provided fields
|
|
419
|
+
local patch_json="{}"
|
|
420
|
+
if [[ -n "$summary" ]]; then
|
|
421
|
+
patch_json=$(echo "$patch_json" | jq --arg v "$summary" '. + {summary: $v}')
|
|
422
|
+
fi
|
|
423
|
+
if [[ -n "$start" ]]; then
|
|
424
|
+
patch_json=$(echo "$patch_json" | jq --arg v "$start" '. + {start: {dateTime: $v}}')
|
|
425
|
+
fi
|
|
426
|
+
if [[ -n "$end" ]]; then
|
|
427
|
+
patch_json=$(echo "$patch_json" | jq --arg v "$end" '. + {end: {dateTime: $v}}')
|
|
428
|
+
fi
|
|
429
|
+
if [[ -n "$location" ]]; then
|
|
430
|
+
patch_json=$(echo "$patch_json" | jq --arg v "$location" '. + {location: $v}')
|
|
431
|
+
fi
|
|
432
|
+
if [[ -n "$description" ]]; then
|
|
433
|
+
patch_json=$(echo "$patch_json" | jq --arg v "$description" '. + {description: $v}')
|
|
434
|
+
fi
|
|
435
|
+
|
|
436
|
+
if [[ "$patch_json" == "{}" ]]; then
|
|
437
|
+
echo "ERROR: No fields to update. Use --summary, --start, --end, --location, --description" >&2
|
|
438
|
+
exit 1
|
|
439
|
+
fi
|
|
440
|
+
|
|
441
|
+
local result
|
|
442
|
+
result=$(api_patch "/calendars/${cal}/events/${event_id}" "$patch_json")
|
|
443
|
+
|
|
444
|
+
local updated_id
|
|
445
|
+
updated_id=$(echo "$result" | jq -r '.id // empty')
|
|
446
|
+
if [[ -n "$updated_id" ]]; then
|
|
447
|
+
echo "Event updated: $updated_id"
|
|
448
|
+
echo "$result" | format_event
|
|
449
|
+
else
|
|
450
|
+
echo "ERROR: Failed to update event" >&2
|
|
451
|
+
echo "$result" >&2
|
|
452
|
+
exit 1
|
|
453
|
+
fi
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
cmd_delete() {
|
|
457
|
+
local event_id="${1:-}"
|
|
458
|
+
shift || true
|
|
459
|
+
|
|
460
|
+
if [[ -z "$event_id" ]]; then
|
|
461
|
+
echo "ERROR: Event ID required" >&2
|
|
462
|
+
exit 1
|
|
463
|
+
fi
|
|
464
|
+
|
|
465
|
+
local cal="primary"
|
|
466
|
+
while [[ $# -gt 0 ]]; do
|
|
467
|
+
case "$1" in
|
|
468
|
+
--cal) cal="$2"; shift 2 ;;
|
|
469
|
+
*) shift ;;
|
|
470
|
+
esac
|
|
471
|
+
done
|
|
472
|
+
|
|
473
|
+
api_delete "/calendars/${cal}/events/${event_id}"
|
|
474
|
+
echo "Event deleted: $event_id"
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
# --- Dispatch ---
|
|
478
|
+
COMMAND="${1:-}"
|
|
479
|
+
shift || true
|
|
480
|
+
|
|
481
|
+
case "$COMMAND" in
|
|
482
|
+
list) cmd_list "$@" ;;
|
|
483
|
+
today) cmd_today "$@" ;;
|
|
484
|
+
upcoming) cmd_upcoming "$@" ;;
|
|
485
|
+
week) cmd_week "$@" ;;
|
|
486
|
+
search) cmd_search "$@" ;;
|
|
487
|
+
get) cmd_get "$@" ;;
|
|
488
|
+
create) cmd_create "$@" ;;
|
|
489
|
+
update) cmd_update "$@" ;;
|
|
490
|
+
delete) cmd_delete "$@" ;;
|
|
491
|
+
calendars) cmd_calendars "$@" ;;
|
|
492
|
+
*)
|
|
493
|
+
echo "Usage: calendar.sh <command> [options]" >&2
|
|
494
|
+
echo "" >&2
|
|
495
|
+
echo "Commands:" >&2
|
|
496
|
+
echo " list [--max N] [--cal ID] Today's events" >&2
|
|
497
|
+
echo " today [--cal ID] Alias for list" >&2
|
|
498
|
+
echo " upcoming [--days N] [--max N] [--cal ID] Upcoming events" >&2
|
|
499
|
+
echo " week [--cal ID] This week's events" >&2
|
|
500
|
+
echo " search \"query\" [--max N] [--cal ID] Search events" >&2
|
|
501
|
+
echo " get <eventId> [--cal ID] Event details" >&2
|
|
502
|
+
echo " create --summary T --start ISO [--end ISO] Create event" >&2
|
|
503
|
+
echo " update <eventId> [--summary T] [--start ISO] Update event" >&2
|
|
504
|
+
echo " delete <eventId> [--cal ID] Delete event" >&2
|
|
505
|
+
echo " calendars List calendars" >&2
|
|
506
|
+
exit 1
|
|
507
|
+
;;
|
|
508
|
+
esac
|