AgentStack
Browse Sign in
Browse Why AgentStack Sell Docs
Sign in
SKILL verified Apache-2.0 Self-run

Weekly Store Report

skill-storehand-storehand-weekly-store-report · by storehand

Read-only weekly report on what changed in a Shopify store against the previous whole week — revenue, orders, average order value, sessions and conversion, with the figures that have a second source cross-checked against the order records. Use when the user asks how last week went, for a weekly report, week-on-week numbers, or whether sales or conversion are moving. Not for today or yesterday, wh…

No reviews yet
0 installs
24 views
0.0% view→install

Install

$ agentstack add skill-storehand-storehand-weekly-store-report

✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution No
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

View the full security report →

Verified badge

Passed review? Show it. Paste this badge into your README, it links to the public security report.

AgentStack Verified badge Links to your public security report.
[![AgentStack Verified](https://agentstack.voostack.com/badges/verified.svg)](https://agentstack.voostack.com/security/report/skill-storehand-storehand-weekly-store-report)

Reliability & compatibility

Security review passed
0 installs to date
no reviews yet
1mo ago

Declared compatibility

Claude CodeClaude Desktop

Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.

Preview Execution monitoring

We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.

How agent discovery & health will work →
Are you the author of Weekly Store Report? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Weekly store report

What changed since the week before. One read-only run per week. It writes nothing to the store and asks for no write scope — never add --allow-mutations to any command in this skill.

Network: none

Two kinds of path, do not mix them up. Files belonging to this plugin — shared/*.md and the queries/ directory — live under the plugin's install directory. The store profile (.storehand/) lives in the user's working directory.

Step 0 — Find the plugin

$CLAUDE_PLUGIN_ROOT is empty in the environment of a Bash tool call. That is measured, not assumed; the working routes and the reasoning are in shared/plugin-root.md. Run this once, before anything else:

ROOT=$( {
  printf '%s\n' "${CLAUDE_PLUGIN_ROOT:-}"
  printf '%s' "$PATH" | tr ':' '\n' | sed -n 's|/bin$||p' | grep -E '/storehand/[^/]+$'
  node -e 'const fs=require("fs"),os=require("os"),p=require("path");try{const j=JSON.parse(fs.readFileSync(p.join(os.homedir(),".claude","plugins","installed_plugins.json"),"utf8"));for(const[k,v]of Object.entries(j.plugins||{}))if(k.split("@")[0]==="storehand"&&v[0]&&v[0].installPath){console.log(v[0].installPath);break}}catch{}' 2>/dev/null
} | while IFS= read -r c; do
  [ -n "$c" ] && [ -f "$c/shared/api-version.md" ] && { printf '%s' "$c"; break; }
done )
[ -n "$ROOT" ] && echo "StoreHand plugin root: $ROOT" || echo "StoreHand plugin root NOT FOUND"

Shell state does not survive between tool calls, so there is nothing to export into: take the path it printed and substitute it literally wherever these instructions write $CLAUDE_PLUGIN_ROOT. Never guess it.

Printed NOT FOUND? Stop, and tell the user to reinstall the plugin with /plugin marketplace add storehand/storehand.

Read $CLAUDE_PLUGIN_ROOT/shared/safety.md and $CLAUDE_PLUGIN_ROOT/shared/store-profile.md before you start.

Step 1 — Load the profile and the memory

Read .storehand/store.yaml: you need store and the store's timezone. No .storehand/? Point the user at /storehand:storehand-setup and stop.

Read .storehand/state.json and keep the whole object in hand — other skills store their keys there and they must survive your write in Step 5. Under weeklyReport you may find lastWeekReported (an ISO week like 2026-W31).

  • File absent, or no weeklyReport key → this is the first report: say so.
  • lastWeekReported equals the week you are about to report → say the

report is unchanged since the last run and ask whether to print it again, rather than presenting old news as new.

  • File present but unparseable → say so in the report, run anyway, and do

not write state.json at the end. Overwriting a file you could not parse destroys another skill's memory along with your own.

Step 2 — Work out the two weeks

Weeks run Monday 00:00:00 to Sunday 23:59:59, in the store's own timezone as recorded in .storehand/store.yaml.

Report the last closed week — the most recent Sunday that has already passed, and the six days before it. Compare it against the seven days before that.

Never compare a partial week against a whole one. A Wednesday run that put three days next to seven would print a column of negative percentages that mean nothing, which is precisely the false signal this skill exists to avoid. If today is not a Monday, say in the report which week was covered and that the current one is still running.

State both ranges in the report header so the reader can check them:

WEEK 31 · 28 Jul – 3 Aug previous: 21–27 Jul

Step 3 — Run the queries

Check $CLAUDE_PLUGIN_ROOT/shared/api-version.md; if it names a pinned version, pass --version on every call. All calls are read-only — never add --allow-mutations. Write variables to a file, never inline, because quoting damage is silent:

V="$(mktemp -d)"
printf '%s' '{"query":"FROM sales SHOW total_sales, net_sales, orders, average_order_value SINCE 2026-07-28 UNTIL 2026-08-03"}' > "$V/q.json"
shopify store execute --store  --json \
  --query-file "$CLAUDE_PLUGIN_ROOT/skills/weekly-store-report/queries/shopifyql.graphql" \
  --variable-file "$V/q.json"

Four calls in total: the sales query and the sessions query, each for the reported week and for the week before.

| Dataset | Columns | |---|---| | FROM sales | total_sales, net_sales, orders, average_order_value | | FROM sessions | sessions, conversion_rate |

These six are the whole list. orders and products are not ShopifyQL datasets — only sales and sessions are. There is no returning-customer column: four candidate names were tried against a live store on 2026-08-05 and every one returned Column Not Found. If a merchant asks for returning customers, say the metric is not available through this interface. Do not guess a seventh name.

Read the column list, not the labels. displayName comes back translated into the store's own language — total_sales arrives as "Totale omzet" on a Dutch store. Match on name for every decision you make. displayName is safe to print, and is the better label to print, because it matches what the owner sees in their own admin. Never branch on displayName.

Step 3b — Re-check what came back

Every StoreHand skill re-checks its own output before printing it. An aggregate has no records to walk, so this one checks in two layers.

Layer 1 — was the question valid

Before reading a single row:

  • parseErrors must be empty. If it is not, the query was wrong, not the store.

Print the parse error and stop. Never report a number you did not measure, and a query that failed to parse measured nothing.

  • Every column you asked for must appear in columns. A column that silently

went missing is the same failure wearing a friendlier face.

  • A present column can still hold null. Measured on a live store on

2026-08-05: with no orders in the week, average_order_value came back null while total_sales and orders came back "0". Null is not zero and it is not a hundred percent drop. Print the row as with a word for why, and never compute a percentage change from it or into it.

ShopifyQL fails loudly here, which is the opposite of the Admin API search filters recorded in the probes of 2026-07-30. A bad ShopifyQL column is an error; a bad Admin API filter term is silently ignored and returns everything. Do not carry an instinct from one to the other.

Layer 2 — was the answer right

total_sales and orders have a second, independent source: the order records themselves. Fetch them for the same week with the query the briefing already ships —

printf '%s' '{"query":"created_at:>='2026-07-28T00:00:00Z' AND created_at: "$V/o.json"
shopify store execute --store  --json \
  --query-file "$CLAUDE_PLUGIN_ROOT/skills/daily-store-briefing/queries/orders-since.graphql" \
  --variable-file "$V/o.json"

— then count the nodes and sum totalPriceSet.shopMoney.amount, and put the two measurements of the same week side by side.

Print both numbers whenever they differ. Never pick a winner. A silent preference for one source is indistinguishable from not having checked.

There is no tolerance, deliberately. total_sales is a defined Shopify metric, not a synonym for "add up the order totals" — discounts, shipping, taxes and returns may each sit on a different side of that definition. Until a run against a store with real revenue establishes what the normal gap is and why, report the difference as unexplained, not as wrong. Inventing a tolerance before measuring one is exactly the move the rest of this skill refuses to make.

If pageInfo.hasNextPage is true on the orders query, say the cross-check covered only the first 250 orders and is therefore incomplete. An incomplete cross-check is not a passed one.

What both outcomes look like

Agreement:

Revenue and orders cross-checked against the order records: they agree.

Disagreement:

Revenue and orders do not match the order records: ShopifyQL total_sales €3,890.00 Sum of 42 order records €4,102.50 difference €212.50, unexplained Both figures are printed because it is not known which definition the gap comes from. Do not treat either as the corrected one.

What cannot be checked

sessions and conversion_rate have no second source. Nothing else in the Shopify API computes them, so there is nothing to compare against. Say this once at the bottom of the report. Two verified numbers must not lend borrowed credibility to two unverified ones sitting in the same table.

Step 4 — Report

Fixed shape. One table, sorted by size of change, largest first by absolute percentage:

WEEK 31 · 28 Jul – 3 Aug          previous: 21–27 Jul

Conversion    1.9%   → 1.2%      −37%   (48 → 42 orders)
Sessions      2,140  → 1,510     −29%
Net revenue   €3,980 → €3,610     −9%
AOV           €85.80 → €92.60     +8%
Orders        48     → 42         −7%
Revenue       €4,120 → €3,890     −6%

Revenue and orders cross-checked against the order records: they agree.
Sessions and conversion come from ShopifyQL only — nothing else in the
Shopify API computes them, so they are reported unverified.

Three rules hold this shape together, and each exists because the obvious alternative would need a number nobody measured:

  1. Nothing is suppressed. Every metric appears every week, including the

ones that barely moved. Hiding a quiet row needs a threshold, and no threshold here has been measured.

  1. Absolute values sit next to every percentage. −37% on 42 orders is six

orders. Show both and the reader sees the size without being told what to conclude.

  1. No verdict. No severity words, no warning markers. Sort order carries the

emphasis and asserts nothing.

Do not add a summary sentence that interprets the table. If the owner wants to know why sessions fell, that is a different question and a different tool.

Step 5 — Update the marker

Only after a successful report, write .storehand/state.json: take the object from Step 1, replace only the weeklyReport key with { "lastWeekReported": "", "lastRunAt": "" }, and write the whole object back. A skill that rebuilds this file from scratch erases another skill's memory along with its own.

A run that stopped at a parse error, or one where the state file could not be parsed in Step 1, writes nothing.

Errors — never report a number you did not measure

  • Parse error from ShopifyQL → print it, report nothing from that query.
  • A column missing from columns → treat it as absent, not as zero.
  • Orders query failed → the cross-check did not happen. Say that, and do not

present the ShopifyQL figures as verified.

  • Store timezone missing from the profile → ask for it rather than assuming UTC.

A week boundary in the wrong timezone moves revenue between weeks.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet, be the first.

Versions

  • v0.1.0 Imported from the upstream source.