@aiguru/google-web-operations 0.6.4
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/.env.example +42 -0
- package/README.md +110 -0
- package/apps/cli/src/index.js +286 -0
- package/apps/mcp-server/src/index.js +360 -0
- package/apps/worker/src/index.js +96 -0
- package/apps/worker/src/loop.js +46 -0
- package/bin/gwo.js +67 -0
- package/docs/BOOTSTRAP-NEXT-PROMPT.md +57 -0
- package/docs/GETTING-STARTED.he.md +63 -0
- package/docs/HOSTED-WORKER.md +36 -0
- package/docs/INSTALL.he.md +57 -0
- package/docs/PRODUCT-TECHNICAL-SPEC-v0.2.md +153 -0
- package/docs/PRODUCT-TECHNICAL-SPEC-v0.3.md +86 -0
- package/docs/PRODUCT-TECHNICAL-SPEC-v0.4.md +92 -0
- package/docs/PRODUCT-TECHNICAL-SPEC-v0.5.md +94 -0
- package/docs/PRODUCT-TECHNICAL-SPEC-v0.6.md +98 -0
- package/docs/SECURITY.md +69 -0
- package/docs/SITE-ADAPTERS.md +87 -0
- package/docs/adr/ADR-001-shared-mcp-core.md +27 -0
- package/docs/adr/ADR-002-policy-gated-writes.md +18 -0
- package/docs/adr/ADR-003-single-source-playbooks.md +18 -0
- package/docs/adr/ADR-004-native-typescript-runtime.md +16 -0
- package/docs/adr/ADR-005-two-token-publish.md +22 -0
- package/docs/adr/ADR-006-one-package-one-installer.md +20 -0
- package/hosts/claude/.claude-plugin/plugin.json +9 -0
- package/hosts/claude/.mcp.json +10 -0
- package/hosts/claude/README.md +7 -0
- package/hosts/claude/commands/gwo-add-site.md +7 -0
- package/hosts/claude/commands/gwo-daily.md +7 -0
- package/hosts/claude/commands/gwo-measure.md +7 -0
- package/hosts/claude/commands/gwo-monthly.md +7 -0
- package/hosts/claude/commands/gwo-onboard.md +7 -0
- package/hosts/claude/commands/gwo-scan.md +7 -0
- package/hosts/claude/commands/gwo-setup.md +7 -0
- package/hosts/claude/commands/gwo-triage.md +7 -0
- package/hosts/claude/commands/gwo-weekly.md +7 -0
- package/hosts/claude/skills/gwo-client-report-template/SKILL.md +36 -0
- package/hosts/claude/skills/gwo-content-brief/SKILL.md +60 -0
- package/hosts/claude/skills/gwo-daily/SKILL.md +47 -0
- package/hosts/claude/skills/gwo-measurement/SKILL.md +92 -0
- package/hosts/claude/skills/gwo-monthly/SKILL.md +30 -0
- package/hosts/claude/skills/gwo-onboarding/SKILL.md +27 -0
- package/hosts/claude/skills/gwo-operating-model/SKILL.md +76 -0
- package/hosts/claude/skills/gwo-setup/SKILL.md +42 -0
- package/hosts/claude/skills/gwo-triage/SKILL.md +37 -0
- package/hosts/claude/skills/gwo-weekly/SKILL.md +48 -0
- package/hosts/codex/.codex/config.template.toml +5 -0
- package/hosts/codex/.codex-plugin/plugin.json +30 -0
- package/hosts/codex/AGENTS.md +470 -0
- package/hosts/codex/README.md +13 -0
- package/hosts/codex/skills/gwo-client-report-template/SKILL.md +36 -0
- package/hosts/codex/skills/gwo-content-brief/SKILL.md +60 -0
- package/hosts/codex/skills/gwo-daily/SKILL.md +47 -0
- package/hosts/codex/skills/gwo-measurement/SKILL.md +92 -0
- package/hosts/codex/skills/gwo-monthly/SKILL.md +30 -0
- package/hosts/codex/skills/gwo-onboarding/SKILL.md +27 -0
- package/hosts/codex/skills/gwo-operating-model/SKILL.md +76 -0
- package/hosts/codex/skills/gwo-setup/SKILL.md +42 -0
- package/hosts/codex/skills/gwo-triage/SKILL.md +37 -0
- package/hosts/codex/skills/gwo-weekly/SKILL.md +48 -0
- package/package.json +24 -0
- package/packages/connectors/browser/src/index.js +122 -0
- package/packages/connectors/docx/src/index.js +122 -0
- package/packages/connectors/github/src/index.js +102 -0
- package/packages/connectors/google/src/auth.js +73 -0
- package/packages/connectors/google/src/discover.js +135 -0
- package/packages/connectors/google/src/ga4.js +42 -0
- package/packages/connectors/google/src/gsc.js +49 -0
- package/packages/connectors/google/src/gtm.js +261 -0
- package/packages/connectors/google/src/index.js +8 -0
- package/packages/connectors/google/src/oauth.js +198 -0
- package/packages/connectors/google/src/pagespeed.js +48 -0
- package/packages/connectors/google/src/token-store.js +89 -0
- package/packages/connectors/odoo/src/index.js +206 -0
- package/packages/connectors/repo/src/index.js +94 -0
- package/packages/connectors/resend/src/index.js +30 -0
- package/packages/connectors/web-crawler/src/a11y.js +91 -0
- package/packages/connectors/web-crawler/src/fetch.js +165 -0
- package/packages/connectors/web-crawler/src/index.js +69 -0
- package/packages/connectors/web-crawler/src/parse.js +157 -0
- package/packages/core/src/adapters.js +27 -0
- package/packages/core/src/admin.js +232 -0
- package/packages/core/src/content.js +213 -0
- package/packages/core/src/doctor.js +39 -0
- package/packages/core/src/escalate.js +36 -0
- package/packages/core/src/gtm-ops.js +311 -0
- package/packages/core/src/index.js +13 -0
- package/packages/core/src/operations.js +239 -0
- package/packages/core/src/paths.js +46 -0
- package/packages/core/src/registry.js +114 -0
- package/packages/core/src/report/monthly.js +272 -0
- package/packages/core/src/scan/render.js +40 -0
- package/packages/core/src/scan/rules.js +289 -0
- package/packages/core/src/scan/suppress.js +37 -0
- package/packages/core/src/types.js +10 -0
- package/packages/core/src/writes.js +50 -0
- package/packages/playbooks/00-operating-model.md +74 -0
- package/packages/playbooks/10-daily.md +45 -0
- package/packages/playbooks/20-weekly.md +46 -0
- package/packages/playbooks/30-monthly.md +28 -0
- package/packages/playbooks/40-triage.md +35 -0
- package/packages/playbooks/50-onboarding.md +25 -0
- package/packages/playbooks/60-content-brief.md +58 -0
- package/packages/playbooks/70-measurement.md +90 -0
- package/packages/playbooks/80-setup.md +40 -0
- package/packages/playbooks/90-client-report-template.md +34 -0
- package/packages/policy/src/index.js +115 -0
- package/packages/storage/src/index.js +131 -0
- package/policies/default.yaml +90 -0
- package/sites/schema.yaml +71 -0
- package/sites/secrets.example.yaml +6 -0
- package/tools/build-hosts.js +118 -0
- package/tools/build-package.js +43 -0
- package/tools/install.js +145 -0
- package/tools/schedule.ps1 +37 -0
- package/tools/smoke-mcp.js +61 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Google Web Operations: Product and Technical Specification v0.6
|
|
2
|
+
|
|
3
|
+
Supersedes v0.5 (kept for history). v0.6 closes the roadmap that did not need a client's Tag Manager: rendered-page inspection with Playwright, a hosted worker (container image and built-in scheduler), Odoo product pages, and a pass over the human side of the package: `gwo doctor`, a grouped `gwo` help, a friendlier install, a Hebrew getting-started guide. The Tag Manager live validation waits for the first client container.
|
|
4
|
+
|
|
5
|
+
## 1. Purpose
|
|
6
|
+
|
|
7
|
+
Google Web Operations is an operational control layer for a portfolio of client websites. It observes technical health, search visibility, answer-engine readiness and measurement; turns observations into ranked findings; decides against playbooks; acts within a strict permission model; verifies every write; and records everything. It runs identically from Claude Code and Codex through one MCP server, and its sensing half runs on a schedule without any agent.
|
|
8
|
+
|
|
9
|
+
## 2. Principles
|
|
10
|
+
|
|
11
|
+
Unchanged from v0.2: one source of truth, host-neutral, observe before changing, policy before action, verify every write, auditability, no secret sprawl, background-capable, bounded output.
|
|
12
|
+
|
|
13
|
+
New in v0.4: **a set of changes is reviewed as a set.** Where a write's effect is a bundle rather than one value (a container version), the operator's approval alone is not enough; the write also carries a token derived from the exact bundle that was shown (ADR-005).
|
|
14
|
+
|
|
15
|
+
## 3. What v0.6 delivers (cumulative since v0.3)
|
|
16
|
+
|
|
17
|
+
| Area | Delivered |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Rendered checks | `web_inspect_rendered`: headless Chromium through Playwright (found in the package, the global npm root or `PLAYWRIGHT_PATH`; never a dependency), compared with the static HTML: word ratio, headings, JSON-LD and form fields that appear only after JavaScript; verdict server-rendered / partly-js / js-dependent with plain-language notes. Missing Playwright or browser produce the exact install command |
|
|
20
|
+
| Hosted worker | `gwo worker loop` (daily and weekly at configured times in `TZ`, each run a child process with its exit code logged), `deploy/Dockerfile` and `deploy/compose.yml` (compiled package, state volume at `/home/gwo/.gwo`, AES token store), `docs/HOSTED-WORKER.md` with the token hand-over |
|
|
21
|
+
| Odoo products | `product.template` addressed by `/shop/<slug>-<id>`: SEO fields and JSON-LD in `website_description`; category listings excluded |
|
|
22
|
+
| Human side | `gwo doctor` (checklist with next steps; the same text rides in `ops_setup_status.rendered`), grouped `gwo` help, install output that says what to do next, `docs/GETTING-STARTED.he.md` |
|
|
23
|
+
| Distribution | npm package `@aiguru/google-web-operations` on GitHub Packages (`npm i -g @aiguru/google-web-operations`, `gwo update` for the latest release; a token with read:packages once per machine) with the `gwo` executable: `gwo install [claude\|codex\|all]` writes the user-scope MCP server (absolute node and package paths), skills and slash commands for Claude Code (`~/.claude.json`, `~/.claude/skills`, `~/.claude/commands`) and the MCP block and skills for Codex (`~/.codex/config.toml`, `~/.codex/skills`); idempotent, `uninstall` removes only `gwo-*` entries. State in `GWO_HOME` (default `~/.gwo`: sites, .gwo-data, secrets, .env, optional policy); a checkout keeps state next to the code. The package ships compiled JavaScript (`tools/build-package.ts`, tsc with rewritten `.ts` imports) because Node does not strip types under `node_modules`; development stays native TypeScript. `npm run check` builds and runs the packaged bin. Release workflow packs a tarball on `v*` tags (ADR-006) |
|
|
24
|
+
| Accessibility (IS 5568) | New `accessibility` rule area from static HTML (`packages/connectors/web-crawler/src/a11y.ts`): unlabelled form fields (high), unnamed buttons, empty and generic links, untitled iframes, blocked pinch zoom, skipped heading levels, positive tabindex, duplicate ids, autoplay media, missing main landmark, missing skip link; site-wide `a11y.statement.missing` (high, the Israeli legal requirement). Contrast, keyboard behaviour and PDFs are declared out of scope in the monthly playbook |
|
|
25
|
+
| Tag Manager writes | Workspace creation under the policy prefix; `gtm_stage` writes tags, triggers and variables one verified write each (create, or merge-update by name; trigger names resolve to ids; triggers before tags); `gtm_workspace_diff` shapes GTM's own change list against the live version and derives a review token; `gtm_publish` syncs, re-checks the token, creates the version, publishes, verifies the live version id, and snapshots. `gtm_workspaces` shows staged and client-owned work |
|
|
26
|
+
| Content writes | `content_set_title` (approval always, validated: 15 to 65 characters, one separator, no repeated term); `content_set_schema` (approval always, validated: schema.org context and type, FAQ shape, no script break, 20 KB cap; empty list removes); `site_page_schema` reads managed blocks next to what the live page emits |
|
|
27
|
+
| Adapters | `readPageSchema` / `writePageSchema` on the adapter interface. Repository: `schema` array in the manifest entry. Odoo: one marked `<script type="application/ld+json" data-gwo="schema">` in the page view's arch (before `#wrap`, outside the editor-owned structure) or appended to a blog post's content; XML-safe serialisation; other scripts never touched |
|
|
28
|
+
| Site decisions | `findings.suppress` in the site file: rule (exact or `area.*`), optional path (exact or prefix), reason. Applied after the rules run; suppressed findings are counted per rule in the scan header and audit log, never hidden. Added after the first developer review of a pilot site, where GA4-without-GTM and a short contact page were deliberate |
|
|
29
|
+
| Setup from chat | `ops_setup_status` (one read: OAuth client, authorised account and scopes, write scopes, .env settings, per-site configuration and what is missing, next steps), `google_setup_oauth_client` (installs the downloaded Desktop-app JSON by path; contents never pass through chat), `google_auth_login` (consent flow from the host, waits ~90 s then continues in the background), `google_discover_apply`, `ops_add_site` (standard site file from url, name, platform), `ops_update_site` (dotted path, comments kept; site_id, policy and credentials refused), `ops_add_suppression`. All classed `auto` as this system's own configuration and recorded in the audit log. New `gwo-setup` skill and `/gwo-setup`, `/gwo-add-site` commands |
|
|
30
|
+
| Client report | `report_client_monthly`: collects Search Console and GA4 totals for the month and the previous one, scan health now and a month ago, and the month's audit activity; composes the client document (Hebrew or English) per the report template with the agent's narrative on top; writes a .docx inside the company template (`GWO_DOCX_TEMPLATE`, header, footer and styles inherited, body replaced) or a standalone styled package, plus a Markdown twin. RTL by paragraph, run and table. Own OOXML writer over jszip; no other dependency |
|
|
31
|
+
| Playbooks | New `gwo-measurement`: the standard stack per client, the audit, the stage-diff-publish protocol, entity bodies, key events, what to do when the client publishes. Weekly, daily, triage, onboarding and the content brief updated to name the new tools and their gates. Operating model gains a tools-by-intent table |
|
|
32
|
+
| Hosts | Installed by `gwo install` from the generated `hosts/` tree; 10 skills and 9 commands (`/gwo-measure`, `/gwo-setup`, `/gwo-add-site` added) for Claude Code; the same 10 skills for Codex; both generated |
|
|
33
|
+
| Quality | 92 tests: rendered comparison (a real browser when available), Odoo products, accessibility parsing and rules, path resolution and the installer against temporary homes, setup and registry administration, GTM pure helpers, suppressions, report composition (no jargon leaks), the Word writer standalone and inside the template, a fake Tag Manager API exercising blocked, staged, merged, stale-token and successful publish paths; title and schema validators and writes; Odoo arch injection and blog injection; repo schema round trip |
|
|
34
|
+
|
|
35
|
+
Carried from v0.3 unchanged: OAuth with encrypted refresh token, Search Console, GA4, PageSpeed and Tag Manager reads, meta-description writes, key events, sitemap submission, pull requests, the scheduled worker with escalation and delivery, CI.
|
|
36
|
+
|
|
37
|
+
## 4. Architecture
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
Claude Code ──┐
|
|
41
|
+
├── apps/mcp-server ──┐
|
|
42
|
+
Codex ────────┘ │
|
|
43
|
+
apps/worker (Task Scheduler) ───────┼── packages/core ── connectors/{web-crawler, google, odoo, repo, github, resend}
|
|
44
|
+
apps/cli ───────────────────────────┘ ├── packages/policy
|
|
45
|
+
└── packages/storage
|
|
46
|
+
packages/playbooks ── tools/build-hosts ──> hosts/claude, hosts/codex
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
`bin/gwo.js` dispatches to the MCP server, worker, installer or CLI. `packages/core/src/paths.ts` decides checkout versus installed layout. `packages/core/src/gtm-ops.ts` holds every Tag Manager operation, read and write; `content.ts` holds the three content writes and their validators; `report/monthly.ts` the client report; `connectors/docx` the Word writer. TypeScript executed natively by Node 22.18+. No build step. `tsc --noEmit` in CI.
|
|
50
|
+
|
|
51
|
+
## 5. Operational loop
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
Observe -> Classify -> Authorise -> Execute -> Verify -> Record
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
Every write goes through `performWrite()`. A write whose verification fails is a failed write. Blocked attempts are recorded. For a container publish, "Authorise" is two tokens.
|
|
58
|
+
|
|
59
|
+
## 6. Risk classes
|
|
60
|
+
|
|
61
|
+
| Class | Gate |
|
|
62
|
+
|---|---|
|
|
63
|
+
| `auto` | none |
|
|
64
|
+
| `approval_required` | token `<site_id>:<action>` in-session |
|
|
65
|
+
| `approval_required` + review | the above plus the review token of the reviewed diff (`gtm.publish`) |
|
|
66
|
+
| `protected` | never |
|
|
67
|
+
|
|
68
|
+
`policies/default.yaml`; site overrides may only tighten. No policy ids were added in v0.4: `gtm.stage`, `gtm.publish`, `content.title.update` and `content.schema.update` were already listed and are now implemented.
|
|
69
|
+
|
|
70
|
+
## 7. Schedules
|
|
71
|
+
|
|
72
|
+
| Cadence | Pages | Worker does | Agent does |
|
|
73
|
+
|---|---|---|---|
|
|
74
|
+
| Daily 07:00 | 25 | scan, diff, escalate, report, deliver | triage anything escalated |
|
|
75
|
+
| Weekly Monday 07:30 | 200 | scan, diff, escalate, report, deliver | the weekly improvement pass |
|
|
76
|
+
| Monthly | 200 | on demand | deep scan and client report |
|
|
77
|
+
|
|
78
|
+
Exit codes: 0 healthy, 2 escalations, 1 failures.
|
|
79
|
+
|
|
80
|
+
## 8. Tool surface
|
|
81
|
+
|
|
82
|
+
45 tools. Setup: `ops_setup_status`, `google_setup_oauth_client`, `google_auth_login`, `google_discover_apply`, `ops_add_site`, `ops_update_site`, `ops_add_suppression`. Reads: `report_client_monthly` (writes files under `.gwo-data`, not to any site), `ops_*`, `web_*`, `google_*`, `gsc_*`, `ga4_*`, `gtm_containers`, `gtm_versions`, `gtm_snapshot`, `gtm_diff`, `gtm_workspaces`, `gtm_workspace_diff`, `site_*`, `pagespeed_check`. Writes: `gsc_submit_sitemap`, `content_set_meta_description`, `content_set_title`, `content_set_schema`, `ga4_create_key_event`, `gtm_stage`, `gtm_publish`, `repo_open_pr`.
|
|
83
|
+
|
|
84
|
+
## 9. Data model
|
|
85
|
+
|
|
86
|
+
Under `GWO_HOME` (the checkout itself, or `~/.gwo`): `sites/<host>.yaml` and gitignored `<host>.secrets.yaml`; `.gwo-data/<site_id>/{latest-scan.json, snapshots/, gtm/, gtm-stage/, audit.jsonl}`; `.gwo-data/_runs/` for worker reports; `secrets/` for the OAuth client and token. `gtm-stage/` keeps each staging result with its diff and review token for the record; the gate itself recomputes from live state and never trusts the file.
|
|
87
|
+
|
|
88
|
+
## 10. Security
|
|
89
|
+
|
|
90
|
+
See `docs/SECURITY.md`. Additions in v0.4: Tag Manager edit and publish scopes are still opt-in (`auth login --scope gtm_edit`, `--scope gtm_publish`); the Odoo API user needs view-editing rights only for structured data, and the adapter edits one marked element; the review token is derived from server state and cannot be forged from a stale diff.
|
|
91
|
+
|
|
92
|
+
## 11. Pilot status
|
|
93
|
+
|
|
94
|
+
Both pilot sites are scanned daily and read live. Neither has a Tag Manager container registered yet (`google.tag_manager.public_id` is null), so the staged-write path is verified against a fake API and awaits a container. aiguru is being fixed by hand in parallel and is out of scope for agent writes this cycle. Pending on the operator: `analytics.edit` consent for the key-event write; `local_path` plus `gwo.seo.json` for content writes; Resend and GitHub tokens; a container id per site.
|
|
95
|
+
|
|
96
|
+
## 12. v0.7 targets
|
|
97
|
+
|
|
98
|
+
The first live Tag Manager cycle on a company-owned container, then on a client's; feedback from the first installs on other machines (Codex and a second Claude Code) folded into the setup skill; outcome metrics in the client report (clicks before and after each change); a tightened default policy profile for advisory-tier clients.
|
package/docs/SECURITY.md
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
# Security model
|
|
2
|
+
|
|
3
|
+
## Secrets
|
|
4
|
+
|
|
5
|
+
Site registry files under `sites/` are non-secret configuration and are committed. Anything that grants access lives elsewhere:
|
|
6
|
+
|
|
7
|
+
- `sites/<host>.secrets.yaml`: per-site credentials, today the Odoo API user and key. Gitignored. Merged at load time under a key the public view strips. The Odoo user is dedicated to this system, has Website editing rights only, and its key can be revoked in Odoo without touching anything else.
|
|
8
|
+
- `.env`: `GOOGLE_ACCESS_TOKEN` (development only) and `GOOGLE_PAGESPEED_API_KEY`. Gitignored.
|
|
9
|
+
- `secrets/`: reserved for the phase 2 encrypted refresh-token store. Gitignored.
|
|
10
|
+
|
|
11
|
+
Tokens, keys and secrets must never appear in source control, prompts, tool outputs or audit payloads. `google_auth_status` reports which provider is active and never the token.
|
|
12
|
+
|
|
13
|
+
## Google access
|
|
14
|
+
|
|
15
|
+
One dedicated company Google account is added as a user to each client's Search Console, Analytics and Tag Manager. One identity, many properties. Clients grant access; they never share passwords.
|
|
16
|
+
|
|
17
|
+
All Google calls go through a `TokenProvider`. Two implementations exist:
|
|
18
|
+
|
|
19
|
+
**OAuth (durable, default when authorised).** `npm run cli -- auth login` runs an authorisation-code flow with PKCE against a "Desktop app" client (`secrets/oauth-client.json`), receives the redirect on a loopback port, and stores only the refresh token. Access tokens are refreshed on demand and kept in process memory. Storage at rest: DPAPI with CurrentUser scope on Windows (no key file exists), AES-256-GCM with a 0600 key file elsewhere. `auth status` reports account and scopes, never a token. `auth logout` deletes the record.
|
|
20
|
+
|
|
21
|
+
Default scopes are the least that reads need: `webmasters` (Search Console read plus sitemap submit, which policy classes as `auto`), `analytics.readonly`, `tagmanager.readonly`, `openid`, `userinfo.email`. Write scopes are added deliberately, never by default:
|
|
22
|
+
|
|
23
|
+
| Scope name | Google scope | Enables |
|
|
24
|
+
|---|---|---|
|
|
25
|
+
| `ga4_write` | `analytics.edit` | `ga4_create_key_event` |
|
|
26
|
+
| `gtm_edit` | `tagmanager.edit.containers` | `gtm_stage` (workspaces and entities; nothing live) |
|
|
27
|
+
| `gtm_publish` | `tagmanager.publish` | `gtm_publish` |
|
|
28
|
+
|
|
29
|
+
`auth login --scope gtm_edit --scope gtm_publish` adds both. A write tool called without its scope fails with the exact command to run.
|
|
30
|
+
|
|
31
|
+
**Env bridge (development only).** `GOOGLE_ACCESS_TOKEN` is a bearer token that expires in about an hour. Used only when no stored authorisation exists or `GWO_GOOGLE_AUTH=env` forces it.
|
|
32
|
+
|
|
33
|
+
PageSpeed Insights does not need an OAuth scope, but calls are attributed to a project for quota. The connector sends the OAuth token so the calls count against our project rather than Google's shared keyless pool. `GOOGLE_PAGESPEED_API_KEY`, if set, takes precedence.
|
|
34
|
+
|
|
35
|
+
Property discovery (`discover`) lists what the account can see and matches it to sites by host. It writes ids into `sites/*.yaml` only with `--apply`, and never writes credentials.
|
|
36
|
+
|
|
37
|
+
## Setup from the chat
|
|
38
|
+
|
|
39
|
+
The administration tools change this system's configuration only. The OAuth client is installed from a file path, so its contents never pass through a chat transcript. `ops_update_site` refuses any path that names a credential, `site_id`, or `policy`; site credentials are typed into `sites/<host>.secrets.yaml` by the operator, never into a tool call. `google_auth_login` opens the browser on the operator's machine; the consent happens there and only the encrypted refresh token is stored.
|
|
40
|
+
|
|
41
|
+
## Writes
|
|
42
|
+
|
|
43
|
+
Every mutating operation is classified before execution (`packages/policy`). `protected` actions are refused in code. `approval_required` actions need a token the operator produces in the same session. A host prompt cannot lower a class. Site overrides can only raise one.
|
|
44
|
+
|
|
45
|
+
Every write records the value before, the value after, and whether a post-write read verified the change.
|
|
46
|
+
|
|
47
|
+
Tag Manager writes always go to a workspace whose name carries the policy prefix (`gwo-`), never to the default workspace and never to one the client created. Publishing needs the approval token for `gtm.publish` and a review token derived from the exact set of staged changes and the live version id (ADR-005). The token is recomputed from GTM's own state inside the write, so a stale or invented token cannot publish. Deleting a workspace that holds changes, and deleting a container, are protected.
|
|
48
|
+
|
|
49
|
+
Structured data written to a site is validated for shape (schema.org context and type, no `</script`, size cap) and placed in one marked element the system owns; the adapters never edit any other script on the page. The Odoo API user needs view-editing rights for this and nothing more.
|
|
50
|
+
|
|
51
|
+
## PII
|
|
52
|
+
|
|
53
|
+
Visitor form values, names, email addresses, phone numbers, message content and health information are never stored, forwarded or sent to analytics by this system. This matters especially for clients in health and personal services.
|
|
54
|
+
|
|
55
|
+
## Logging
|
|
56
|
+
|
|
57
|
+
For stdio MCP, stdout is reserved for protocol traffic. All diagnostics go to stderr. Audit records redact anything that looks like a credential.
|
|
58
|
+
|
|
59
|
+
## Crawling
|
|
60
|
+
|
|
61
|
+
The crawler identifies itself with a user agent that names the operator, respects a politeness delay, caps concurrency and pages per scan, and only crawls sites in the registry.
|
|
62
|
+
|
|
63
|
+
## Delivery and pull requests
|
|
64
|
+
|
|
65
|
+
RESEND_API_KEY sends run reports by email; the reports contain site health summaries, never credentials. GITHUB_TOKEN is a fine-grained token limited to Contents and Pull requests on the site repositories the company develops; it is used only by repo_open_pr, which is approval-gated. Both are environment variables and never stored in site files.
|
|
66
|
+
|
|
67
|
+
## Scheduled runs
|
|
68
|
+
|
|
69
|
+
The worker is registered in Windows Task Scheduler as the operator Windows user, because the Google token is DPAPI-bound to that user. It runs only when the machine is on and catches up a missed run at next start. A hosted worker with its own consent is a v0.6 target.
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
# Site write adapters
|
|
2
|
+
|
|
3
|
+
How the agent changes a site itself, as opposed to changing Google's view of it. Every adapter exposes the same surface (`packages/core/src/adapters.ts`): `ping`, `listPages`, `readPageSeo`, `writePageSeo`, `readPageSchema`, `writePageSchema`. Write tools never call an adapter's write directly; they go through `performWrite()`, which reads before, writes, reads after and verifies.
|
|
4
|
+
|
|
5
|
+
| Write | Tool | Policy |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| meta description | `content_set_meta_description` | `auto` when empty, `content.meta_description.update` otherwise |
|
|
8
|
+
| title | `content_set_title` | `content.title.update`, always approval |
|
|
9
|
+
| structured data | `content_set_schema` | `content.schema.update`, always approval |
|
|
10
|
+
|
|
11
|
+
## Odoo (`write.adapter: odoo`)
|
|
12
|
+
|
|
13
|
+
Transport: JSON-RPC to `<url>/jsonrpc`. Authentication: a dedicated API user with an API key, used as the RPC password. No browser session.
|
|
14
|
+
|
|
15
|
+
Site file, non-secret:
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
write:
|
|
19
|
+
adapter: odoo
|
|
20
|
+
odoo:
|
|
21
|
+
url: https://erp.client.co.il
|
|
22
|
+
db: client_prod
|
|
23
|
+
website_id: 1 # optional, for multi-website databases
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Secrets file `sites/<host>.secrets.yaml` (gitignored):
|
|
27
|
+
|
|
28
|
+
```yaml
|
|
29
|
+
odoo:
|
|
30
|
+
login: gwo-api
|
|
31
|
+
api_key: "..."
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
What the API user needs in Odoo: Website > Restricted Editor is enough for `website.page` and `blog.post` SEO fields. Structured data on pages edits the page's view (`ir.ui.view.arch`), which needs Website > Editor and Designer. Do not give it Settings or Administration rights; the adapter never needs them.
|
|
35
|
+
|
|
36
|
+
Addressable content: `website.page` (matched by exact `url`), `blog.post` (matched by the trailing `-<id>` in the slug) and, when the eCommerce app is installed, `product.template` (`/shop/<slug>-<id>`; category listings are not addressed). Fields: `website_meta_title`, `website_meta_description`, `website_meta_keywords`, `website_meta_og_img`, plus `is_published` and `website_indexed` read-only. Product pages can be added the same way when a client needs them.
|
|
37
|
+
|
|
38
|
+
Structured data: the adapter manages exactly one element per page, `<script type="application/ld+json" data-gwo="schema">`. On a `website.page` it goes into the view arch before `<div id="wrap">`, outside the structure the website builder owns, so a client editing the page in the builder does not lose it. On a `blog.post` it is appended to `content`; on a `product.template` to `website_description`, which the shop page renders. Reading returns only the managed element; anything the theme emits on its own is left alone and reported separately by `site_page_schema`. Values are serialised with JSON escapes for `&`, `<` and `>` so the arch stays valid XML. Multi-website databases: the page's own view is edited, the generic view is never touched.
|
|
39
|
+
|
|
40
|
+
## Repository (`write.adapter: repo`)
|
|
41
|
+
|
|
42
|
+
For sites AI Guru develops. Convention: the repository keeps its SEO metadata in `gwo.seo.json` at the root, keyed by path, and the build reads it.
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"/he": { "name": "Home", "title": "...", "description": "...", "ogImage": "/og.png",
|
|
47
|
+
"schema": [{ "@context": "https://schema.org", "@type": "Organization", "name": "...", "url": "https://..." }] },
|
|
48
|
+
"/he/services": { "name": "Services", "title": "...", "description": null }
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Next.js example, in a route's `generateMetadata` and layout:
|
|
53
|
+
|
|
54
|
+
```ts
|
|
55
|
+
import seo from '../../gwo.seo.json';
|
|
56
|
+
export function generateMetadata({ params }) {
|
|
57
|
+
const e = seo[`/${params.locale}/${params.slug}`];
|
|
58
|
+
return { title: e?.title, description: e?.description, openGraph: { images: e?.ogImage ? [e.ogImage] : [] } };
|
|
59
|
+
}
|
|
60
|
+
// in the page component
|
|
61
|
+
{(e?.schema ?? []).map((block, i) => (
|
|
62
|
+
<script key={i} type="application/ld+json" dangerouslySetInnerHTML={{ __html: JSON.stringify(block).replace(/</g, '\\u003c') }} />
|
|
63
|
+
))}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`schema` is the array of JSON-LD blocks the page emits, in order. `content_set_schema` replaces the whole array; an empty list removes the key.
|
|
67
|
+
|
|
68
|
+
Site file:
|
|
69
|
+
|
|
70
|
+
```yaml
|
|
71
|
+
write:
|
|
72
|
+
adapter: repo
|
|
73
|
+
repo:
|
|
74
|
+
url: https://github.com/ai-gur/site
|
|
75
|
+
branch: main
|
|
76
|
+
local_path: E:/AI-Guru/sites/site # a local checkout on the operator machine
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
The adapter changes the working tree only. Committing and opening a pull request is the GitHub connector's job (phase 6). Until a site adopts the manifest, `ping` fails with a message saying so, and changes for that site are proposals for a developer.
|
|
80
|
+
|
|
81
|
+
## None (`write.adapter: none`)
|
|
82
|
+
|
|
83
|
+
Advisory only. Every content change is a proposal. The site is still scanned and reported on.
|
|
84
|
+
|
|
85
|
+
## Onboarding check
|
|
86
|
+
|
|
87
|
+
`npm run cli -- site ping <site>` must succeed before a site is declared `full` tier. It authenticates and reports what it can address, without changing anything.
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# ADR-001: Shared MCP core with host adapters
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-09-18
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Google Web Operations must be usable from both Claude Code and Codex. Both support MCP. Their packaging and instruction mechanisms differ.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
One MCP server and one operations core. Claude Code and Codex are hosts, not implementations.
|
|
13
|
+
|
|
14
|
+
Host-specific material is limited to packaging, configuration, skills, commands and instructions under `hosts/`, and even that is generated (ADR-003).
|
|
15
|
+
|
|
16
|
+
No business logic, connector logic, policy logic or site-specific logic may exist in a host directory.
|
|
17
|
+
|
|
18
|
+
## Repository rule
|
|
19
|
+
|
|
20
|
+
- One repository.
|
|
21
|
+
- One long-lived branch: `main`.
|
|
22
|
+
- Host-specific directories, never host-specific branches.
|
|
23
|
+
- Releases are semantic version tags.
|
|
24
|
+
|
|
25
|
+
## Consequences
|
|
26
|
+
|
|
27
|
+
No Claude/Codex behaviour drift. One test surface. One security model. New hosts are a generator target, not a rewrite. Host-native features stay thin.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ADR-002: Policy-gated, verified writes
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-09-18 (enforcement added in v0.2)
|
|
5
|
+
|
|
6
|
+
## Decision
|
|
7
|
+
|
|
8
|
+
Every mutating operation has one of three risk classes: `auto`, `approval_required`, `protected`. The class is resolved from `policies/default.yaml` plus site overrides before execution.
|
|
9
|
+
|
|
10
|
+
Classification alone is not enough. `packages/policy` exposes `enforce()`, which throws unless the action may proceed. Every write path calls it. `approval_required` passes only with the token `<site_id>:<action>` supplied in the current session. `protected` never passes.
|
|
11
|
+
|
|
12
|
+
Site overrides may raise a class and may not lower one. Host prompts cannot change a class at all.
|
|
13
|
+
|
|
14
|
+
Every write records the state before, the state after, and the result of a verification read. A write whose verification failed is a failed write, whatever the API returned.
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
|
|
18
|
+
An agent can be trusted with write tools because the tools refuse what policy forbids. Approval is explicit and auditable. Rollback is possible because the previous value is always recorded.
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# ADR-003: Playbooks are the single source; hosts are generated
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-09-18
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Operating knowledge (what a daily run checks, when to escalate, what may be fixed autonomously) is the most valuable and most drift-prone part of the system. Claude Code consumes it as skills with frontmatter; Codex consumes it as one instructions file. Two hand-maintained copies diverge within weeks.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
Playbooks live once, in `packages/playbooks/*.md`, with frontmatter (`name`, `description`). `tools/build-hosts.ts` generates `hosts/claude` (plugin manifest, `.mcp.json`, one skill per playbook, slash commands) and `hosts/codex` (plugin manifest, the same skills, `AGENTS.md`, a config template). Both hosts consume the SKILL.md format unchanged; the only per-host differences are the manifest, Claude's slash commands, and how the MCP server is registered.
|
|
13
|
+
|
|
14
|
+
`hosts/` is committed so the plugin installs from a checkout without a build step, but it is never edited by hand. CI regenerates it and fails on any difference.
|
|
15
|
+
|
|
16
|
+
## Consequences
|
|
17
|
+
|
|
18
|
+
One edit updates both hosts. Adding a host is a generator branch, not new content. Reviewers read playbooks, not generated files.
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# ADR-004: TypeScript executed natively, no build step
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-09-18
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
The code should be typed, because two different agents will edit it. But a build step means a `dist/` to keep in sync, and a runtime transpiler such as `tsx` means a dev dependency on the production path. Both hosts launch the MCP server directly, so the launch command must work on a clean checkout.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
Source is TypeScript restricted to erasable syntax (`erasableSyntaxOnly`), with explicit `.ts` import extensions and `verbatimModuleSyntax`. Node 22.18+ strips types natively, so `node apps/mcp-server/src/index.ts` runs as-is. `tsc --noEmit` checks types in `npm run check` and CI. There is no `dist/` and no transpiler at runtime.
|
|
13
|
+
|
|
14
|
+
## Consequences
|
|
15
|
+
|
|
16
|
+
Zero build. The generated `.mcp.json` and `config.toml` invoke `node` on the source file. Enums, namespaces and parameter properties are unavailable; interfaces, type aliases and unions cover everything this codebase needs. Contributors must import with `.ts` extensions and use `import type` for types.
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# ADR-005: Publishing a container needs a review token as well as approval
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-09-18 (v0.4)
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
A Tag Manager publish is the widest-reaching write the system makes: one version can silently stop every conversion on a site, or start sending visitor data somewhere new. The approval token from ADR-002 proves the operator agreed to *a* publish for *a* site. It does not prove they saw what would ship, and a workspace can change between the moment they looked and the moment the publish runs, whether by another staged write, by a client editing in the UI, or by someone else publishing a version underneath it.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
Every Tag Manager change is staged into a workspace whose name carries the policy prefix (`gwo-`). `gtm_workspace_diff` computes the workspace's changes against the live version and derives a **review token**: a short hash of the exact set of changed entities (kind, id, name, change status, entity fingerprint) and the live version id.
|
|
13
|
+
|
|
14
|
+
`gtm_publish` requires both the approval token for `gtm.publish` and a review token. Inside the write, before creating a version, the current diff is recomputed and its token compared; a mismatch fails the write and records why. The workspace is synced first, and the token is checked again after the sync, so a version published by someone else in between also invalidates it.
|
|
15
|
+
|
|
16
|
+
The two gates are enforced in `packages/core/src/gtm-ops.ts`, not by prompt. A host cannot skip either.
|
|
17
|
+
|
|
18
|
+
## Consequences
|
|
19
|
+
|
|
20
|
+
Nothing publishes that was not diffed, and nothing publishes a diff that moved after it was reviewed. The cost is one extra read per publish and a second value the operator passes back. Stage results carry the token so the common path (stage, look, publish) needs no separate diff call.
|
|
21
|
+
|
|
22
|
+
The same shape applies to any future write whose effect is a set of changes rather than one value: compute a token from the set, require it back.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# ADR-006: One npm package, one installer, state outside the package
|
|
2
|
+
|
|
3
|
+
**Status:** Accepted
|
|
4
|
+
**Date:** 2026-09-18 (v0.5)
|
|
5
|
+
|
|
6
|
+
## Context
|
|
7
|
+
|
|
8
|
+
Until v0.4 the system ran from a repository checkout: sites, data and secrets sat next to the code, Claude Code loaded the plugin from `hosts/claude`, and Codex needed a separate installer. Distributing that to a second machine or a colleague meant cloning the repository and following two sets of steps.
|
|
9
|
+
|
|
10
|
+
## Decision
|
|
11
|
+
|
|
12
|
+
The repository is an npm package (`google-web-operations`) with one executable, `gwo`. `npm i -g` from a release tarball or from GitHub installs the code; `gwo install` registers the MCP server, skills and slash commands with both hosts in one run, from the same generated `hosts/` tree, and seeds the operator state directory.
|
|
13
|
+
|
|
14
|
+
Operator state (sites, data, secrets, `.env`, an optional tightened policy) lives in `GWO_HOME`, default `~/.gwo`. A repository checkout is detected by its `.git` and keeps state next to the code, so development is unchanged. Upgrading the package never touches state.
|
|
15
|
+
|
|
16
|
+
The hosts launch the server as `node <package>/apps/mcp-server/src/index.ts` with absolute paths written at install time, because neither host expands variables in a user-scope MCP entry. `gwo install` is idempotent and rewrites those paths after an upgrade.
|
|
17
|
+
|
|
18
|
+
## Consequences
|
|
19
|
+
|
|
20
|
+
One command installs everything on a new machine; the same command repairs it after an upgrade. Development keeps ADR-004 (TypeScript run natively, no build step), but the package itself ships JavaScript: Node refuses to strip types for files under `node_modules`, so `tools/build-package.ts` compiles to `build/` with `.ts` imports rewritten to `.js`, copies the non-code parts (policies, hosts, playbooks, site templates, docs, scheduler) to the same relative paths, and `npm pack` runs there. The bin, the installer and the scheduler pick `.js` when it exists and `.ts` otherwise, so the same files serve both layouts. Releases are tags; CI packs the tarball and attaches it to the GitHub release. The Claude Code plugin directory stays generated for people who prefer `claude plugin` flows, but the installer does not depend on it.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "google-web-operations",
|
|
3
|
+
"version": "0.6.4",
|
|
4
|
+
"description": "Google Web Operations: agent-run SEO and GEO operations for client websites. One core, one MCP server, two hosts (Claude Code, Codex).",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "AI Guru"
|
|
7
|
+
},
|
|
8
|
+
"mcpServers": "./.mcp.json"
|
|
9
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
2
|
+
|
|
3
|
+
# Google Web Operations for Claude Code
|
|
4
|
+
|
|
5
|
+
Install with `gwo install claude` (user-scope MCP server, skills and commands), or load this directory as a plugin.
|
|
6
|
+
|
|
7
|
+
Commands: /gwo-daily, /gwo-weekly, /gwo-monthly, /gwo-triage, /gwo-onboard, /gwo-scan, /gwo-setup, /gwo-add-site, /gwo-measure.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Register the site in $ARGUMENTS.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Register the site in $ARGUMENTS. Load gwo-setup and gwo-onboarding; ask only for what cannot be inferred, then ops_add_site, baseline scan, and the access checklist for the client.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the daily health pass.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Run the daily health pass. Load skills gwo-operating-model and gwo-daily, then execute for every enabled site, or only the site named in $ARGUMENTS.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Audit measurement for the site in $ARGUMENTS.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Audit measurement for the site in $ARGUMENTS. Load gwo-operating-model and gwo-measurement, run the audit, and propose staged Tag Manager changes with their tokens.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the monthly deep scan and produce both reports.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Run the monthly deep scan and produce both reports. Load gwo-operating-model, gwo-monthly and gwo-client-report-template. Site: $ARGUMENTS or all.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run web_scan_site for the site in $ARGUMENTS and summarise the findings in the operator tone from gwo-operating-model.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Run web_scan_site for the site in $ARGUMENTS and summarise the findings in the operator tone from gwo-operating-model.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Set up or administer the system from the chat.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Set up or administer the system from the chat. Load gwo-setup, call ops_setup_status, and walk the operator through the next step: $ARGUMENTS.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Triage an incident for the site in $ARGUMENTS.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Triage an incident for the site in $ARGUMENTS. Load gwo-operating-model and gwo-triage. Find the cause before proposing any fix.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Run the weekly improvement pass.
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
6
|
+
|
|
7
|
+
Run the weekly improvement pass. Load gwo-operating-model and gwo-weekly, then execute for every enabled site, or only $ARGUMENTS.
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gwo-client-report-template
|
|
3
|
+
description: Structure and tone for the client-facing monthly report. Plain language, three questions, one small table.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
7
|
+
|
|
8
|
+
# Client report template
|
|
9
|
+
|
|
10
|
+
Write in the client's language (Hebrew for Israeli clients unless the site file says otherwise). One page. The document is produced by `report_client_monthly`; the four fields you pass map to the four sections below, and whatever you leave out is derived from the month's data.
|
|
11
|
+
|
|
12
|
+
## How the site is doing
|
|
13
|
+
|
|
14
|
+
Two or three sentences. More or fewer visitors from search than last month, and is that expected. One number that matters: clicks from Google this month vs last month.
|
|
15
|
+
|
|
16
|
+
| | This month | Last month | Change |
|
|
17
|
+
|---|---|---|---|
|
|
18
|
+
| Visits from Google | | | |
|
|
19
|
+
| Times shown in results | | | |
|
|
20
|
+
| Average position | | | |
|
|
21
|
+
|
|
22
|
+
## What we did this month
|
|
23
|
+
|
|
24
|
+
A short list, in business terms. Not "fixed canonical.cross-host on 12 pages" but "fixed a setup issue that was splitting the site's ranking signal between two addresses".
|
|
25
|
+
|
|
26
|
+
## What we need from you
|
|
27
|
+
|
|
28
|
+
Only things that require the client: approvals for proposed changes, content they need to write, decisions on direction. If nothing, say "nothing this month".
|
|
29
|
+
|
|
30
|
+
## Next month
|
|
31
|
+
|
|
32
|
+
One or two sentences on the focus.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
Never include: rule ids, tool names, severity labels, raw query lists, or anything the client cannot act on.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: gwo-content-brief
|
|
3
|
+
description: How to write titles, meta descriptions and structured-data proposals for client sites, in Hebrew and English, so that search engines and answer engines both pick them up. Load before any content write.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<!-- GENERATED by tools/build-hosts.ts from packages/playbooks. Do not edit here. -->
|
|
7
|
+
|
|
8
|
+
# Content brief
|
|
9
|
+
|
|
10
|
+
You write the text. The tools validate and apply it. Follow this brief exactly; the validator enforces the measurable parts and the operator reviews the rest.
|
|
11
|
+
|
|
12
|
+
## Meta description
|
|
13
|
+
|
|
14
|
+
**Length by script.** Hebrew renders denser than Latin text, so the same snippet width holds fewer characters.
|
|
15
|
+
|
|
16
|
+
| Script | Characters |
|
|
17
|
+
|---|---|
|
|
18
|
+
| Hebrew | 120 to 150 |
|
|
19
|
+
| Latin (English) | 70 to 160 |
|
|
20
|
+
|
|
21
|
+
**Structure.** Answer first, then the reason to click. The first clause states what the page delivers. The last clause invites the action or names who it is for. One idea per sentence. No line breaks, no HTML, no quotation marks around the whole text.
|
|
22
|
+
|
|
23
|
+
**Terms.** The page's primary term appears once, early. Never repeat a term more than twice; repetition lowers visibility with answer engines and reads as spam to people.
|
|
24
|
+
|
|
25
|
+
**Hebrew rules.**
|
|
26
|
+
- Ktiv maleh throughout: תוכנה, שירות, אינטרנט.
|
|
27
|
+
- Gender-neutral phrasing: "ניתן", "אפשר", "יש ל" instead of "אתה יכול", "המשתמש צריך".
|
|
28
|
+
- Business register, direct. No English-style softeners ("נשמח אם", "ייתכן שכדאי"). Say the thing.
|
|
29
|
+
- Established tech terms stay in English or in their spoken transliteration: API, SaaS, אימייל, סטארטאפ. Brand names stay as written.
|
|
30
|
+
- No calques: "בשורה התחתונה" not "בסוף היום"; "כדי" not "בכדי".
|
|
31
|
+
- Digits for numbers in body copy: "תוך 24 שעות".
|
|
32
|
+
|
|
33
|
+
**English rules.** Sentence case. Primary term in the first 60 characters. No exclamation marks. Numbers as digits.
|
|
34
|
+
|
|
35
|
+
**Do not** write a description that could belong to any page. If it does not name what this page uniquely offers, rewrite it.
|
|
36
|
+
|
|
37
|
+
## Title
|
|
38
|
+
|
|
39
|
+
50 to 60 characters, primary term near the beginning, brand at the end after one separator (`|`). Hebrew titles count Hebrew characters the same way. The validator in `content_set_title` rejects under 15 or over 65 characters, more than one separator, a repeated term, HTML and line breaks. Do not change a title on a page that already ranks without a Search Console reason; it is the most expensive edit in this playbook, and it always needs the token for `content.title.update`.
|
|
40
|
+
|
|
41
|
+
## Structured data
|
|
42
|
+
|
|
43
|
+
Answer engines cite sources; they do not rank pages. Schema tells them what a page is.
|
|
44
|
+
|
|
45
|
+
- Sitewide: `Organization` (or `Person` for a personal brand) with name, url, logo, sameAs.
|
|
46
|
+
- Per template: `WebPage`, `Article` (with author and dates), `Service`, `Product`, `LocalBusiness` where true.
|
|
47
|
+
- `FAQPage` wherever the page already answers concrete questions. Each answer 2 to 3 sentences, answer first, a number or a source where one exists. Never invent questions the page does not answer.
|
|
48
|
+
- Check `site_page_schema` first: the theme may already emit `WebSite` or `Organization`. Never add a type the page already has; two `Organization` blocks with different names is worse than none.
|
|
49
|
+
- Write the exact JSON-LD blocks and apply them with `content_set_schema`. The validator requires `"@context": "https://schema.org"` and an `@type` on every block, a non-empty `mainEntity` on `FAQPage`, no `</script` inside values, and at most 20 KB per page. Structured data is approval-required on every site: propose the blocks and the token for `content.schema.update` together.
|
|
50
|
+
- On Odoo the blocks go into the page's view; on repository sites into the manifest entry's `schema` array, which the layout renders. Either way the live page shows them only after the next render or deploy: verify with `web_inspect_url` and say "stored, awaiting deploy" until it does.
|
|
51
|
+
|
|
52
|
+
## Content that gets cited
|
|
53
|
+
|
|
54
|
+
When proposing page-level content changes (weekly and monthly runs), prefer, in this order: a specific statistic with its source; a short expert quote with attribution; a direct answer in the first paragraph; headed sections an engine can lift; a comparison table. Avoid keyword stuffing, which measurably hurts. Fluency plus statistics is the strongest combination.
|
|
55
|
+
|
|
56
|
+
## Before you submit any text
|
|
57
|
+
|
|
58
|
+
1. Run it through `content validate` (CLI) or rely on the tool's validation.
|
|
59
|
+
2. Read it aloud once in the page's language. If it sounds translated, it is.
|
|
60
|
+
3. Check the page still has exactly one H1 and that the description does not contradict it.
|