@stacksjs/defaults 0.72.102 → 0.73.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.
- package/ai/AGENTS.md +25 -4
- package/ai/README.md +26 -4
- package/ai/skills/stacks-actions/SKILL.md +1 -1
- package/ai/skills/stacks-ai/SKILL.md +1 -1
- package/ai/skills/stacks-alias/SKILL.md +1 -1
- package/ai/skills/stacks-analytics/SKILL.md +1 -1
- package/ai/skills/stacks-api/SKILL.md +1 -1
- package/ai/skills/stacks-arrays/SKILL.md +1 -1
- package/ai/skills/stacks-auto-imports/SKILL.md +1 -1
- package/ai/skills/stacks-browse/SKILL.md +1 -1
- package/ai/skills/stacks-browser/SKILL.md +1 -1
- package/ai/skills/stacks-buddy/SKILL.md +1 -1
- package/ai/skills/stacks-build/SKILL.md +79 -5
- package/ai/skills/stacks-cache/SKILL.md +1 -1
- package/ai/skills/stacks-calendar/SKILL.md +1 -1
- package/ai/skills/stacks-chat/SKILL.md +1 -1
- package/ai/skills/stacks-cli/SKILL.md +1 -1
- package/ai/skills/stacks-cloud/SKILL.md +1 -1
- package/ai/skills/stacks-cms/SKILL.md +1 -1
- package/ai/skills/stacks-codebase-design/DEEPENING.md +79 -0
- package/ai/skills/stacks-codebase-design/DESIGN-IT-TWICE.md +72 -0
- package/ai/skills/stacks-codebase-design/SKILL.md +180 -0
- package/ai/skills/stacks-collections/SKILL.md +1 -1
- package/ai/skills/stacks-commerce/SKILL.md +141 -35
- package/ai/skills/stacks-composables/SKILL.md +1 -1
- package/ai/skills/stacks-config/SKILL.md +1 -1
- package/ai/skills/stacks-configuration/SKILL.md +1 -1
- package/ai/skills/stacks-cron/SKILL.md +1 -1
- package/ai/skills/stacks-crosswind/SKILL.md +1 -1
- package/ai/skills/stacks-database/SKILL.md +1 -1
- package/ai/skills/stacks-datetime/SKILL.md +1 -1
- package/ai/skills/stacks-dependencies/SKILL.md +1 -1
- package/ai/skills/stacks-deploy/SKILL.md +1 -1
- package/ai/skills/stacks-desktop/SKILL.md +1 -1
- package/ai/skills/stacks-development/SKILL.md +1 -1
- package/ai/skills/stacks-dns/SKILL.md +1 -1
- package/ai/skills/stacks-docs/SKILL.md +1 -1
- package/ai/skills/stacks-domain-modeling/FORMATS.md +123 -0
- package/ai/skills/stacks-domain-modeling/SKILL.md +109 -0
- package/ai/skills/stacks-enums/SKILL.md +1 -1
- package/ai/skills/stacks-error-handling/SKILL.md +1 -1
- package/ai/skills/stacks-events/SKILL.md +1 -1
- package/ai/skills/stacks-faker/SKILL.md +1 -1
- package/ai/skills/stacks-flow/PHASE-BOUNDARIES.md +91 -0
- package/ai/skills/stacks-flow/SKILL.md +117 -0
- package/ai/skills/stacks-git/SKILL.md +37 -9
- package/ai/skills/stacks-grilling/SKILL.md +85 -0
- package/ai/skills/stacks-guard/SKILL.md +86 -11
- package/ai/skills/stacks-guard/scripts/block-destructive.sh +67 -0
- package/ai/skills/stacks-handoff/SKILL.md +70 -0
- package/ai/skills/stacks-health/SKILL.md +1 -1
- package/ai/skills/stacks-http/SKILL.md +1 -1
- package/ai/skills/stacks-i18n/SKILL.md +1 -1
- package/ai/skills/stacks-investigate/SKILL.md +234 -106
- package/ai/skills/stacks-investigate/scripts/hitl-loop.template.sh +47 -0
- package/ai/skills/stacks-jobs/SKILL.md +1 -1
- package/ai/skills/stacks-listeners/SKILL.md +1 -1
- package/ai/skills/stacks-logging/SKILL.md +1 -1
- package/ai/skills/stacks-mail/SKILL.md +1 -1
- package/ai/skills/stacks-middleware/SKILL.md +1 -1
- package/ai/skills/stacks-migrations/SKILL.md +1 -1
- package/ai/skills/stacks-models/SKILL.md +1 -1
- package/ai/skills/stacks-new-feature/SKILL.md +65 -5
- package/ai/skills/stacks-notifications/SKILL.md +1 -1
- package/ai/skills/stacks-objects/SKILL.md +1 -1
- package/ai/skills/stacks-office-hours/SKILL.md +16 -2
- package/ai/skills/stacks-orm/SKILL.md +1 -1
- package/ai/skills/stacks-path/SKILL.md +1 -1
- package/ai/skills/stacks-payments/SKILL.md +35 -1
- package/ai/skills/stacks-plan-review/SKILL.md +27 -6
- package/ai/skills/stacks-plugins/SKILL.md +1 -1
- package/ai/skills/stacks-prototype/LOGIC.md +103 -0
- package/ai/skills/stacks-prototype/SKILL.md +66 -0
- package/ai/skills/stacks-prototype/UI.md +112 -0
- package/ai/skills/stacks-push/SKILL.md +1 -1
- package/ai/skills/stacks-query-builder/SKILL.md +1 -1
- package/ai/skills/stacks-queue/SKILL.md +1 -1
- package/ai/skills/stacks-realtime/SKILL.md +1 -1
- package/ai/skills/stacks-registry/SKILL.md +1 -1
- package/ai/skills/stacks-repl/SKILL.md +1 -1
- package/ai/skills/stacks-retro/SKILL.md +121 -75
- package/ai/skills/stacks-review/SKILL.md +182 -74
- package/ai/skills/stacks-router/SKILL.md +1 -1
- package/ai/skills/stacks-routes/SKILL.md +1 -1
- package/ai/skills/stacks-scaffolding/SKILL.md +1 -1
- package/ai/skills/stacks-scheduler/SKILL.md +1 -1
- package/ai/skills/stacks-search-engine/SKILL.md +1 -1
- package/ai/skills/stacks-security/SKILL.md +1 -1
- package/ai/skills/stacks-security-audit/SKILL.md +1 -1
- package/ai/skills/stacks-server/SKILL.md +1 -1
- package/ai/skills/stacks-shell/SKILL.md +1 -1
- package/ai/skills/stacks-slug/SKILL.md +1 -1
- package/ai/skills/stacks-sms/SKILL.md +1 -1
- package/ai/skills/stacks-socials/SKILL.md +1 -1
- package/ai/skills/stacks-storage/SKILL.md +1 -1
- package/ai/skills/stacks-strings/SKILL.md +1 -1
- package/ai/skills/stacks-stx/SKILL.md +1 -1
- package/ai/skills/stacks-tdd/EXAMPLES.md +136 -0
- package/ai/skills/stacks-tdd/SKILL.md +125 -0
- package/ai/skills/stacks-testing/SKILL.md +13 -3
- package/ai/skills/stacks-tunnel/SKILL.md +1 -1
- package/ai/skills/stacks-types/SKILL.md +1 -1
- package/ai/skills/stacks-ui/SKILL.md +1 -1
- package/ai/skills/stacks-utils/SKILL.md +1 -1
- package/ai/skills/stacks-validation/SKILL.md +1 -1
- package/ai/skills/stacks-whois/SKILL.md +1 -1
- package/ai/skills/stacks-wizard/SKILL.md +127 -0
- package/ai/skills/stacks-wizard/scripts/template.sh +208 -0
- package/ai/skills/stacks-writing-for-agents/MECHANICS.md +125 -0
- package/ai/skills/stacks-writing-for-agents/SKILL.md +218 -0
- package/app/Actions/Auth/GenerateTwoFactorSecretAction.ts +12 -2
- package/app/Actions/Commerce/Shipping/{DriverDestroyAction.ts → CourierDestroyAction.ts} +6 -6
- package/app/Actions/Commerce/Shipping/{DriverIndexAction.ts → CourierIndexAction.ts} +3 -3
- package/app/Actions/Commerce/Shipping/CourierPingStoreAction.ts +61 -0
- package/app/Actions/Commerce/Shipping/{DriverShowAction.ts → CourierShowAction.ts} +5 -5
- package/app/Actions/Commerce/Shipping/{DriverStoreAction.ts → CourierStoreAction.ts} +4 -4
- package/app/Actions/Commerce/Shipping/{DriverUpdateAction.ts → CourierUpdateAction.ts} +6 -6
- package/app/Actions/Commerce/Shipping/DeliveryRouteStartAction.ts +38 -0
- package/app/Actions/Commerce/Shipping/DeliveryStopCompleteAction.ts +40 -0
- package/app/Actions/Commerce/Shipping/DeliveryStopFailAction.ts +44 -0
- package/app/Actions/Commerce/Shipping/DeliveryStopStartAction.ts +39 -0
- package/app/Actions/Commerce/Shipping/courier-session.ts +75 -0
- package/app/Actions/Commerce/commerce-action.test.ts +5 -5
- package/app/Actions/Dashboard/Commerce/CommerceDeliveryAction.ts +5 -5
- package/app/Actions/Dashboard/Commerce/CourierIndexAction.ts +24 -0
- package/app/Actions/Dashboard/Commerce/DeliveryRouteIndexAction.ts +7 -7
- package/app/Actions/Dashboard/Commerce/commerce-delivery.test.ts +11 -11
- package/app/Actions/Dashboard/Commerce/commerce-delivery.ts +29 -29
- package/app/Actions/Dashboard/Commerce/{driver-records.test.ts → courier-records.test.ts} +9 -9
- package/app/Actions/Dashboard/Commerce/{driver-records.ts → courier-records.ts} +14 -14
- package/app/Actions/Dashboard/Commerce/delivery-route-records.test.ts +13 -13
- package/app/Actions/Dashboard/Commerce/delivery-route-records.ts +25 -25
- package/app/Models/User.ts +1 -1
- package/app/Models/commerce/{Driver.ts → Courier.ts} +7 -7
- package/app/Models/commerce/{DriverPing.ts → CourierPing.ts} +7 -7
- package/app/Models/commerce/DeliveryRoute.ts +6 -6
- package/app/Models/commerce/DeliveryStop.ts +31 -8
- package/bootstrap.ts +7 -0
- package/functions/commerce/shippings/couriers.ts +19 -0
- package/ide/vscode/package.json +1 -1
- package/package.json +4 -3
- package/resources/components/Dashboard/Commerce/Delivery/{DriverDeleteDialog.stx → CourierDeleteDialog.stx} +5 -5
- package/resources/components/Dashboard/Commerce/Delivery/{DriverDialog.stx → CourierDialog.stx} +10 -10
- package/resources/components/Dashboard/Commerce/Delivery/{DriversDashboard.stx → CouriersDashboard.stx} +43 -43
- package/resources/components/Dashboard/Commerce/Delivery/{DriversTable.stx → CouriersTable.stx} +21 -21
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryOverviewDashboard.stx +18 -18
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDeleteDialog.stx +2 -2
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRouteDialog.stx +22 -22
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesDashboard.stx +24 -24
- package/resources/components/Dashboard/Commerce/Delivery/DeliveryRoutesTable.stx +8 -8
- package/resources/components/Dashboard/Commerce/Delivery/TabNavigation.stx +1 -1
- package/resources/functions/dashboard/data.ts +1 -1
- package/resources/functions/dashboard/sidebar.ts +2 -2
- package/routes/dashboard-api.ts +6 -6
- package/routes/dashboard.ts +7 -7
- package/routes/delivery.ts +24 -0
- package/types/defaults.ts +3 -3
- package/views/dashboard/.discovered-models.json +18 -18
- package/views/dashboard/AUDIT.md +1 -1
- package/views/dashboard/commerce/delivery/{drivers.stx → couriers.stx} +2 -2
- package/views/dashboard/composables/useChart.ts +16 -2
- package/views/dashboard/layouts/default.stx +1 -1
- package/app/Actions/Dashboard/Commerce/DriverIndexAction.ts +0 -24
- package/functions/commerce/shippings/drivers.ts +0 -19
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
#
|
|
3
|
+
# A wizard walks a human through a manual procedure, step by step.
|
|
4
|
+
# Generated by the stacks-wizard skill.
|
|
5
|
+
#
|
|
6
|
+
# Library adapted from Matt Pocock's `wizard` skill (MIT),
|
|
7
|
+
# https://github.com/mattpocock/skills
|
|
8
|
+
#
|
|
9
|
+
# Everything above the "STAGES" marker is the wizard library: do not hand-edit
|
|
10
|
+
# it. Author the per-step stages below the marker.
|
|
11
|
+
|
|
12
|
+
set -euo pipefail
|
|
13
|
+
|
|
14
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
15
|
+
# Wizard library: delightful, consistent UX, identical across every wizard.
|
|
16
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && [[ "$(tput colors 2>/dev/null || echo 0)" -ge 8 ]]; then
|
|
19
|
+
BOLD=$(tput bold); DIM=$(tput dim); RESET=$(tput sgr0)
|
|
20
|
+
BLUE=$(tput setaf 4); GREEN=$(tput setaf 2); YELLOW=$(tput setaf 3); RED=$(tput setaf 1)
|
|
21
|
+
else
|
|
22
|
+
BOLD=""; DIM=""; RESET=""; BLUE=""; GREEN=""; YELLOW=""; RED=""
|
|
23
|
+
fi
|
|
24
|
+
|
|
25
|
+
# Author sets this at the top of the stages section.
|
|
26
|
+
TOTAL_STAGES=0
|
|
27
|
+
|
|
28
|
+
_STAGE_INDEX=0
|
|
29
|
+
ENV_FILE="${ENV_FILE:-.env}"
|
|
30
|
+
WRITTEN_ENV=() # KEYs written to ENV_FILE this run
|
|
31
|
+
WRITTEN_SECRET=() # secret NAMEs set this run
|
|
32
|
+
SKIPPED=() # things we couldn't do (e.g. gh missing)
|
|
33
|
+
|
|
34
|
+
# _clear wipes the terminal so only the current step is on screen. No-op when
|
|
35
|
+
# output isn't a terminal, so piped logs stay readable.
|
|
36
|
+
_clear() {
|
|
37
|
+
[[ -t 1 ]] || return 0
|
|
38
|
+
if command -v tput >/dev/null 2>&1; then tput clear; else printf '\033[2J\033[3J\033[H'; fi
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# banner "Title" shows the opening frame: what this wizard does.
|
|
42
|
+
banner() {
|
|
43
|
+
_clear
|
|
44
|
+
printf '\n%s%s %s%s\n' "$BOLD" "$BLUE" "$1" "$RESET"
|
|
45
|
+
printf '%s %s stages%s\n\n' "$DIM" "$TOTAL_STAGES" "$RESET"
|
|
46
|
+
printf '%s You drive the browser; this wizard tells you exactly what to do and\n' "$DIM"
|
|
47
|
+
printf ' captures the values you copy back. Stop any time with Ctrl-C and re-run\n'
|
|
48
|
+
printf ' later, since it remembers values already saved.%s\n' "$RESET"
|
|
49
|
+
pause "Ready to start?"
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
# stage "Name" clears the screen, then announces a stage and shows progress.
|
|
53
|
+
# Clearing keeps only the current step on screen.
|
|
54
|
+
stage() {
|
|
55
|
+
_clear
|
|
56
|
+
_STAGE_INDEX=$((_STAGE_INDEX + 1))
|
|
57
|
+
printf '\n%s%s▸ Stage %s/%s · %s%s\n' \
|
|
58
|
+
"$BOLD" "$BLUE" "$_STAGE_INDEX" "$TOTAL_STAGES" "$1" "$RESET"
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
# say "..." prints a plain instruction line.
|
|
62
|
+
say() { printf ' %s\n' "$1"; }
|
|
63
|
+
# step "..." is a numbered-feeling action the human takes in the browser.
|
|
64
|
+
step() { printf ' %s•%s %s\n' "$BLUE" "$RESET" "$1"; }
|
|
65
|
+
note() { printf ' %s%s%s\n' "$DIM" "$1" "$RESET"; }
|
|
66
|
+
warn() { printf ' %s⚠ %s%s\n' "$YELLOW" "$1" "$RESET"; }
|
|
67
|
+
|
|
68
|
+
# open_url URL opens it in the human's browser, cross-platform incl. WSL.
|
|
69
|
+
open_url() {
|
|
70
|
+
local url="$1"
|
|
71
|
+
printf ' %s↗ opening%s %s\n' "$GREEN" "$RESET" "$url"
|
|
72
|
+
{ if command -v wslview >/dev/null 2>&1; then wslview "$url"
|
|
73
|
+
elif command -v explorer.exe >/dev/null 2>&1; then explorer.exe "$url"
|
|
74
|
+
elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$url"
|
|
75
|
+
elif command -v open >/dev/null 2>&1; then open "$url"
|
|
76
|
+
else warn "couldn't open a browser; visit it manually: $url"; fi
|
|
77
|
+
} >/dev/null 2>&1 || warn "couldn't open a browser, so visit it manually: $url"
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
# pause "msg" waits for the human to confirm they've done the manual part.
|
|
81
|
+
pause() {
|
|
82
|
+
printf ' %s%s%s ' "$DIM" "${1:-Press Enter to continue}" "$RESET"
|
|
83
|
+
read -r _ || true
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# confirm "question" is a y/N gate; returns success on yes.
|
|
87
|
+
confirm() {
|
|
88
|
+
local reply=""
|
|
89
|
+
printf ' %s? %s [y/N] ' "$YELLOW" "$1"
|
|
90
|
+
read -r reply || true
|
|
91
|
+
[[ "$reply" =~ ^[Yy] ]]
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
# _existing KEY: current value of KEY in ENV_FILE, if any.
|
|
95
|
+
_existing() {
|
|
96
|
+
[[ -f "$ENV_FILE" ]] || return 1
|
|
97
|
+
local line; line=$(grep -E "^${1}=" "$ENV_FILE" | tail -n1) || return 1
|
|
98
|
+
printf '%s' "${line#*=}"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
# ask KEY "Prompt" reads a value into $KEY. Offers the existing .env value as
|
|
102
|
+
# a default on re-runs (Enter keeps it). Visible input (non-secret).
|
|
103
|
+
ask() {
|
|
104
|
+
local key="$1" prompt="$2" current input
|
|
105
|
+
current=$(_existing "$key" || true)
|
|
106
|
+
if [[ -n "$current" ]]; then
|
|
107
|
+
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
|
|
108
|
+
else
|
|
109
|
+
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
|
|
110
|
+
fi
|
|
111
|
+
read -r input || true
|
|
112
|
+
[[ -z "$input" && -n "$current" ]] && input="$current"
|
|
113
|
+
printf -v "$key" '%s' "$input"
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# ask_secret KEY "Prompt" is like ask, but input is hidden.
|
|
117
|
+
ask_secret() {
|
|
118
|
+
local key="$1" prompt="$2" current input
|
|
119
|
+
current=$(_existing "$key" || true)
|
|
120
|
+
if [[ -n "$current" ]]; then
|
|
121
|
+
printf ' %s%s%s %s[Enter keeps current]%s ' "$BOLD" "$prompt" "$RESET" "$DIM" "$RESET"
|
|
122
|
+
else
|
|
123
|
+
printf ' %s%s%s ' "$BOLD" "$prompt" "$RESET"
|
|
124
|
+
fi
|
|
125
|
+
read -rs input || true
|
|
126
|
+
printf '\n'
|
|
127
|
+
[[ -z "$input" && -n "$current" ]] && input="$current"
|
|
128
|
+
printf -v "$key" '%s' "$input"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
# write_env KEY VALUE upserts KEY=VALUE into ENV_FILE (creates it; replaces
|
|
132
|
+
# any existing line). Idempotent.
|
|
133
|
+
write_env() {
|
|
134
|
+
local key="$1" value="$2" tmp
|
|
135
|
+
touch "$ENV_FILE"
|
|
136
|
+
tmp=$(mktemp)
|
|
137
|
+
grep -vE "^${key}=" "$ENV_FILE" > "$tmp" || true
|
|
138
|
+
printf '%s=%s\n' "$key" "$value" >> "$tmp"
|
|
139
|
+
mv "$tmp" "$ENV_FILE"
|
|
140
|
+
WRITTEN_ENV+=("$key")
|
|
141
|
+
printf ' %s✓ wrote%s %s → %s\n' "$GREEN" "$RESET" "$key" "$ENV_FILE"
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
# set_secret NAME VALUE sets a GitHub Actions repo secret via gh. Falls back
|
|
145
|
+
# to a warning (and records it) if gh is unavailable or unauthenticated.
|
|
146
|
+
set_secret() {
|
|
147
|
+
local name="$1" value="$2"
|
|
148
|
+
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
|
|
149
|
+
if printf '%s' "$value" | gh secret set "$name" >/dev/null 2>&1; then
|
|
150
|
+
WRITTEN_SECRET+=("$name")
|
|
151
|
+
printf ' %s✓ set%s GitHub secret %s\n' "$GREEN" "$RESET" "$name"
|
|
152
|
+
return
|
|
153
|
+
fi
|
|
154
|
+
fi
|
|
155
|
+
SKIPPED+=("GitHub secret $name (set it manually: gh secret set $name)")
|
|
156
|
+
warn "skipped GitHub secret $name: gh not ready; set it later"
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
# set_var NAME VALUE sets a GitHub Actions repo variable (non-secret).
|
|
160
|
+
set_var() {
|
|
161
|
+
local name="$1" value="$2"
|
|
162
|
+
if command -v gh >/dev/null 2>&1 && gh auth status >/dev/null 2>&1; then
|
|
163
|
+
if gh variable set "$name" --body "$value" >/dev/null 2>&1; then
|
|
164
|
+
printf ' %s✓ set%s GitHub variable %s\n' "$GREEN" "$RESET" "$name"
|
|
165
|
+
return
|
|
166
|
+
fi
|
|
167
|
+
fi
|
|
168
|
+
SKIPPED+=("GitHub variable $name")
|
|
169
|
+
warn "skipped GitHub variable $name, gh not ready; set it later"
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
# finish clears, then shows a closing summary of everything configured.
|
|
173
|
+
finish() {
|
|
174
|
+
_clear
|
|
175
|
+
printf '\n%s%s ✓ Setup complete%s\n' "$BOLD" "$GREEN" "$RESET"
|
|
176
|
+
(( ${#WRITTEN_ENV[@]} )) && note "wrote ${#WRITTEN_ENV[@]} value(s) to $ENV_FILE: ${WRITTEN_ENV[*]}"
|
|
177
|
+
(( ${#WRITTEN_SECRET[@]} )) && note "set ${#WRITTEN_SECRET[@]} GitHub secret(s): ${WRITTEN_SECRET[*]}"
|
|
178
|
+
if (( ${#SKIPPED[@]} )); then
|
|
179
|
+
printf '\n'; warn "still to do by hand:"
|
|
180
|
+
for s in "${SKIPPED[@]}"; do note " - $s"; done
|
|
181
|
+
fi
|
|
182
|
+
printf '\n'
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
186
|
+
# STAGES: author this section. One stage() per step the human takes.
|
|
187
|
+
# Replace the example below. Set TOTAL_STAGES to match the stages you write.
|
|
188
|
+
# ──────────────────────────────────────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
TOTAL_STAGES=1
|
|
191
|
+
|
|
192
|
+
banner "AWS credentials for buddy deploy"
|
|
193
|
+
|
|
194
|
+
# -- Example stage: replace with the real steps ---------------------------
|
|
195
|
+
stage "AWS: access keys"
|
|
196
|
+
say "We'll capture an AWS access key pair so 'buddy deploy' can reach your account."
|
|
197
|
+
open_url "https://console.aws.amazon.com/iam/home#/security_credentials"
|
|
198
|
+
step "Open Access keys, then Create access key, and pick 'Command Line Interface'."
|
|
199
|
+
ask AWS_ACCESS_KEY_ID "Paste the access key ID:"
|
|
200
|
+
step "Copy the secret access key before you close the dialog. It is shown once."
|
|
201
|
+
ask_secret AWS_SECRET_ACCESS_KEY "Paste the secret access key:"
|
|
202
|
+
write_env AWS_ACCESS_KEY_ID "$AWS_ACCESS_KEY_ID"
|
|
203
|
+
write_env AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY"
|
|
204
|
+
set_secret AWS_SECRET_ACCESS_KEY "$AWS_SECRET_ACCESS_KEY" # CI needs this one
|
|
205
|
+
note "Encrypt the file when every stage is done: ./buddy env:encrypt"
|
|
206
|
+
# -------------------------------------------------------------------------
|
|
207
|
+
|
|
208
|
+
finish
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
# Skill mechanics in Stacks
|
|
2
|
+
|
|
3
|
+
The skill-specific branch of [SKILL.md](SKILL.md): what changes when the
|
|
4
|
+
document is a `SKILL.md`. Everything else about writing it is the universal
|
|
5
|
+
reference next door.
|
|
6
|
+
|
|
7
|
+
## Where a skill lives
|
|
8
|
+
|
|
9
|
+
`@stacksjs/skills` resolves a skill name against two sources, in order:
|
|
10
|
+
|
|
11
|
+
1. `app/Skills/<name>/SKILL.md` - the project's own skills
|
|
12
|
+
2. `storage/framework/defaults/ai/skills/<name>/SKILL.md` - the bundled ones
|
|
13
|
+
|
|
14
|
+
First hit wins, which is the same app-overrides-defaults model as
|
|
15
|
+
`app/Actions/` and `app/Models/`. To shadow a bundled skill, create a directory
|
|
16
|
+
with **the same name** under `app/Skills/`. To add a new one, pick a name no
|
|
17
|
+
bundled skill uses.
|
|
18
|
+
|
|
19
|
+
`buddy setup:ai <agent>` materializes the merged set into whatever directory the
|
|
20
|
+
agent reads (`.claude/skills` for Claude Code), symlinking by default so an
|
|
21
|
+
upgrade keeps them in sync, or copying with `--copy` when you want to edit them
|
|
22
|
+
per project. The generated directories are gitignored. Only `AGENTS.md` is
|
|
23
|
+
committed, because which agent you use is a personal choice.
|
|
24
|
+
|
|
25
|
+
Nothing writes into `storage/framework/defaults/ai/skills`. A project skill goes
|
|
26
|
+
in `app/Skills/`. Edit the bundled directory only when you are working on the
|
|
27
|
+
framework itself.
|
|
28
|
+
|
|
29
|
+
## Frontmatter
|
|
30
|
+
|
|
31
|
+
```yaml
|
|
32
|
+
---
|
|
33
|
+
name: stacks-cache
|
|
34
|
+
description: Use when implementing caching in Stacks - memory cache, Redis cache, cache-aside (getOrSet), TTL management, or cache stats. Covers @stacksjs/cache and config/cache.ts.
|
|
35
|
+
license: MIT
|
|
36
|
+
compatibility: Bun >= 1.3.0, TypeScript
|
|
37
|
+
allowed-tools: Read Edit Write Bash Grep Glob
|
|
38
|
+
---
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
`validateSkill()` in `@stacksjs/skills` enforces four rules, and
|
|
42
|
+
`storage/framework/core/skills/tests/skills.test.ts` runs it over every bundled
|
|
43
|
+
skill, so a broken one fails the suite:
|
|
44
|
+
|
|
45
|
+
- `name` is required, 1 to 64 characters, lowercase letters, numbers and hyphens
|
|
46
|
+
only.
|
|
47
|
+
- `name` must equal the directory name.
|
|
48
|
+
- `description` is required and at most 1024 characters.
|
|
49
|
+
|
|
50
|
+
Two more rules the validator does not catch but the parsers do:
|
|
51
|
+
|
|
52
|
+
- **Keep `: ` out of the description.** The frontmatter parser splits each line
|
|
53
|
+
on its first colon and takes the rest verbatim, and a bare `: ` inside an
|
|
54
|
+
unquoted YAML scalar is invalid YAML besides. Use a hyphen or a comma.
|
|
55
|
+
- **Keep the em-dash out of everything.** The project-wide rule in `AGENTS.md`
|
|
56
|
+
applies to skills as much as to UI copy. Older bundled skills still carry them
|
|
57
|
+
in their descriptions, which is drift, not licence.
|
|
58
|
+
|
|
59
|
+
Optional fields `SkillMetadata` understands, beyond the five above:
|
|
60
|
+
`disable-model-invocation`, `user-invocable`, `argument-hint`, `context: fork`,
|
|
61
|
+
`agent`, `model`, `effort`, and a free-form `metadata` map.
|
|
62
|
+
|
|
63
|
+
## Supporting files
|
|
64
|
+
|
|
65
|
+
`getSkill()` reports three well-known subdirectories beside `SKILL.md`:
|
|
66
|
+
|
|
67
|
+
- `scripts/` - runnable helpers the skill invokes (`stacks-browse` and
|
|
68
|
+
`stacks-wizard` both ship one)
|
|
69
|
+
- `references/` - disclosed reference the body points at
|
|
70
|
+
- `assets/` - templates, images, fixtures
|
|
71
|
+
|
|
72
|
+
A sibling `.md` file with no directory works too, and is the lightest form of
|
|
73
|
+
progressive disclosure: `stacks-technical-diagrams` keeps 36 of them.
|
|
74
|
+
|
|
75
|
+
## Invocation
|
|
76
|
+
|
|
77
|
+
Two choices, trading the two loads:
|
|
78
|
+
|
|
79
|
+
- A **model-invoked** skill keeps its `description`, so the agent can fire it on
|
|
80
|
+
its own and other skills can reach it. The description is the skill's top-level
|
|
81
|
+
context pointer, forced to stay loaded at all times: permanent context load in
|
|
82
|
+
exchange for discoverability. A model-invoked skill whose content is all
|
|
83
|
+
reference is also the one home for shared reference, because another skill can
|
|
84
|
+
invoke it. Mechanics: omit `disable-model-invocation` and write a model-facing
|
|
85
|
+
description carrying the trigger branches. This is the default for the bundled
|
|
86
|
+
set, and the right choice for every subsystem reference.
|
|
87
|
+
- A **user-invoked** skill strips the description from the agent's reach: only
|
|
88
|
+
the human typing its name can invoke it, and no other skill can. Zero context
|
|
89
|
+
load, but it spends cognitive load, because you are the index that must
|
|
90
|
+
remember it exists. Mechanics: set `disable-model-invocation: true`, and the
|
|
91
|
+
`description` becomes human-facing, a one-line summary with the trigger list
|
|
92
|
+
stripped. `stacks-flow` and `stacks-handoff` are the two bundled examples.
|
|
93
|
+
|
|
94
|
+
Pick model-invocation only when the agent must reach the skill on its own, or
|
|
95
|
+
another skill must. If it only ever fires by hand, make it user-invoked and pay
|
|
96
|
+
no context load.
|
|
97
|
+
|
|
98
|
+
Shared reference that two user-invoked skills both need can live in neither:
|
|
99
|
+
with no descriptions, neither can fire the other. Push it to a plain file
|
|
100
|
+
outside the skill system, or to a model-invoked reference skill both can reach.
|
|
101
|
+
|
|
102
|
+
## Splitting by invocation
|
|
103
|
+
|
|
104
|
+
The invocation cut, as opposed to the sequence cut in [SKILL.md](SKILL.md):
|
|
105
|
+
split off a model-invoked skill when you have a distinct leading word that
|
|
106
|
+
should trigger it on its own (a trigger word you actually type), or when another
|
|
107
|
+
skill must reach it. `stacks-grilling` exists as its own skill for exactly that
|
|
108
|
+
reason: `stacks-office-hours`, `stacks-plan-review` and `stacks-redesign` all
|
|
109
|
+
call it. You pay context load for the new always-loaded description, so that
|
|
110
|
+
independent reach has to be worth it.
|
|
111
|
+
|
|
112
|
+
## Router skills
|
|
113
|
+
|
|
114
|
+
When user-invoked skills multiply past what you can remember, that piled-up
|
|
115
|
+
cognitive load is cured by a **router skill**: one user-invoked skill that names
|
|
116
|
+
the others and when to reach for each, so the human has one skill to remember
|
|
117
|
+
instead of many. `stacks-flow` is it. A router can only hint, never fire:
|
|
118
|
+
user-invoked skills have no description, so nothing but the human can reach them.
|
|
119
|
+
|
|
120
|
+
## Registering a project skill
|
|
121
|
+
|
|
122
|
+
Nothing to register. `listSkills()` reads the directories, so a new
|
|
123
|
+
`app/Skills/<name>/SKILL.md` is live the moment it exists. Re-run
|
|
124
|
+
`buddy setup:ai <agent>` to link it into your agent's directory, and add a row to
|
|
125
|
+
the feature-to-skill table in `AGENTS.md` so a human can find it too.
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stacks-writing-for-agents
|
|
3
|
+
description: Use when writing or editing any document an agent reads - a SKILL.md under app/Skills or storage/framework/defaults/ai/skills, the project AGENTS.md, or a reference file a skill points at. Covers context pointers, the information hierarchy, completion criteria, leading words, pruning, and the skill mechanics behind app/Skills and buddy setup:ai.
|
|
4
|
+
license: MIT
|
|
5
|
+
compatibility: Bun >= 1.3.0, TypeScript
|
|
6
|
+
allowed-tools: Read Edit Write Bash Grep Glob
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Writing for agents
|
|
10
|
+
|
|
11
|
+
Reference for every document an agent consumes in a Stacks project: a skill, the
|
|
12
|
+
project `AGENTS.md`, a doc reached by a pointer. The packaging differs, the
|
|
13
|
+
writing does not. The same levers make each one predictable, because the agent
|
|
14
|
+
takes the same *process* every run rather than producing the same output.
|
|
15
|
+
|
|
16
|
+
Stacks ships 100+ skills and expects projects to add their own under
|
|
17
|
+
`app/Skills/`, so this is the skill that keeps that set from turning to sludge.
|
|
18
|
+
The Stacks-specific mechanics (frontmatter, invocation, the override model,
|
|
19
|
+
what `buddy setup:ai` does with the result) are in
|
|
20
|
+
[MECHANICS.md](MECHANICS.md). Everything below is universal.
|
|
21
|
+
|
|
22
|
+
Credit: the model in this skill is adapted from Matt Pocock's `writing-for-agents`
|
|
23
|
+
skill (MIT), <https://github.com/mattpocock/skills>.
|
|
24
|
+
|
|
25
|
+
## Context pointers
|
|
26
|
+
|
|
27
|
+
A **context pointer** is a reference held in the agent's context that names some
|
|
28
|
+
out-of-context material and encodes the condition for reaching it. A skill's
|
|
29
|
+
`description` is one. A line in `AGENTS.md` naming a doc is the same object. The
|
|
30
|
+
pointer's *wording*, not its target, decides when the agent reaches the material
|
|
31
|
+
and how reliably. A must-have target behind a weakly worded pointer is a variance
|
|
32
|
+
bug: sharpen the wording first, and inline the material only if sharpening fails.
|
|
33
|
+
|
|
34
|
+
A pointer does two jobs: state what the material is, and list the **branches**
|
|
35
|
+
that should trigger reaching it (a branch is a distinct case the document
|
|
36
|
+
handles, so different runs take different paths through it). Every word of an
|
|
37
|
+
always-loaded pointer costs on every turn, so it earns harder pruning than the
|
|
38
|
+
body:
|
|
39
|
+
|
|
40
|
+
- **Front-load the leading word.** The pointer is where it does its triggering work.
|
|
41
|
+
- **One trigger per branch.** Synonyms that rename a single branch are one branch
|
|
42
|
+
written twice. Collapse them, keep only genuinely distinct branches.
|
|
43
|
+
- **Cut identity the body already carries.** `stacks-queue`'s description does not
|
|
44
|
+
need to explain what a queue is.
|
|
45
|
+
|
|
46
|
+
The bundled skills follow one shape, and yours should too: `Use when <situation>
|
|
47
|
+
- <branch>, <branch>, <branch>. Covers <package> and <config file>.` The
|
|
48
|
+
trailing `Covers` clause is doing pointer work, not decoration: it is how an
|
|
49
|
+
agent holding a package name (`@stacksjs/cache`) or a path (`config/queue.ts`)
|
|
50
|
+
finds the skill that owns it.
|
|
51
|
+
|
|
52
|
+
## The two loads
|
|
53
|
+
|
|
54
|
+
Every document and pointer you add spends one of two budgets:
|
|
55
|
+
|
|
56
|
+
- **Context load** is the cost of always-loaded material on the agent's window:
|
|
57
|
+
an `AGENTS.md` line, a skill description, anything sitting in context every
|
|
58
|
+
turn, spending tokens and attention whether or not it fires.
|
|
59
|
+
- **Cognitive load** is the cost on the human: which documents exist and when to
|
|
60
|
+
reach for each. The human is the index. Not a cost to minimise, it is the
|
|
61
|
+
price of human agency. Spend it where human judgement matters, remove it where
|
|
62
|
+
it does not.
|
|
63
|
+
|
|
64
|
+
Material reached only through a pointer escapes context load at the price of the
|
|
65
|
+
pointer's own line. Material with no pointer at all rides entirely on cognitive
|
|
66
|
+
load.
|
|
67
|
+
|
|
68
|
+
## Information hierarchy
|
|
69
|
+
|
|
70
|
+
A document is built from two content types: **steps** (the ordered actions the
|
|
71
|
+
agent performs) and **reference** (definitions, rules, facts consulted on
|
|
72
|
+
demand). The two mix freely: all steps (`stacks-new-feature`), all reference
|
|
73
|
+
(`stacks-orm`), or both (`stacks-investigate`). The core decision is where each
|
|
74
|
+
piece sits on the **information hierarchy**, a ladder ranked by how immediately
|
|
75
|
+
the agent needs the material:
|
|
76
|
+
|
|
77
|
+
1. **In-file step** is the primary tier: what the agent does, in order.
|
|
78
|
+
2. **In-file reference** is consulted on demand. Often a legitimately flat
|
|
79
|
+
peer-set (every rule of a review on one rung), which is a fine arrangement,
|
|
80
|
+
not a smell.
|
|
81
|
+
3. **Disclosed reference** is pushed into a separate file, reached by a context
|
|
82
|
+
pointer, loaded only when the pointer fires. Spans a sibling file in the same
|
|
83
|
+
skill directory through fully external reference any document can point at.
|
|
84
|
+
|
|
85
|
+
Push too little down and the top bloats. Push too much and you hide material the
|
|
86
|
+
agent actually needs. That tension is the whole decision.
|
|
87
|
+
|
|
88
|
+
**Progressive disclosure** is the move down the ladder so the top stays legible.
|
|
89
|
+
Not primarily a token optimisation: it is how the hierarchy is protected.
|
|
90
|
+
Branching is the cleanest disclosure test: inline what every branch needs, push
|
|
91
|
+
behind a pointer what only some branches reach. `stacks-technical-diagrams`
|
|
92
|
+
keeps 36 reference files beside one `SKILL.md` for exactly this reason.
|
|
93
|
+
|
|
94
|
+
**Co-location** is the within-file companion. Where the ladder decides *how far
|
|
95
|
+
down* a piece sits, co-location decides *what sits beside it* once there. Keep a
|
|
96
|
+
concept's definition, rules and caveats under one heading rather than scattered,
|
|
97
|
+
so reading one part brings its neighbours with it. The test: the document should
|
|
98
|
+
read like documentation written for the agent.
|
|
99
|
+
|
|
100
|
+
**Sprawl** is the failure mode: a document simply too long, even when every line
|
|
101
|
+
is live and unique. Attention thins across the excess, and every extra line is
|
|
102
|
+
one more to keep relevant. The cure is the ladder.
|
|
103
|
+
|
|
104
|
+
## Steps and completion criteria
|
|
105
|
+
|
|
106
|
+
Every step ends on a **completion criterion**, the condition that tells the agent
|
|
107
|
+
the work is done. Two properties make it a lever:
|
|
108
|
+
|
|
109
|
+
- **Clarity.** Can the agent tell done from not-done? A vague bound
|
|
110
|
+
("understanding reached") invites **premature completion**: ending the step
|
|
111
|
+
before it is genuinely done, attention slipping to *being done*. The visible
|
|
112
|
+
steps still ahead supply the pull, the criterion's clarity is the resistance.
|
|
113
|
+
Defend in order: sharpen the bound first (local and cheap), and only if it is
|
|
114
|
+
irreducibly fuzzy *and* you observe the rush, hide the later steps by splitting
|
|
115
|
+
the sequence. Hiding only works across a real context boundary (a hand-off or a
|
|
116
|
+
subagent dispatch). An inline call leaves the later steps in context and clears
|
|
117
|
+
nothing.
|
|
118
|
+
- **Demand.** How much the criterion requires. "Every changed model accounted
|
|
119
|
+
for" forces thorough work where "produce a change list" does not. Demand drives
|
|
120
|
+
**legwork**, the digging the agent does within the work, latent in the wording
|
|
121
|
+
rather than written as its own step. It is not step-bound: "every rule applied"
|
|
122
|
+
binds a body of flat reference just as "every step done" binds a sequence,
|
|
123
|
+
which is how an all-reference document still carries an exhaustiveness bar.
|
|
124
|
+
|
|
125
|
+
The strongest criteria are both checkable and exhaustive. `stacks-investigate`
|
|
126
|
+
Phase 1 is the model to copy: one command, already run once, output shown.
|
|
127
|
+
|
|
128
|
+
## When to split
|
|
129
|
+
|
|
130
|
+
Splitting one document into two spends one of the two loads, so split only when
|
|
131
|
+
the cut earns it:
|
|
132
|
+
|
|
133
|
+
- **By sequence.** Split a run of steps where the post-completion steps tempt the
|
|
134
|
+
agent to rush the one in front of it. Keeping them out of view drives more
|
|
135
|
+
legwork on the current task. Beware the reverse: merging sequences exposes each
|
|
136
|
+
step's later steps to what follows, inviting premature completion.
|
|
137
|
+
- **By invocation.** Skill-specific, see [MECHANICS.md](MECHANICS.md).
|
|
138
|
+
|
|
139
|
+
## Leading words
|
|
140
|
+
|
|
141
|
+
A **leading word** is a compact concept already living in the model's
|
|
142
|
+
pretraining that the agent thinks with while running the document (*lesson*,
|
|
143
|
+
*fog of war*, *tracer bullet*, *seam*). Repeated as a token, never as a
|
|
144
|
+
sentence, it accumulates a distributed definition and anchors a whole region of
|
|
145
|
+
behaviour in the fewest tokens by recruiting priors the model already holds.
|
|
146
|
+
Coining your own works if you define it clearly, but a made-up word recruits no
|
|
147
|
+
priors: you pay in definition tokens what a pretrained word gives free. Reach
|
|
148
|
+
for an existing word first.
|
|
149
|
+
|
|
150
|
+
It anchors twice. In the body, *execution*: the agent reaches for the same
|
|
151
|
+
behaviour every time the word appears. In a pointer, *invocation*: when the same
|
|
152
|
+
word lives in your prompts, your docs and your codebase, the agent links that
|
|
153
|
+
shared language to the material and reaches it more reliably. This is why the
|
|
154
|
+
Stacks skills insist on `trait`, `driver`, `action`, `seam` and `tracer bullet`
|
|
155
|
+
rather than paraphrasing them.
|
|
156
|
+
|
|
157
|
+
Hunt for opportunities to refactor with leading words. A triad spelled out at
|
|
158
|
+
three sites, a pointer spending a sentence to gesture at one idea. Each is a
|
|
159
|
+
passage begging to collapse into a single token:
|
|
160
|
+
|
|
161
|
+
- "fast, deterministic, low-overhead" becomes *tight* (a *tight* loop).
|
|
162
|
+
- "a loop you believe in" becomes *red*, turning a fuzzy gate into a binary
|
|
163
|
+
observable state (the loop goes *red* on the bug, or it does not).
|
|
164
|
+
|
|
165
|
+
**Negation** is the failure mode beside this lever. Steering by prohibition drags
|
|
166
|
+
the forbidden behaviour into context and makes it *more* available, not less.
|
|
167
|
+
*Do not think of an elephant*, and the elephant is all there is. Prompt the
|
|
168
|
+
**positive**: state the target behaviour ("use signals and composables in stx
|
|
169
|
+
templates") so the banned one is never spoken. A prohibition earns its place only
|
|
170
|
+
as a hard guardrail you cannot phrase positively, and even then, pair it with the
|
|
171
|
+
positive target so attention lands on what to do.
|
|
172
|
+
|
|
173
|
+
## Pruning
|
|
174
|
+
|
|
175
|
+
- Keep each meaning in a **single source of truth**: one authoritative place, so
|
|
176
|
+
changing the behaviour is a one-place edit. **Duplication** costs maintenance
|
|
177
|
+
and tokens, and inflates a meaning's prominence on the ladder past its real
|
|
178
|
+
rank. It is the accidental inverse of a leading word, which repeats a token on
|
|
179
|
+
purpose, never the meaning.
|
|
180
|
+
- The **environment** is a source of truth too, and in a Stacks project it is a
|
|
181
|
+
rich one: `buddy list`, `buddy <command> --help`, `config/*.ts`, the
|
|
182
|
+
`storage/framework/*-auto-imports.json` manifests, the generated
|
|
183
|
+
`storage/framework/types/*.d.ts`. A document that restates it is a **cache**: a
|
|
184
|
+
copy of a lookup, earning its load only when the lookup is expensive. Cache
|
|
185
|
+
what the agent cannot find by looking (the unwritten convention, the reason
|
|
186
|
+
behind a choice, the gotcha no config confesses) and leave the one-command
|
|
187
|
+
lookups to the environment, where they cannot go stale. Every bundled skill's
|
|
188
|
+
`## Gotchas` section is this rule applied.
|
|
189
|
+
- Check every line for **relevance**: does it still bear on what the document
|
|
190
|
+
does? A line loses relevance by never bearing on the task, or by going stale as
|
|
191
|
+
the code it describes changes. Without a pruning discipline the default fate is
|
|
192
|
+
**sediment**: stale layers that settle because adding feels safe and removing
|
|
193
|
+
feels risky.
|
|
194
|
+
- Hunt **no-ops** sentence by sentence: an instruction the model already obeys by
|
|
195
|
+
default pays load to say nothing. The test (does it change behaviour versus the
|
|
196
|
+
default?) is model-relative, not reader-relative. Two people disagreeing about
|
|
197
|
+
a no-op disagree about the default, and settle it by running the document, not
|
|
198
|
+
by debate. When a sentence fails, delete the whole sentence rather than trim
|
|
199
|
+
words from it. The test also grades leading words: a word too weak to beat the
|
|
200
|
+
default (*be thorough*, when the agent is already thorough-ish) is a no-op, and
|
|
201
|
+
the fix is a stronger word (*relentless*), not a different technique.
|
|
202
|
+
|
|
203
|
+
## Before you finish
|
|
204
|
+
|
|
205
|
+
- Every line changes behaviour versus the default.
|
|
206
|
+
- Every meaning lives in exactly one place.
|
|
207
|
+
- The description names distinct branches, front-loads the trigger, and carries
|
|
208
|
+
the `Covers` clause.
|
|
209
|
+
- Nothing in the body restates what `buddy <command> --help` or a config file
|
|
210
|
+
already says.
|
|
211
|
+
- No em-dash anywhere in the file (the project-wide rule in `AGENTS.md`).
|
|
212
|
+
- `bunx --bun pickier .` is clean if you touched code alongside it.
|
|
213
|
+
|
|
214
|
+
## Downstream
|
|
215
|
+
|
|
216
|
+
> Reach for `stacks-retro` after a session to find which documents actually
|
|
217
|
+
> failed, and `stacks-flow` when the problem is that nobody remembers a skill
|
|
218
|
+
> exists.
|
|
@@ -2,10 +2,11 @@ import { Action } from '@stacksjs/actions'
|
|
|
2
2
|
import { generateTwoFactorSetup, stashPendingTwoFactorSecret } from '@stacksjs/auth'
|
|
3
3
|
import { config } from '@stacksjs/config'
|
|
4
4
|
import { response } from '@stacksjs/router'
|
|
5
|
+
import { toSvg } from 'ts-qr-codes'
|
|
5
6
|
|
|
6
7
|
export default new Action({
|
|
7
8
|
name: 'GenerateTwoFactorSecretAction',
|
|
8
|
-
description: 'Generate a new TOTP secret
|
|
9
|
+
description: 'Generate a new TOTP secret, otpauth URI and scannable QR code for the authenticated user',
|
|
9
10
|
method: 'POST',
|
|
10
11
|
async handle(request: RequestInstance) {
|
|
11
12
|
const user = await request.user()
|
|
@@ -21,6 +22,15 @@ export default new Action({
|
|
|
21
22
|
// two-factor.ts's doc comment for why.
|
|
22
23
|
await stashPendingTwoFactorSecret(user.id as number, secret)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
// Rendered here rather than left to the client. Every authenticator flow
|
|
26
|
+
// needs the URI as a QR code, and a caller that has to find its own
|
|
27
|
+
// encoder either ships one to the browser or, more often, falls back to
|
|
28
|
+
// asking the user to type a 32-character secret by hand.
|
|
29
|
+
//
|
|
30
|
+
// SVG so it stays sharp at any size and can be inlined into a page or an
|
|
31
|
+
// email; the URI is still returned for clients that render their own.
|
|
32
|
+
const qr = toSvg(uri, { size: 240, title: 'Two-factor authentication setup' })
|
|
33
|
+
|
|
34
|
+
return response.json({ secret, uri, qr })
|
|
25
35
|
},
|
|
26
36
|
})
|
|
@@ -4,19 +4,19 @@ import { response } from '@stacksjs/router'
|
|
|
4
4
|
import { commerceIdentifier, commerceNotFound } from '../commerce-action'
|
|
5
5
|
|
|
6
6
|
export default new Action({
|
|
7
|
-
name: '
|
|
8
|
-
description: '
|
|
7
|
+
name: 'Courier Destroy',
|
|
8
|
+
description: 'Courier Destroy ORM Action',
|
|
9
9
|
method: 'DELETE',
|
|
10
10
|
async handle(request: RequestInstance) {
|
|
11
|
-
const identifier = commerceIdentifier(request, '
|
|
11
|
+
const identifier = commerceIdentifier(request, 'Courier')
|
|
12
12
|
if (identifier.error)
|
|
13
13
|
return identifier.error
|
|
14
14
|
const { id } = identifier
|
|
15
15
|
|
|
16
|
-
const deleted = await shippings.
|
|
16
|
+
const deleted = await shippings.couriers.destroy(id)
|
|
17
17
|
if (!deleted)
|
|
18
|
-
return commerceNotFound('
|
|
18
|
+
return commerceNotFound('Courier', id)
|
|
19
19
|
|
|
20
|
-
return response.json({ message: '
|
|
20
|
+
return response.json({ message: 'Courier deleted successfully' })
|
|
21
21
|
},
|
|
22
22
|
})
|
|
@@ -5,11 +5,11 @@ import { shippings } from '@stacksjs/commerce'
|
|
|
5
5
|
import { response } from '@stacksjs/router'
|
|
6
6
|
|
|
7
7
|
export default new Action({
|
|
8
|
-
name: '
|
|
9
|
-
description: '
|
|
8
|
+
name: 'Courier Index',
|
|
9
|
+
description: 'Courier Index ORM Action',
|
|
10
10
|
method: 'GET',
|
|
11
11
|
async handle() {
|
|
12
|
-
const results = await shippings.
|
|
12
|
+
const results = await shippings.couriers.fetchAll()
|
|
13
13
|
|
|
14
14
|
return response.json(results)
|
|
15
15
|
},
|