crewly 1.20.450 → 1.20.451

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.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: Check-up Report
3
- description: The intake and report steps of the small-business check-up as one Crewly App. Copies the bundled App page (five intake questions, the report with the top 3 breaks and what an agent could do, a 7-day daily summary) into the project for publish-app, turns checkup-analyse's breaks.json into the App's report document (with the number the owner asked to see first), and writes daily-summary documents for app-data.
4
- version: 1.0.0
3
+ description: The intake and report steps of the small-business check-up as one Crewly App. Copies the bundled App page (five intake questions, the report with the top 3 breaks and what an agent could do, a 7-day daily summary) into the project for publish-app, turns checkup-analyse's breaks.json into the App's report document (with the number the owner asked to see first), writes daily-summary documents for app-data, and keeps a per-step time log (--log-step) the report shows.
4
+ version: 1.1.0
5
5
  category: productivity
6
6
  skillType: claude-skill
7
7
  assignableRoles:
@@ -12,6 +12,7 @@ triggers:
12
12
  - publish the check-up
13
13
  - intake questions
14
14
  - daily check-up summary
15
+ - log a check-up step
15
16
  tags:
16
17
  - check-up
17
18
  - small-business
@@ -84,10 +85,26 @@ bash app-data --app <appId> --set daily 2026-10-27 --data-file ./checkup-data/da
84
85
  Say what changed and what was done, in the owner's language, under 300
85
86
  characters. Seven entries, then stop unless the owner asks for more.
86
87
 
87
- ## Time log
88
+ The free **Small Business Check-up** team template (`small-business-checkup`)
89
+ runs these steps with a Checkup Lead, a Data Analyst and an optional Reviewer
90
+ who checks `report-doc.json` before it is published; remove the Reviewer from
91
+ the team to skip that step.
88
92
 
89
- Record the start and end of each step (intake, connect, analyse, report) in
90
- your work notes; the check-up is measured by how long it really takes.
93
+ ## Time log — how long the check-up really took
94
+
95
+ Mark the start and end of every step; the report shows the minutes per step
96
+ and the total, and the owner sees them under "How long it took".
97
+
98
+ ```bash
99
+ bash execute.sh --log-step intake --start --out ./checkup-data # steps: intake, connect, analyse, report, daily
100
+ bash execute.sh --log-step intake --end --out ./checkup-data
101
+ ```
102
+
103
+ `--out` is the same directory as the report; the log is `timelog.json` there
104
+ and `--breaks` folds it in as `timing` (closed steps with minutes, the total,
105
+ and any step still open). A second `--start` on an open step restarts it;
106
+ `--end` with no open entry fails (exit 2) so a forgotten start is noticed.
107
+ `--at <iso>` back-fills a time you know. One `daily` entry per day is fine.
91
108
 
92
109
  ## Report back
93
110
 
@@ -68,6 +68,7 @@
68
68
  <div id="notes-wrap" class="hidden"><h2>Could not check</h2><ul class="plain" id="notes" data-crewly-id="notes"></ul></div>
69
69
  <h2>Files</h2>
70
70
  <ul class="plain" id="files" data-crewly-id="files"></ul>
71
+ <div id="timing-wrap" class="hidden"><h2>How long it took</h2><ul class="plain" id="timing" data-crewly-id="timing"></ul></div>
71
72
  <h2>Assumptions</h2>
72
73
  <ul class="plain" id="assumptions" data-crewly-id="assumptions"></ul>
73
74
  <button class="secondary" id="ask" data-crewly-id="ask">Ask about this report</button>
@@ -193,6 +194,15 @@
193
194
  (r.files || []).forEach(function (f) { files.appendChild(el('li', null, f.file + ' — ' + f.source + ' ' + f.kind + ', ' + f.rows + ' rows')); });
194
195
  var as = $('assumptions'); as.replaceChildren();
195
196
  Object.keys(r.assumptions || {}).forEach(function (k) { as.appendChild(el('li', null, k + ': ' + r.assumptions[k])); });
