@aiguru/google-web-operations 0.6.4 → 0.6.6

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
@@ -38,5 +38,7 @@ GWO_REPORT_TO=ai@aiguru.co.il
38
38
  # Fine-grained token: Contents write + Pull requests write on the site repos.
39
39
  GITHUB_TOKEN=
40
40
 
41
- # --- NPM JS ACCESS TOKEN - gwo-release --------------------
42
- NPM_TOKEN=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
41
+ # --- Publishing ---------------------------------------------------------------
42
+ # The npm publish token is NOT an environment variable of this system and must
43
+ # never be written here or anywhere in the repository: this file is shipped
44
+ # inside the package. It lives only in the GitHub repository secret NPM_TOKEN.
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.
@@ -0,0 +1,21 @@
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 */ }
@@ -6,7 +6,7 @@
6
6
 
7
7
  דרישות: Node 22.18 ומעלה.
8
8
 
9
- 1. בטרמינל:
9
+ 1. בטרמינל, מכל תיקייה, עם `-g` (בלעדיו npm מתקין לתיקייה הנוכחית ו‑`gwo` לא יהיה זמין):
10
10
  ```
11
11
  npm i -g @aiguru/google-web-operations
12
12
  gwo install
@@ -7,11 +7,15 @@
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` (ה‑`./` חובה).
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-web-operations",
3
- "version": "0.6.4",
3
+ "version": "0.6.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
5
  "author": {
6
6
  "name": "AI Guru"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "google-web-operations",
3
- "version": "0.6.4",
3
+ "version": "0.6.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
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.4",
3
+ "version": "0.6.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
5
  "license": "UNLICENSED",
6
6
  "private": false,
@@ -15,6 +15,9 @@
15
15
  "bin": {
16
16
  "gwo": "bin/gwo.js"
17
17
  },
18
+ "scripts": {
19
+ "postinstall": "node bin/postinstall.js"
20
+ },
18
21
  "dependencies": {
19
22
  "@modelcontextprotocol/server": "^2.0.0",
20
23
  "jszip": "^3.10.2",
@@ -37,6 +37,7 @@ 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' },
40
41
  dependencies: pkg['dependencies'],
41
42
  };
42
43
  writeFileSync(path.join(OUT, 'package.json'), JSON.stringify(out, null, 2) + '\n');
@@ -0,0 +1,59 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Refuse to build, test or release when a credential sits in a file that the
4
+ * package or the repository would carry.
5
+ *
6
+ * Scans every tracked file and every file the build copies (so .env.example,
7
+ * docs, policies, playbooks and hosts are all covered) for well-known token
8
+ * shapes and for non-empty secret-looking assignments in example env files.
9
+ * Runs inside `npm run check`, so CI and the release workflow fail before
10
+ * anything is packed or published.
11
+ *
12
+ * node tools/check-secrets.ts
13
+ */
14
+ import { execFileSync } from 'node:child_process';
15
+ import { readFileSync, statSync } from 'node:fs';
16
+ import path from 'node:path';
17
+ import { ROOT } from "../packages/core/src/paths.js";
18
+ const PATTERNS = [
19
+ ['npm token', /\bnpm_[A-Za-z0-9]{30,}\b/],
20
+ ['GitHub token', /\b(ghp|gho|ghu|ghs|ghr)_[A-Za-z0-9]{30,}\b|\bgithub_pat_[A-Za-z0-9_]{40,}\b/],
21
+ ['Google API key', /\bAIza[0-9A-Za-z_-]{30,}\b/],
22
+ ['Google OAuth client secret', /\bGOCSPX-[0-9A-Za-z_-]{20,}\b/],
23
+ ['Resend key', /\bre_[A-Za-z0-9]{20,}\b/],
24
+ ['OpenAI-style key', /\bsk-[A-Za-z0-9_-]{30,}\b/],
25
+ ['private key block', /-----BEGIN [A-Z ]*PRIVATE KEY-----/],
26
+ ];
27
+ /** In *.example env files, any secret-looking variable must be empty. */
28
+ const ENV_ASSIGN = /^\s*([A-Z0-9_]*(TOKEN|KEY|SECRET|PASSWORD)[A-Z0-9_]*)\s*=\s*(\S.*)$/;
29
+ const SKIP = /^(package-lock\.json|.*\.(png|jpg|jpeg|gif|ico|wmf|woff2?|ttf|docx|pdf|tgz))$/i;
30
+ const files = execFileSync('git', ['ls-files', '-z'], { cwd: ROOT, encoding: 'utf8' }).split('\0').filter(Boolean)
31
+ .filter((f) => !SKIP.test(f)).filter((f) => { try {
32
+ return statSync(path.join(ROOT, f)).isFile();
33
+ }
34
+ catch {
35
+ return false;
36
+ } });
37
+ const problems = [];
38
+ for (const f of files) {
39
+ const text = readFileSync(path.join(ROOT, f), 'utf8');
40
+ for (const [name, re] of PATTERNS)
41
+ if (re.test(text))
42
+ problems.push(`${f}: looks like a ${name}`);
43
+ if (/\.env(\.example)?$|\.example\.yaml$/.test(f)) {
44
+ text.split('\n').forEach((line, i) => {
45
+ const m = line.match(ENV_ASSIGN);
46
+ if (m && !/^(GWO_[A-Z_]+|.*=\s*(<|\$\{|""|'')).*/.test(line) && !/^\s*#/.test(line)) {
47
+ // Allow documented placeholders such as ops@aiguru.co.il for non-secret names, refuse real-looking values for secret names.
48
+ if (!/^(GWO_TOKEN_STORE)$/.test(m[1]))
49
+ problems.push(`${f}:${i + 1}: ${m[1]} has a value; example files must leave secrets empty`);
50
+ }
51
+ });
52
+ }
53
+ }
54
+ if (problems.length) {
55
+ console.error('check-secrets: refusing to continue.\n' + problems.map((p) => ' - ' + p).join('\n') +
56
+ '\nRemove the value, rotate the credential, and keep secrets in .env (gitignored), sites/*.secrets.yaml (gitignored) or GitHub repository secrets.');
57
+ process.exit(1);
58
+ }
59
+ console.log(`check-secrets: ${files.length} tracked files clean`);
package/tools/install.js CHANGED
@@ -17,6 +17,7 @@
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';
@@ -119,6 +120,29 @@ function codex() {
119
120
  log(`Google Web Operations ${uninstall ? 'uninstall' : 'install'}${print ? ' (print only)' : ''}`);
120
121
  log(`package: ${ROOT}`);
121
122
  log(`node: ${node}`);
123
+ // A package under some project's node_modules is a fragile place to point the
124
+ // hosts at: it disappears with the folder or the next npm install there.
125
+ if (!uninstall && !IS_CHECKOUT && /[\\/]node_modules[\\/]/.test(ROOT) && !argv.includes('--allow-local')) {
126
+ let globalRoot = '';
127
+ try {
128
+ globalRoot = execSync('npm root -g', { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
129
+ }
130
+ catch { /* npm not on PATH */ }
131
+ const isGlobal = globalRoot && path.resolve(ROOT).toLowerCase().startsWith(path.resolve(globalRoot).toLowerCase());
132
+ if (!isGlobal) {
133
+ console.error([
134
+ '',
135
+ 'This copy of the package is installed locally (inside a project\'s node_modules), so the hosts would be',
136
+ 'pointed at a path that vanishes with the folder or the next npm install there. Install it globally instead:',
137
+ '',
138
+ ' npm i -g @aiguru/google-web-operations',
139
+ ' gwo install',
140
+ '',
141
+ 'To register this local copy anyway (a deliberate, pinned setup): gwo install --allow-local',
142
+ ].join('\n'));
143
+ process.exit(2);
144
+ }
145
+ }
122
146
  if (!uninstall)
123
147
  ensureHome();
124
148
  if (targets.includes('claude'))