@aiguru/google-web-operations 0.6.5 → 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 +2 -2
- package/bin/gwo.js +6 -0
- package/docs/GETTING-STARTED.he.md +1 -1
- package/docs/INSTALL.he.md +17 -0
- package/hosts/claude/.claude-plugin/plugin.json +1 -1
- package/hosts/codex/.codex-plugin/plugin.json +1 -1
- package/package.json +1 -1
- package/packages/core/src/admin.js +9 -9
- package/packages/core/src/doctor.js +7 -1
- package/tools/install.js +77 -7
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@
|
|
35
|
-
GWO_REPORT_TO=
|
|
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.
|
package/bin/gwo.js
CHANGED
|
@@ -27,6 +27,12 @@ if (major < 22 || (major === 22 && minor < 18)) {
|
|
|
27
27
|
|
|
28
28
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
29
29
|
const cmd = process.argv[2];
|
|
30
|
+
|
|
31
|
+
// Installed inside a project rather than globally? Say so once per run (stderr, never on the MCP channel).
|
|
32
|
+
// Global installs live under <prefix>/lib/node_modules or <prefix>/npm/node_modules; a checkout has no node_modules above it.
|
|
33
|
+
if (/[\\/]node_modules[\\/]/.test(root) && !/[\\/](lib|npm)[\\/]node_modules[\\/]/i.test(root) && cmd !== 'mcp') {
|
|
34
|
+
console.error('note: gwo is installed locally in a project. For a stable setup: npm i -g @aiguru/google-web-operations\n');
|
|
35
|
+
}
|
|
30
36
|
// A checkout runs the TypeScript sources; the packaged build ships JavaScript.
|
|
31
37
|
const ext = existsSync(path.join(root, 'apps', 'cli', 'src', 'index.js')) ? 'index.js' : 'index.ts';
|
|
32
38
|
// Dynamic imports need file URLs on Windows; a bare drive path is read as a URL scheme.
|
package/docs/INSTALL.he.md
CHANGED
|
@@ -7,15 +7,29 @@
|
|
|
7
7
|
|
|
8
8
|
## 1. התקנה
|
|
9
9
|
|
|
10
|
+
מכל תיקייה שהיא (לא צריך תיקיית פרויקט), עם `-g`:
|
|
11
|
+
|
|
10
12
|
```
|
|
11
13
|
npm i -g @aiguru/google-web-operations
|
|
12
14
|
gwo install
|
|
13
15
|
```
|
|
14
16
|
|
|
17
|
+
ה‑`-g` חשוב: בלעדיו npm יוצר `package.json`, `package-lock.json` ו‑`node_modules` בתיקייה הנוכחית, ו‑`gwo` לא נכנס ל‑PATH. הכלי מזהה את זה: `gwo install` מסרב לרשום נתיב מתוך `node_modules` של פרויקט ואומר מה לעשות. אם זה קרה: למחוק את שלושת אלה ולהריץ שוב עם `-g`.
|
|
18
|
+
|
|
15
19
|
`gwo install` רושם את שרת ה‑MCP, את הסקילים ואת הפקודות בקלוד ובקודקס, ויוצר את `~/.gwo`. רק להוסט אחד: `gwo install claude` או `gwo install codex`. לראות בלי לשנות: `gwo install --print`.
|
|
16
20
|
|
|
17
21
|
חלופה בלי npm: להוריד את ה‑tgz מעמוד ה‑Releases בריפו ולהריץ `npm i -g ./aiguru-google-web-operations-<version>.tgz` (ה‑`./` חובה).
|
|
18
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
|
+
|
|
19
33
|
## 2. הפעלה מחדש ובדיקה
|
|
20
34
|
|
|
21
35
|
הפעלה מחדש של Claude Code ו‑Codex, ואז:
|
|
@@ -30,6 +44,9 @@ gwo doctor
|
|
|
30
44
|
|
|
31
45
|
- Claude Code: `/gwo-setup`
|
|
32
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 עובד אבל אין סקילים, והסוכן יעבוד לפי תיאורי הכלים בלבד.
|
|
33
50
|
|
|
34
51
|
הסוכן מוביל: חיבור לגוגל (לתת לו את הנתיב לקובץ ה‑JSON של לקוח ה‑OAuth, לא את התוכן), הסכמה בדפדפן עם חשבון התפעול, רישום אתר ראשון, ורשימת ההרשאות שהלקוח צריך לתת.
|
|
35
52
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "google-web-operations",
|
|
3
|
-
"version": "0.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.
|
|
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.
|
|
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,
|
|
@@ -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 (
|
|
27
|
+
missing.push('Search Console property (the client grants access, then discover the properties)');
|
|
28
28
|
if (!ga4)
|
|
29
|
-
missing.push('GA4 property id (
|
|
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('
|
|
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('
|
|
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:
|
|
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:
|
|
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('
|
|
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('
|
|
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 (!/
|
|
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
|
-
|
|
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}`);
|
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
|
|
@@ -17,15 +17,20 @@
|
|
|
17
17
|
* Only gwo-* entries are ever written or removed; nothing else in those
|
|
18
18
|
* directories or files is touched. Idempotent: run it again after an upgrade.
|
|
19
19
|
*/
|
|
20
|
+
import { execSync } from 'node:child_process';
|
|
20
21
|
import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, rmSync, writeFileSync, copyFileSync } from 'node:fs';
|
|
21
22
|
import { homedir } from 'node:os';
|
|
22
23
|
import path from 'node:path';
|
|
23
24
|
import { HOME_DIR, HOSTS_DIR, IS_CHECKOUT, ROOT } from "../packages/core/src/paths.js";
|
|
24
25
|
const argv = process.argv.slice(2);
|
|
25
26
|
const uninstall = argv.includes('uninstall');
|
|
26
|
-
const targetArg = argv.find((a) => ['claude', 'codex', 'all'].includes(a)) ?? 'all';
|
|
27
|
+
const targetArg = argv.find((a) => ['claude', 'codex', 'desktop', 'all'].includes(a)) ?? 'all';
|
|
27
28
|
const print = argv.includes('--print') || argv.includes('--dry-run');
|
|
28
|
-
|
|
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];
|
|
29
34
|
const SERVER = 'google-web-operations';
|
|
30
35
|
const toPosix = (p) => p.replace(/\\/g, '/');
|
|
31
36
|
const node = process.execPath;
|
|
@@ -86,6 +91,45 @@ function claude() {
|
|
|
86
91
|
copyFileSync(path.join(cmdSrc, n), path.join(cmdDst, n));
|
|
87
92
|
}
|
|
88
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
|
+
}
|
|
89
133
|
// ---------------------------------------------------------------- Codex
|
|
90
134
|
function codex() {
|
|
91
135
|
const cfg = path.join(CODEX_HOME, 'config.toml');
|
|
@@ -119,12 +163,37 @@ function codex() {
|
|
|
119
163
|
log(`Google Web Operations ${uninstall ? 'uninstall' : 'install'}${print ? ' (print only)' : ''}`);
|
|
120
164
|
log(`package: ${ROOT}`);
|
|
121
165
|
log(`node: ${node}`);
|
|
166
|
+
// A package under some project's node_modules is a fragile place to point the
|
|
167
|
+
// hosts at: it disappears with the folder or the next npm install there.
|
|
168
|
+
if (!uninstall && !IS_CHECKOUT && /[\\/]node_modules[\\/]/.test(ROOT) && !argv.includes('--allow-local')) {
|
|
169
|
+
let globalRoot = '';
|
|
170
|
+
try {
|
|
171
|
+
globalRoot = execSync('npm root -g', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
172
|
+
}
|
|
173
|
+
catch { /* npm not on PATH */ }
|
|
174
|
+
const isGlobal = globalRoot && path.resolve(ROOT).toLowerCase().startsWith(path.resolve(globalRoot).toLowerCase());
|
|
175
|
+
if (!isGlobal) {
|
|
176
|
+
console.error([
|
|
177
|
+
'',
|
|
178
|
+
'This copy of the package is installed locally (inside a project\'s node_modules), so the hosts would be',
|
|
179
|
+
'pointed at a path that vanishes with the folder or the next npm install there. Install it globally instead:',
|
|
180
|
+
'',
|
|
181
|
+
' npm i -g @aiguru/google-web-operations',
|
|
182
|
+
' gwo install',
|
|
183
|
+
'',
|
|
184
|
+
'To register this local copy anyway (a deliberate, pinned setup): gwo install --allow-local',
|
|
185
|
+
].join('\n'));
|
|
186
|
+
process.exit(2);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
122
189
|
if (!uninstall)
|
|
123
190
|
ensureHome();
|
|
124
191
|
if (targets.includes('claude'))
|
|
125
192
|
claude();
|
|
126
193
|
if (targets.includes('codex'))
|
|
127
194
|
codex();
|
|
195
|
+
if (targets.includes('desktop'))
|
|
196
|
+
await desktop();
|
|
128
197
|
if (print)
|
|
129
198
|
log('\nNothing changed. Run without --print to apply.');
|
|
130
199
|
else if (uninstall)
|
|
@@ -135,8 +204,9 @@ else
|
|
|
135
204
|
'Done.',
|
|
136
205
|
'',
|
|
137
206
|
'Next',
|
|
138
|
-
' 1. Restart Claude Code
|
|
139
|
-
' 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.'] : []),
|
|
140
210
|
' The assistant connects Google, registers the first site and tells you what the client must grant.',
|
|
141
211
|
' 3. Any time: gwo doctor (what is configured, what is missing, the next step)',
|
|
142
212
|
'',
|