@aiguru/google-web-operations 0.6.6 → 0.6.7

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 CHANGED
@@ -31,8 +31,8 @@ GWO_DOCX_TEMPLATE=
31
31
 
32
32
  # --- Delivery (worker --deliver) -------------------------------------------
33
33
  RESEND_API_KEY=
34
- GWO_REPORT_FROM=ops@aiguru.co.il
35
- GWO_REPORT_TO=ai@aiguru.co.il
34
+ GWO_REPORT_FROM= # e.g. ops@your-domain.co.il (a domain verified in Resend)
35
+ GWO_REPORT_TO= # e.g. you@your-domain.co.il
36
36
 
37
37
  # --- GitHub (pull requests for repository-backed sites) --------------------
38
38
  # Fine-grained token: Contents write + Pull requests write on the site repos.
@@ -20,6 +20,16 @@ gwo install
20
20
 
21
21
  חלופה בלי npm: להוריד את ה‑tgz מעמוד ה‑Releases בריפו ולהריץ `npm i -g ./aiguru-google-web-operations-<version>.tgz` (ה‑`./` חובה).
22
22
 
23
+ **איפה להריץ.** בטרמינל של המחשב שבו רצים Claude Code וקודקס (ב‑Windows: PowerShell או CMD). לא מתוך סוכן שרץ בסביבה מבודדת (Cowork, קונטיינר, VM): שם ההתקנה עובדת אבל נמחקת בסוף הסשן ולא נראית להוסטים שלך.
24
+
25
+ **Linux/macOS, שגיאת EACCES** על `/usr/lib/node_modules`: להתקין ל‑prefix של המשתמש במקום `sudo`:
26
+ ```
27
+ npm config set prefix ~/.npm-global
28
+ export PATH=~/.npm-global/bin:$PATH # וגם ב-~/.bashrc או ~/.zshrc
29
+ ```
30
+
31
+ **המצב הוא לכל משתמש ולכל מחשב.** `~/.gwo` (אתרים, הסכמה לגוגל, `.env`) לא משותף בין מחשבים או בין Windows ל‑VM. במחשב נוסף שאמור לראות את אותם אתרים: להעתיק `sites/*.yaml`, ולעשות את ההסכמה לגוגל מחדש.
32
+
23
33
  ## 2. הפעלה מחדש ובדיקה
24
34
 
25
35
  הפעלה מחדש של Claude Code ו‑Codex, ואז:
@@ -34,6 +44,9 @@ gwo doctor
34
44
 
35
45
  - Claude Code: `/gwo-setup`
36
46
  - Codex: "הגדר את Google Web Operations"
47
+ - Claude for Windows / Mac (הדסקטופ, Cowork): "הגדר את Google Web Operations"
48
+
49
+ **Claude Desktop זה הוסט אחר.** הוא לא קורא את הסקילים מהדיסק. `gwo install desktop` (או `gwo install` כשהאפליקציה מותקנת) רושם את ה‑MCP ב‑`claude_desktop_config.json`, ואורז את הסקילים כקובצי zip ב‑`~/.gwo/skills-for-claude-ai/`. אותם מעלים פעם אחת ב‑claude.ai: Settings > Capabilities > Skills > Upload, ומאז הם מסתנכרנים לכל המחשבים של אותו חשבון. בלי ההעלאה, ה‑MCP עובד אבל אין סקילים, והסוכן יעבוד לפי תיאורי הכלים בלבד.
37
50
 
38
51
  הסוכן מוביל: חיבור לגוגל (לתת לו את הנתיב לקובץ ה‑JSON של לקוח ה‑OAuth, לא את התוכן), הסכמה בדפדפן עם חשבון התפעול, רישום אתר ראשון, ורשימת ההרשאות שהלקוח צריך לתת.