197
+ var timing = $('timing'); timing.replaceChildren();
198
+ var t = r.timing;
199
+ if (t && t.steps && t.steps.length) {
200
+ t.steps.forEach(function (s) {
201
+ timing.appendChild(el('li', null, s.step + ': ' + (s.endedAt ? s.minutes + ' min' : 'still running')));
202
+ });
203
+ timing.appendChild(el('li', 'note', 'Total ' + t.totalMinutes + ' min of agent time' + (t.open && t.open.length ? ' so far' : '')));
204
+ }
205
+ $('timing-wrap').classList.toggle('hidden', !(t && t.steps && t.steps.length));
196
206
  }
197
207
  $('ask').onclick = function () {
198
208
  crewly.notify('The owner opened the check-up report and tapped "Ask about this report". Reply in their DM with a one-line offer to walk through the top 3.').then(function () {
@@ -9,6 +9,8 @@
9
9
  # bash execute.sh --app-dir ./checkup-app # copy the App page
10
10
  # bash execute.sh --breaks …/analysis/breaks.json --business "Oak Pottery" --intake ./intake.json --out ./checkup-data
11
11
  # bash execute.sh --daily "Replied to 3 open inquiries; 1 booked." --out ./checkup-data
12
+ # bash execute.sh --log-step intake --start --out ./checkup-data # per-step time log (timelog.json)
13
+ # bash execute.sh '{"logStep":"intake","mark":"end","out":"./checkup-data"}'
12
14
  # bash execute.sh '{"breaks":"…/breaks.json","business":"Oak Pottery","out":"./checkup-data"}'
13
15
  #
14
16
  # Output: one JSON line with the files written and the exact next command
@@ -23,13 +25,15 @@ command -v node >/dev/null 2>&1 || { printf '{"success":false,"error":"node is r
23
25
  ARGS=()
24
26
  if [[ $# -gt 0 && ${1:0:1} == '{' ]]; then
25
27
  command -v jq >/dev/null 2>&1 || { printf '{"success":false,"error":"jq is required to read JSON input"}\n'; exit 1; }
26
- for key in appDir breaks business intake examples out daily date; do
28
+ for key in appDir breaks business intake examples out daily date logStep at; do
27
29
  v=$(printf '%s' "$1" | jq -r --arg k "$key" '.[$k] // empty')
28
30
  if [ -n "$v" ]; then
29
- case "$key" in appDir) flag=--app-dir ;; *) flag="--$key" ;; esac
31
+ case "$key" in appDir) flag=--app-dir ;; logStep) flag=--log-step ;; *) flag="--$key" ;; esac
30
32
  ARGS+=("$flag" "$v")
31
33
  fi
32
34
  done
35
+ mark=$(printf '%s' "$1" | jq -r '.mark // empty')
36
+ case "$mark" in start|end) ARGS+=("--$mark") ;; esac
33
37
  shift
34
38
  fi
35
39
  ARGS+=("$@")
@@ -133,5 +133,42 @@ bash "$EXEC" --daily " " --out "$T/x" >/dev/null 2>&1; check "empty daily exit
133
133
  bash "$EXEC" --breaks "$T/an/breaks.json" --examples lots --out "$T/x" >/dev/null 2>&1; check "bad examples exits 1" "$?" "1"
134
134
  bash "$EXEC" --bogus >/dev/null 2>&1; check "unknown flag exits 1" "$?" "1"
135
135
 
136
+ # 9. --log-step: per-step time log
137
+ check "page has a timing block" "$(grep -c 'data-crewly-id="timing"' "$PAGE")" "1"
138
+ bash "$EXEC" --log-step intake --start --at 2026-10-26T09:00:00Z --out "$T/tl" >/dev/null; check "log start exits 0" "$?" "0"
139
+ check "timelog written" "$([ -f "$T/tl/timelog.json" ] && echo yes || echo no)" "yes"
140
+ check "open entry" "$(jq -c '.steps[0] | {step, endedAt, minutes}' "$T/tl/timelog.json")" '{"step":"intake","endedAt":null,"minutes":null}'
141
+ LOUT="$(bash "$EXEC" --log-step intake --end --at 2026-10-26T09:12:30Z --out "$T/tl")"
142
+ check "log end exits 0" "$?" "0"
143
+ check "minutes = 12.5" "$(jq -r '.steps[0].minutes' "$T/tl/timelog.json")" "12.5"
144
+ check "end output carries the entry" "$(printf '%s' "$LOUT" | jq -r '.entry.endedAt')" "2026-10-26T09:12:30.000Z"
145
+ check "end output totals" "$(printf '%s' "$LOUT" | jq -c '.timing | {totalMinutes, open}')" '{"totalMinutes":12.5,"open":[]}'
146
+ bash "$EXEC" --log-step connect --start --at 2026-10-26T09:13:00Z --out "$T/tl" >/dev/null
147
+ bash "$EXEC" --log-step connect --start --at 2026-10-26T09:14:00Z --out "$T/tl" >/dev/null
148
+ check "restart replaces the open start" "$(jq -r '[.steps[] | select(.step=="connect")] | length, .[0].startedAt' "$T/tl/timelog.json" | tr '\n' ' ')" "1 2026-10-26T09:14:00.000Z "
149
+ bash "$EXEC" --log-step connect --end --at 2026-10-26T10:14:00Z --out "$T/tl" >/dev/null
150
+ bash "$EXEC" --log-step analyse --start --at 2026-10-26T10:15:00Z --out "$T/tl" >/dev/null
151
+ bash "$EXEC" --log-step analyse --end --at 2026-10-26T10:21:00Z --out "$T/tl" >/dev/null
152
+ bash "$EXEC" --log-step report --start --at 2026-10-26T10:22:00Z --out "$T/tl" >/dev/null
153
+ JLOG="$(bash "$EXEC" "{\"logStep\":\"report\",\"mark\":\"end\",\"at\":\"2026-10-26T10:30:00Z\",\"out\":\"$T/tl\"}")"
154
+ check "json input logs a step" "$(printf '%s' "$JLOG" | jq -r '.entry.minutes')" "8"
155
+ check "four closed steps" "$(jq -r '[.steps[] | select(.endedAt)] | length' "$T/tl/timelog.json")" "4"
156
+ check "same step twice keeps both" "$(bash "$EXEC" --log-step daily --start --at 2026-10-27T09:00:00Z --out "$T/tl" >/dev/null; bash "$EXEC" --log-step daily --end --at 2026-10-27T09:03:00Z --out "$T/tl" >/dev/null; bash "$EXEC" --log-step daily --start --at 2026-10-28T09:00:00Z --out "$T/tl" >/dev/null; jq -r '[.steps[] | select(.step=="daily")] | length' "$T/tl/timelog.json")" "2"
157
+ bash "$EXEC" --breaks "$T/an/breaks.json" --business "Oak Pottery" --out "$T/tl" >/dev/null
158
+ check "report folds the time log in" "$(jq -c '.timing | {totalMinutes, open, n: (.steps|length)}' "$T/tl/report-doc.json")" '{"totalMinutes":89.5,"open":["daily"],"n":6}'
159
+ check "timing steps carry minutes" "$(jq -r '.timing.steps[1] | "\(.step) \(.minutes)"' "$T/tl/report-doc.json")" "connect 60"
160
+ check "no time log → no timing" "$(jq -r 'has("timing")' "$D")" "false"
161
+ bash "$EXEC" --log-step cook --start --out "$T/tl" >/dev/null 2>&1; check "unknown step exits 1" "$?" "1"
162
+ bash "$EXEC" --log-step analyse --end --out "$T/tl" >/dev/null 2>&1; check "end without start exits 2" "$?" "2"
163
+ bash "$EXEC" --log-step intake --out "$T/tl" >/dev/null 2>&1; check "log-step without --start/--end exits 1" "$?" "1"
164
+ bash "$EXEC" --start --out "$T/tl" >/dev/null 2>&1; check "--start without --log-step exits 1" "$?" "1"
165
+ bash "$EXEC" --log-step intake --start >/dev/null 2>&1; check "log-step without --out exits 1" "$?" "1"
166
+ bash "$EXEC" --log-step intake --start --at yesterday --out "$T/tl" >/dev/null 2>&1; check "bad --at exits 1" "$?" "1"
167
+ bash "$EXEC" --log-step intake --start --at 2026-10-26T09:00:00Z --out "$T/tl2" >/dev/null
168
+ bash "$EXEC" --log-step intake --end --at 2026-10-26T08:00:00Z --out "$T/tl2" >/dev/null 2>&1; check "end before start exits 1" "$?" "1"
169
+ printf '{"nope":1}' > "$T/tl3.json"; mkdir -p "$T/tl3"; cp "$T/tl3.json" "$T/tl3/timelog.json"
170
+ bash "$EXEC" --log-step intake --start --out "$T/tl3" >/dev/null 2>&1; check "broken timelog exits 2" "$?" "2"
171
+ bash "$EXEC" --breaks "$T/an/breaks.json" --out "$T/tl3" >/dev/null 2>&1; check "report with broken timelog exits 2" "$?" "2"
172
+
136
173
  echo "checkup-report: $PASS passed, $FAIL failed"