39
52
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-web-operations",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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
5
  "author": {
6
6
  "name": "AI Guru"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-web-operations",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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
5
  "author": {
6
6
  "name": "AI Guru"
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aiguru/google-web-operations",
3
- "version": "0.6.6",
3
+ "version": "0.6.7",
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
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -15,9 +15,6 @@
15
15
  "bin": {
16
16
  "gwo": "bin/gwo.js"
17
17
  },
18
- "scripts": {
19
- "postinstall": "node bin/postinstall.js"
20
- },
21
18
  "dependencies": {
22
19
  "@modelcontextprotocol/server": "^2.0.0",
23
20
  "jszip": "^3.10.2",
@@ -24,9 +24,9 @@ function siteSetup(s) {
24
24
  const missing = [];
25
25
  const gsc = s.google.search_console?.property ?? null, ga4 = s.google.ga4?.property_id ?? null, gtm = s.google.tag_manager?.public_id ?? null;
26
26
  if (!gsc)
27
- missing.push('Search Console property (grant access, then google_discover_apply)');
27
+ missing.push('Search Console property (the client grants access, then discover the properties)');
28
28
  if (!ga4)
29
- missing.push('GA4 property id (grant access, then google_discover_apply)');
29
+ missing.push('GA4 property id (the client grants access, then discover the properties)');
30
30
  if (!gtm)
31
31
  missing.push('Tag Manager container (none registered; fine if the site has no container)');
32
32
  const adapter = s.write?.adapter ?? 'none';
@@ -62,22 +62,22 @@ export function setupStatus() {
62
62
  const sites = listSites({ includeDisabled: true }).map(siteSetup);
63
63
  const next = [];
64
64
  if (!o.clientFile)
65
- next.push('Create a "Desktop app" OAuth client in Google Cloud (project "Google Web Operations"), download its JSON, then google_setup_oauth_client with the downloaded file path.');
65
+ next.push('Connect Google: create a "Desktop app" OAuth client in Google Cloud (one project for all sites), download its JSON, then in the chat "/gwo-setup" and give it the file path (CLI: save it as secrets/oauth-client.json under the state directory).');
66
66
  else if (!o.authorised)
67
- next.push('google_auth_login: the browser opens for consent with the operations account.');
67
+ next.push('Authorise the operations Google account: in the chat "/gwo-setup" (CLI: gwo auth login). A browser opens for consent.');
68
68
  else {
69
69
  if (!writeScopes.ga4_write)
70
- next.push('Optional: google_auth_login with scopes ["ga4_write"] before creating key events.');
70
+ next.push('Optional, before creating key events: add the ga4_write scope (chat: ask for it in /gwo-setup; CLI: gwo auth login --scope ga4_write).');
71
71
  if (!writeScopes.gtm_edit || !writeScopes.gtm_publish)
72
- next.push('Optional: google_auth_login with scopes ["gtm_edit", "gtm_publish"] before staging Tag Manager changes.');
72
+ next.push('Optional, before Tag Manager changes: add the gtm_edit and gtm_publish scopes (CLI: gwo auth login --scope gtm_edit --scope gtm_publish).');
73
73
  }
74
74
  if (!sites.length)
75
- next.push('ops_add_site for the first site.');
75
+ next.push('Register the first site: in the chat "/gwo-add-site https://example.co.il" (CLI: gwo add-site <url> --name "...").');
76
76
  if (o.authorised && sites.some((s) => !s.searchConsole || !s.ga4PropertyId))
77
- next.push('google_discover_apply once the client has added the operations account to Search Console and GA4.');
77
+ next.push('Once the client has added the operations account to Search Console and GA4: discover the properties (chat: "discover the Google properties"; CLI: gwo discover --apply).');
78
78
  for (const s of sites)
79
79
  for (const m of s.missing)
80
- if (!/discover_apply|none registered/.test(m))
80
+ if (!/discover the properties|none registered/.test(m))
81
81
  next.push(`${s.id}: ${m}`);
82
82
  if (!env.RESEND_API_KEY)
83
83
  next.push('Optional: RESEND_API_KEY, GWO_REPORT_FROM, GWO_REPORT_TO in .env for emailed worker reports.');
@@ -4,12 +4,18 @@
4
4
  * by the chat when the operator asks "is everything set up?".
5
5
  */
6
6
  import { setupStatus } from "./admin.js";
7
+ import { hostname, userInfo } from 'node:os';
7
8
  import { HOME_DIR, IS_CHECKOUT, ROOT } from "./paths.js";
8
9
  const OK = ' [ok] ', MISSING = ' [--] ', OPT = ' [..] ';
9
10
  export function renderDoctor(s = setupStatus()) {
10
11
  const out = [];
11
12
  const [major = 0, minor = 0] = process.versions.node.split('.').map(Number);
12
- out.push('Google Web Operations: doctor', '');
13
+ let who = '';
14
+ try {
15
+ who = `${userInfo().username}@${hostname()}`;
16
+ }
17
+ catch { /* cosmetic */ }
18
+ out.push(`Google Web Operations: doctor${who ? ` (${who})` : ''}`, '');
13
19
  out.push(`${major > 22 || (major === 22 && minor >= 18) ? OK : MISSING}Node ${process.versions.node} (needs 22.18+)`);
14
20
  out.push(`${OK}package ${ROOT}`);
15
21
  out.push(`${OK}state ${IS_CHECKOUT ? 'next to the code (repository checkout)' : HOME_DIR}`);
@@ -37,7 +37,6 @@ const out = {
37
37
  name: pkg['name'], version: pkg['version'], description: pkg['description'], license: pkg['license'] ?? 'UNLICENSED', private: false,
38
38
  type: 'module', engines: pkg['engines'], repository: pkg['repository'],
39
39
  bin: { gwo: 'bin/gwo.js' },
40
- scripts: { postinstall: 'node bin/postinstall.js' },
41
40
  dependencies: pkg['dependencies'],
42
41
  };
43
42
  writeFileSync(path.join(OUT, 'package.json'), JSON.stringify(out, null, 2) + '\n');
package/tools/install.js CHANGED
@@ -2,12 +2,12 @@
2
2
  /**
3
3
  * Install or remove the hosts' side of Google Web Operations on this machine.
4
4
  *
5
- * gwo install [claude|codex|all] [--print]
6
- * gwo uninstall [claude|codex|all]
5
+ * gwo install [claude|codex|desktop|all] [--print]
6
+ * gwo uninstall [claude|codex|desktop|all]
7
7
  *
8
8
  * Claude Code: the MCP server goes into ~/.claude.json (user scope), skills
9
9
  * into ~/.claude/skills/gwo-*, slash commands into
10
- * ~/.claude/commands/gwo-*.md.
10
+ * ~/.claude/commands/gwo-*.md (both show under /gwo-...).
11
11
  * Codex: the MCP server goes into ~/.codex/config.toml, skills into
12
12
  * ~/.codex/skills/gwo-*.
13
13
  * Both: the operator state directory (GWO_HOME, default ~/.gwo) is
@@ -24,9 +24,13 @@ import path from 'node:path';
24
24
  import { HOME_DIR, HOSTS_DIR, IS_CHECKOUT, ROOT } from "../packages/core/src/paths.js";
25
25
  const argv = process.argv.slice(2);
26
26
  const uninstall = argv.includes('uninstall');
27
- const targetArg = argv.find((a) => ['claude', 'codex', 'all'].includes(a)) ?? 'all';
27
+ const targetArg = argv.find((a) => ['claude', 'codex', 'desktop', 'all'].includes(a)) ?? 'all';
28
28
  const print = argv.includes('--print') || argv.includes('--dry-run');
29
- const targets = targetArg === 'all' ? ['claude', 'codex'] : [targetArg];
29
+ /** Claude Desktop (Claude for Windows/Mac, Cowork) keeps its MCP servers in its own config file. */
30
+ const DESKTOP_DIR = process.platform === 'win32' ? path.join(process.env['APPDATA'] ?? path.join(homedir(), 'AppData', 'Roaming'), 'Claude')
31
+ : process.platform === 'darwin' ? path.join(homedir(), 'Library', 'Application Support', 'Claude') : path.join(homedir(), '.config', 'Claude');
32
+ // "all" covers the desktop app only where it is installed, so a plain `gwo install` never invents its config.
33
+ const targets = targetArg === 'all' ? ['claude', 'codex', ...(existsSync(DESKTOP_DIR) ? ['desktop'] : [])] : [targetArg];
30
34
  const SERVER = 'google-web-operations';
31
35
  const toPosix = (p) => p.replace(/\\/g, '/');
32
36
  const node = process.execPath;
@@ -87,6 +91,45 @@ function claude() {
87
91
  copyFileSync(path.join(cmdSrc, n), path.join(cmdDst, n));
88
92
  }
89
93
  }
94
+ // ---------------------------------------------------------------- Claude Desktop (Claude for Windows / Mac, Cowork)
95
+ /**
96
+ * The desktop app reads MCP servers from claude_desktop_config.json. Skills
97
+ * cannot be installed from a local file: they are uploaded once in claude.ai
98
+ * (Settings > Capabilities > Skills) and sync down. So the skills are packed
99
+ * as one zip per skill under <state>/skills-for-claude-ai/ for that upload.
100
+ */
101
+ async function desktop() {
102
+ const cfg = path.join(DESKTOP_DIR, 'claude_desktop_config.json');
103
+ const skillsSrc = path.join(HOSTS_DIR, 'claude', 'skills');
104
+ const skills = gwoDirs(skillsSrc);
105
+ const zipDir = path.join(HOME_DIR, 'skills-for-claude-ai');
106
+ log(`\nClaude Desktop (Claude for Windows / Mac, Cowork)`);
107
+ log(` MCP server "${SERVER}" ${uninstall ? 'removed from' : 'in'} ${cfg}`);
108
+ log(` ${skills.length} skills packed as zips -> ${zipDir} (upload once in claude.ai: Settings > Capabilities > Skills)`);
109
+ if (print)
110
+ return;
111
+ mkdirSync(DESKTOP_DIR, { recursive: true });
112
+ let json = {};
113
+ if (existsSync(cfg))
114
+ json = JSON.parse(readFileSync(cfg, 'utf8'));
115
+ const servers = (json['mcpServers'] ?? {});
116
+ if (uninstall)
117
+ delete servers[SERVER];
118
+ else
119
+ servers[SERVER] = { command: node, args: [server] };
120
+ json['mcpServers'] = servers;
121
+ writeFileSync(cfg, JSON.stringify(json, null, 2));
122
+ rmSync(zipDir, { recursive: true, force: true });
123
+ if (!uninstall) {
124
+ const { default: JSZip } = await import('jszip');
125
+ mkdirSync(zipDir, { recursive: true });
126
+ for (const n of skills) {
127
+ const zip = new JSZip();
128
+ zip.file(`${n}/SKILL.md`, readFileSync(path.join(skillsSrc, n, 'SKILL.md')));
129
+ writeFileSync(path.join(zipDir, `${n}.zip`), await zip.generateAsync({ type: 'nodebuffer', compression: 'DEFLATE' }));
130
+ }
131
+ }
132
+ }
90
133
  // ---------------------------------------------------------------- Codex
91
134
  function codex() {
92
135
  const cfg = path.join(CODEX_HOME, 'config.toml');
@@ -149,6 +192,8 @@ if (targets.includes('claude'))
149
192
  claude();
150
193
  if (targets.includes('codex'))
151
194
  codex();
195
+ if (targets.includes('desktop'))
196
+ await desktop();
152
197
  if (print)
153
198
  log('\nNothing changed. Run without --print to apply.');
154
199
  else if (uninstall)
@@ -159,8 +204,9 @@ else
159
204
  'Done.',
160
205
  '',
161
206
  'Next',
162
- ' 1. Restart Claude Code and Codex so they pick up the server and skills.',
163
- ' 2. Claude Code: type /gwo-setup. Codex: say "set up Google Web Operations".',
207
+ ' 1. Restart Claude Code, Codex or the Claude desktop app so they pick up the server and skills.',
208
+ ' 2. Claude Code: type /gwo-setup. Codex or Claude desktop: say "set up Google Web Operations".',
209
+ ...(targets.includes('desktop') ? [' Claude desktop: first upload the skill zips once at claude.ai > Settings > Capabilities > Skills.'] : []),
164
210
  ' The assistant connects Google, registers the first site and tells you what the client must grant.',
165
211
  ' 3. Any time: gwo doctor (what is configured, what is missing, the next step)',
166
212
  '',
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Runs after `npm install` of the package. Says one thing, only when it
4
- * matters: this is a command-line tool and belongs in a global install.
5
- * Never fails the install.
6
- */
7
- try {
8
- if (process.env.npm_config_global !== 'true') {
9
- console.log([
10
- '',
11
- ' Google Web Operations was installed into this project, not globally.',
12
- ' It is a command-line tool (gwo) and the hosts need a stable path to it:',
13
- '',
14
- ' npm i -g @aiguru/google-web-operations',
15
- ' gwo install',
16
- '',
17
- ' (then remove the local copy: node_modules, package.json, package-lock.json if this folder was empty)',
18
- '',
19
- ].join('\n'));
20
- }
21
- } catch { /* never break an install */ }