137
174
  [ "$FAIL" -eq 0 ]
@@ -10,6 +10,8 @@
10
10
  // node report.mjs --app-dir ./checkup-app # copy the App page to publish
11
11
  // node report.mjs --breaks …/breaks.json --business "Oak Pottery" [--intake answers.json] --out ./checkup-data
12
12
  // node report.mjs --daily "Replied to 3 open inquiries; 1 booked." [--date 2026-10-27] --out ./checkup-data
13
+ // node report.mjs --log-step intake --start --out ./checkup-data # per-step time log → timelog.json
14
+ // node report.mjs --log-step intake --end --out ./checkup-data
13
15
  // =============================================================================
14
16
  import { readFileSync, writeFileSync, mkdirSync, existsSync, copyFileSync, readdirSync, statSync } from 'node:fs';
15
17
  import { join, resolve, dirname } from 'node:path';
@@ -20,6 +22,9 @@ const APP_SRC = join(HERE, 'app');
20
22
  const DEFAULT_EXAMPLES = 3;
21
23
  /** Crewly App documents are capped at 256 KB; stay well under it. */
22
24
  const MAX_DOC_BYTES = 200 * 1024;
25
+ /** The check-up's steps, in order; the time log accepts no other name. */
26
+ export const STEPS = ['intake', 'connect', 'analyse', 'report', 'daily'];
27
+ const TIMELOG_FILE = 'timelog.json';
23
28
 
24
29
  /** The "which number first" choices offered by the intake form, with how each is read off the breaks. */
25
30
  const FIRST_NUMBER_RULES = [
@@ -60,7 +65,8 @@ function examplesFor(b, limit) {
60
65
  /**
61
66
  * Build the report document the App renders from checkup-analyse output.
62
67
  * Items are summarised into a few examples per break so the document stays small
63
- * and no full list of people lands in the App's data.
68
+ * and no full list of people lands in the App's data. `opts.timelog` (from
69
+ * `readTimelog`) adds a `timing` block with minutes per step.
64
70
  */
65
71
  export function buildReportDoc(breaks, opts = {}) {
66
72
  const by = Object.fromEntries(breaks.breaks.map((b) => [b.type, b]));
@@ -91,9 +97,58 @@ export function buildReportDoc(breaks, opts = {}) {
91
97
  if (rule) doc.firstNumber = { asked: want, key: rule.key, label: rule.label, value: rule.value(by) };
92
98
  else doc.notes = [...doc.notes, `The owner asked to see "${want}" first; no break maps to it, so the top 3 lead.`];
93
99
  }
100
+ // How long the check-up took, when the agent logged its steps (`--log-step`).
101
+ if (opts.timelog && opts.timelog.steps.length) doc.timing = summariseTimelog(opts.timelog);
94
102
  return doc;
95
103
  }
96
104
 
105
+ /** Read `<dir>/timelog.json`; an absent file is an empty log, a broken one throws. */
106
+ export function readTimelog(dir) {
107
+ const file = join(dir, TIMELOG_FILE);
108
+ if (!existsSync(file)) return { steps: [] };
109
+ const raw = JSON.parse(readFileSync(file, 'utf8'));
110
+ if (!raw || !Array.isArray(raw.steps)) throw new Error(`${file}: not a time log ({ steps: [] })`);
111
+ return raw;
112
+ }
113
+
114
+ /**
115
+ * Record the start or end of a check-up step in the time log.
116
+ *
117
+ * `--start` opens an entry (a second start on a still-open step restarts it);
118
+ * `--end` closes the latest open entry of that step and fills in `minutes`.
119
+ * `at` overrides the clock (ISO timestamp) for back-filling and tests.
120
+ *
121
+ * @returns the updated log and the entry touched
122
+ * @throws when the step is unknown, `--end` finds no open entry, or `at` is not a date
123
+ */
124
+ export function logStep(log, step, mark, at) {
125
+ if (!STEPS.includes(step)) throw new Error(`unknown step "${step}"; use one of ${STEPS.join(', ')}`);
126
+ const when = at ? new Date(at) : new Date();
127
+ if (Number.isNaN(when.getTime())) throw new Error(`--at must be an ISO timestamp, got "${at}"`);
128
+ const iso = when.toISOString();
129
+ const open = [...log.steps].reverse().find((s) => s.step === step && !s.endedAt);
130
+ let entry;
131
+ if (mark === 'start') {
132
+ if (open) { open.startedAt = iso; entry = open; }
133
+ else { entry = { step, startedAt: iso, endedAt: null, minutes: null }; log.steps.push(entry); }
134
+ } else {
135
+ if (!open) throw new Error(`no open "${step}" entry to end; run --log-step ${step} --start first`);
136
+ if (when.getTime() < new Date(open.startedAt).getTime()) throw new Error(`end (${iso}) is before start (${open.startedAt})`);
137
+ open.endedAt = iso;
138
+ open.minutes = Math.round((when.getTime() - new Date(open.startedAt).getTime()) / 6000) / 10;
139
+ entry = open;
140
+ }
141
+ return { log, entry };
142
+ }
143
+
144
+ /** Fold a time log into the shape the report document carries: closed steps with minutes, plus the total. */
145
+ export function summariseTimelog(log) {
146
+ const steps = log.steps.map((s) => ({ step: s.step, startedAt: s.startedAt, endedAt: s.endedAt, minutes: s.minutes }));
147
+ const closed = steps.filter((s) => s.endedAt);
148
+ const totalMinutes = Math.round(closed.reduce((a, s) => a + (s.minutes || 0), 0) * 10) / 10;
149
+ return { steps, totalMinutes, open: steps.filter((s) => !s.endedAt).map((s) => s.step) };
150
+ }
151
+
97
152
  /** Read intake answers from a plain document or from app-data's `{ success, data }` output. */
98
153
  function readIntake(path) {
99
154
  const raw = JSON.parse(readFileSync(path, 'utf8'));
@@ -119,7 +174,7 @@ function out(obj, code) {
119
174
  }
120
175
 
121
176
  function usage() {
122
- out({ success: false, error: 'usage: report.mjs --app-dir <dir> | --breaks <breaks.json> [--business <name>] [--intake <answers.json>] [--examples N] --out <dir> | --daily "<summary>" [--date YYYY-MM-DD] --out <dir>' }, 1);
177
+ out({ success: false, error: 'usage: report.mjs --app-dir <dir> | --breaks <breaks.json> [--business <name>] [--intake <answers.json>] [--examples N] --out <dir> | --daily "<summary>" [--date YYYY-MM-DD] --out <dir> | --log-step <intake|connect|analyse|report|daily> --start|--end [--at <iso>] --out <dir>' }, 1);
123
178
  }
124
179
 
125
180
  function main(argv) {
@@ -135,14 +190,39 @@ function main(argv) {
135
190
  else if (a === '--out') o.out = next();
136
191
  else if (a === '--daily') o.daily = next();
137
192
  else if (a === '--date') o.date = next();
193
+ else if (a === '--log-step') o.logStep = next();
194
+ else if (a === '--start') o.mark = 'start';
195
+ else if (a === '--end') o.mark = 'end';
196
+ else if (a === '--at') o.at = next();
138
197
  else usage();
139
198
  }
140
- if (!o.appDir && !o.breaks && !o.daily) usage();
141
- if ((o.breaks || o.daily) && !o.out) usage();
199
+ if (!o.appDir && !o.breaks && !o.daily && !o.logStep) usage();
200
+ if ((o.breaks || o.daily || o.logStep) && !o.out) usage();
201
+ if (o.logStep && !o.mark) usage();
202
+ if (o.mark && !o.logStep) usage();
142
203
  if (o.date && !/^\d{4}-\d{2}-\d{2}$/.test(o.date)) out({ success: false, error: `--date must be YYYY-MM-DD, got "${o.date}"` }, 1);
143
204
  if (!Number.isFinite(o.examples) || o.examples < 0) out({ success: false, error: '--examples needs a number >= 0' }, 1);
144
205
 
145
206
  const result = { success: true };
207
+ if (o.logStep) {
208
+ let log;
209
+ try {
210
+ log = readTimelog(o.out);
211
+ } catch (e) {
212
+ out({ success: false, error: e.message }, 2);
213
+ }
214
+ let entry;
215
+ try {
216
+ ({ entry } = logStep(log, o.logStep, o.mark, o.at));
217
+ } catch (e) {
218
+ out({ success: false, error: e.message }, o.mark === 'end' && /no open/.test(e.message) ? 2 : 1);
219
+ }
220
+ mkdirSync(o.out, { recursive: true });
221
+ writeFileSync(join(o.out, TIMELOG_FILE), JSON.stringify(log, null, 2));
222
+ result.timelog = resolve(join(o.out, TIMELOG_FILE));
223
+ result.entry = entry;
224
+ result.timing = summariseTimelog(log);
225
+ }
146
226
  if (o.appDir) {
147
227
  result.appDir = resolve(o.appDir);
148
228
  result.copied = copyApp(o.appDir);
@@ -166,7 +246,13 @@ function main(argv) {
166
246
  out({ success: false, error: `${o.intake}: ${e.message}` }, 2);
167
247
  }
168
248
  }
169
- const doc = buildReportDoc(breaks, { business: o.business, intake, examples: o.examples });
249
+ let timelog = null;
250
+ try {
251
+ timelog = readTimelog(o.out);
252
+ } catch (e) {
253
+ out({ success: false, error: e.message }, 2);
254
+ }
255
+ const doc = buildReportDoc(breaks, { business: o.business, intake, examples: o.examples, timelog });
170
256
  const json = JSON.stringify(doc, null, 2);
171
257
  if (Buffer.byteLength(json) > MAX_DOC_BYTES) out({ success: false, error: `report document is ${Buffer.byteLength(json)} bytes; lower --examples` }, 2);
172
258
  mkdirSync(o.out, { recursive: true });
@@ -175,6 +261,7 @@ function main(argv) {
175
261
  result.top3 = doc.top3;
176
262
  result.totals = doc.totals;
177
263
  result.firstNumber = doc.firstNumber || null;
264
+ result.timing = doc.timing || null;
178
265
  result.next = `app-data --app <appId> --set report current --data-file ${join(o.out, 'report-doc.json')}`;
179
266
  }
180
267
  if (o.daily) {
@@ -0,0 +1,22 @@
1
+ {
2
+ "id": "small-business-checkup",
3
+ "name": "Small Business Check-up",
4
+ "description": "Runs the small-business check-up: five intake questions on a phone-sized App, exports or connectors from the business's apps, an analysis of where those apps disagree (missed inquiries, unpaid sign-ups, overdue invoices, double entry, lapsed customers) with an hours/week and $/month estimate per break, a report in the App, and one summary line a day for 7 days. A lead, an analyst and an optional reviewer; remove the Reviewer to skip the review step.",
5
+ "members": [
6
+ {
7
+ "name": "Checkup Lead",
8
+ "role": "product-manager",
9
+ "systemPrompt": "You run the small-business check-up end to end for one business owner. The four steps are intake, connect, analyse, report; log the start and end of each one with the 'checkup-report' skill (--log-step <step> --start / --end --out ./checkup-data) so the owner can see how long the check-up really took. Intake: run 'checkup-report --app-dir ./checkup-app', then 'publish-app' so the owner gets the App on their phone, and wait for the five answers (app-data --get intake answers). Connect and analyse: hand the Data Analyst the list of apps from the answers and the exports or connector access the owner provides; the analyst returns the path to breaks.json. Report: build the report document with 'checkup-report --breaks ... --business ... --intake ... --out ./checkup-data'. If a Reviewer is on the team, send them the report-doc.json path and wait for 'approved' or a list of fixes before publishing; if there is no Reviewer, check the document yourself and publish. Publish with 'app-data --set report current' and share the App link. Then post one daily summary for 7 days with 'checkup-report --daily'. Tell the owner the top 3 in plain words with count and estimate, say that the dollars are estimates from listed assumptions, and name the one thing you would start on. Never paste the whole list of people into chat or the App, and never send anything to a third party without the owner's say-so."
10
+ },
11
+ {
12
+ "name": "Data Analyst",
13
+ "role": "researcher",
14
+ "systemPrompt": "You do the connect and analyse steps of the small-business check-up. For each app the owner named, pick the path: a Crewly connector when one exists, otherwise a CSV export loaded with the 'ingest-exports' skill (Square, QuickBooks Online, Mailchimp, Jotform, Jackrabbit, Mindbody, Calendly and Stripe exports are recognised by their headers; an unknown CSV still loads with a 'could not map' note). Use --hash-names when the exports hold children's names or the owner asks for it. Then run the 'checkup-analyse' skill on the ingested folder with the as-of date the lead gives you; it writes breaks.json and report.md. Report back to the Checkup Lead with: the files loaded (source, kind, rows, date range), which checks the data could not support, the path to breaks.json, and anything that looked wrong in the exports (duplicate people, dates in the future, empty columns). Keep every file inside the project; do not paste rows of people into chat. Ask the lead, not the owner, when something is missing."
15
+ },
16
+ {
17
+ "name": "Reviewer",
18
+ "role": "qa",
19
+ "systemPrompt": "You review the check-up report before it reaches the business owner. This member is optional: the owner removes you from the team to switch the review step off, and the Checkup Lead then publishes without a review. When the lead sends you a report-doc.json path, check it against breaks.json and the intake answers: every dollar figure has a formula and is marked as an estimate; the top 3 are the three largest $/month breaks; the number the owner asked to see first is present and matches the answer, or a note explains why not; examples never contain hashed ids (h:...) or more than three people per break; every check the data could not support has a note; the assumptions listed are the ones the analysis used; the business name is right. Reply to the lead with exactly 'approved' or a numbered list of fixes, each one a single line saying what is wrong and what it should be. Do not rewrite the report yourself and do not contact the owner."
20
+ }
21
+ ]
22
+ }
@@ -1,4 +1,4 @@
1
1
  {
2
- "commit": "c6b8bd2a15dd18c54fdac4989ef5e0c8c014514c",
3
- "builtAt": "2026-10-26T19:09:40.077Z"
2
+ "commit": "4911e107e2bed7bdc4e6e1e5371674a7ff5a663a",
3
+ "builtAt": "2026-10-26T19:47:51.620Z"
4
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "crewly",
3
- "version": "1.20.450",
3
+ "version": "1.20.451",
4
4
  "type": "module",
5
5
  "description": "Multi-agent orchestration platform for AI coding teams — coordinates Claude Code, Gemini CLI, and Codex agents with a real-time web dashboard",
6
6
  "workspaces": [