@fudrouter/fsrouter 0.6.92 → 0.6.94

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fudrouter/fsrouter",
3
- "version": "0.6.92",
3
+ "version": "0.6.94",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "9Router v2 — Express Backend (API, SSE, DB, Auth, MITM)",
@@ -9,8 +9,16 @@
9
9
  "fsrouter": "bin/cli.cjs"
10
10
  },
11
11
  "scripts": {
12
- "start": "node dist/server.js"
12
+ "start": "node dist/server.js",
13
+ "postinstall": "node scripts/postinstall.cjs"
13
14
  },
15
+ "files": [
16
+ "dist",
17
+ "public",
18
+ "open-sse",
19
+ "bin",
20
+ "scripts"
21
+ ],
14
22
  "dependencies": {
15
23
  "bcryptjs": "^3.0.3",
16
24
  "better-sqlite3": "^12.11.1",
@@ -0,0 +1,92 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Post-install: wire automation symlinks so the signup scripts can find the
4
+ * Python venv, source tree and browser profiles regardless of the process cwd.
5
+ *
6
+ * The automation routes resolve paths relative to process.cwd()
7
+ * (e.g. .venv/bin/python, src/automation/*.py, profiles/<provider>). When the
8
+ * server runs under pm2 the cwd is the installed package directory
9
+ * (/usr/local/lib/node_modules/@fudrouter/fsrouter), NOT /root — so we create
10
+ * the symlinks in BOTH locations to be cwd-independent.
11
+ *
12
+ * Idempotent and non-fatal: never throws (npm install must succeed even if a
13
+ * symlink can't be created).
14
+ */
15
+ const fs = require("fs");
16
+ const path = require("path");
17
+
18
+ function log(msg) {
19
+ // eslint-disable-next-line no-console
20
+ console.log(`[fsrouter:postinstall] ${msg}`);
21
+ }
22
+
23
+ function isSymlink(p) {
24
+ try {
25
+ return fs.lstatSync(p).isSymbolicLink();
26
+ } catch {
27
+ return false;
28
+ }
29
+ }
30
+
31
+ function symlinkIfMissing(linkPath, targetPath) {
32
+ try {
33
+ if (!fs.existsSync(targetPath)) {
34
+ // target missing — remove a dangling link if present, then skip
35
+ if (isSymlink(linkPath)) fs.unlinkSync(linkPath);
36
+ log(`skip ${linkPath} (target ${targetPath} not found)`);
37
+ return;
38
+ }
39
+ if (isSymlink(linkPath)) {
40
+ if (fs.readlinkSync(linkPath) === targetPath) {
41
+ log(`ok ${linkPath} -> ${targetPath}`);
42
+ return;
43
+ }
44
+ fs.unlinkSync(linkPath);
45
+ } else if (fs.existsSync(linkPath)) {
46
+ log(`skip ${linkPath} (exists and is not a symlink — leaving untouched)`);
47
+ return;
48
+ }
49
+ fs.symlinkSync(targetPath, linkPath, "dir");
50
+ log(`linked ${linkPath} -> ${targetPath}`);
51
+ } catch (e) {
52
+ log(`warn: could not link ${linkPath} -> ${targetPath}: ${e.message}`);
53
+ }
54
+ }
55
+
56
+ function main() {
57
+ // Real AMRouter checkout (where the venv + browser profiles live)
58
+ const candidates = ["/root/AMRouter", path.resolve(__dirname, "..", "..")];
59
+ let base = null;
60
+ for (const c of candidates) {
61
+ if (fs.existsSync(c)) {
62
+ base = c;
63
+ break;
64
+ }
65
+ }
66
+ if (!base) {
67
+ log("no AMRouter base found; skipping symlinks");
68
+ return;
69
+ }
70
+ log(`base=${base}`);
71
+
72
+ const venvTarget = path.join(base, ".venv");
73
+ const srcTarget = path.join(base, "backend", "src");
74
+ const profilesTarget = path.join(base, "backend", "profiles");
75
+
76
+ // Locations to symlink from (cwd-independent coverage)
77
+ const linkBases = ["/root", process.cwd()]; // /root + the dir npm ran install in (global package dir under pm2)
78
+
79
+ for (const lb of linkBases) {
80
+ symlinkIfMissing(path.join(lb, ".venv"), venvTarget);
81
+ // src only if it doesn't already exist as a real dir (package ships src/)
82
+ const srcLink = path.join(lb, "src");
83
+ if (!fs.existsSync(srcLink)) symlinkIfMissing(srcLink, srcTarget);
84
+ symlinkIfMissing(path.join(lb, "profiles"), profilesTarget);
85
+ }
86
+ }
87
+
88
+ try {
89
+ main();
90
+ } catch (e) {
91
+ log(`unexpected error: ${e.message}`);
92
+ }
@@ -1,158 +0,0 @@
1
- #!/usr/bin/env python3
2
- """
3
- cf_token_via_session.py
4
- Login ke akun Cloudflare yang ada, buat Workers AI token via session cookie API.
5
- Usage:
6
- python cf_token_via_session.py --email X --password Y --account-id Z
7
- """
8
- import argparse, json, time, sys
9
- import requests
10
- from pathlib import Path
11
-
12
- def log(m): print(json.dumps({"step": m}), flush=True)
13
- def die(m): print(json.dumps({"status": "error", "error": m}), flush=True); sys.exit(1)
14
-
15
-
16
- def create_token_with_cookies(cookies: dict, account_id: str) -> str | None:
17
- """POST /api/v4/user/tokens using browser session cookies."""
18
- base = "https://dash.cloudflare.com"
19
- sess = requests.Session()
20
- sess.cookies.update(cookies)
21
- sess.headers.update({
22
- "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 Chrome/120",
23
- "Referer": f"{base}/profile/api-tokens/create",
24
- "Origin": base,
25
- "Accept": "application/json",
26
- })
27
-
28
- # A1: Get permission groups to find Workers AI ID
29
- log("Fetching permission groups...")
30
- r = sess.get(f"{base}/api/v4/user/tokens/permission_groups", timeout=15)
31
- log(f"Permission groups status: {r.status_code}")
32
- if r.status_code != 200:
33
- log(f"Permission groups response: {r.text[:300]}")
34
- return None
35
-
36
- pg_data = r.json()
37
- log(f"Total groups: {len(pg_data.get('result', []))}")
38
-
39
- # Find Workers AI groups
40
- workers_ai_ids = []
41
- for pg in pg_data.get('result', []):
42
- name = pg.get('name', '')
43
- if 'Workers AI' in name:
44
- log(f"Found: {name} -> {pg['id']}")
45
- workers_ai_ids.append({'id': pg['id'], 'name': name})
46
-
47
- if not workers_ai_ids:
48
- log("No Workers AI permission groups found")
49
- log(f"All groups: {[p['name'] for p in pg_data.get('result', [])][:20]}")
50
- return None
51
-
52
- # Prefer Workers AI:Read for safety, otherwise take first
53
- target_id = workers_ai_ids[0]['id']
54
- for pg in workers_ai_ids:
55
- if 'Read' in pg['name']:
56
- target_id = pg['id']
57
- break
58
-
59
- log(f"Using permission group ID: {target_id}")
60
-
61
- # A2: Create token
62
- payload = {
63
- "name": "9router-workers-ai",
64
- "policies": [{
65
- "effect": "allow",
66
- "resources": {f"com.cloudflare.api.account.{account_id}": "*"},
67
- "permission_groups": [{"id": target_id}]
68
- }]
69
- }
70
-
71
- log(f"Creating token with payload: {json.dumps(payload)}")
72
- r2 = sess.post(f"{base}/api/v4/user/tokens", json=payload, timeout=15)
73
- log(f"Create token status: {r2.status_code}")
74
- resp2 = r2.json()
75
- log(f"Response: {json.dumps(resp2)[:500]}")
76
-
77
- if resp2.get('success'):
78
- return resp2['result'].get('value', '')
79
- return None
80
-
81
-
82
- def main():
83
- parser = argparse.ArgumentParser()
84
- parser.add_argument("--email", required=True)
85
- parser.add_argument("--password", required=True)
86
- parser.add_argument("--account-id", required=True, dest="account_id")
87
- parser.add_argument("--headless", action="store_true")
88
- args = parser.parse_args()
89
-
90
- try:
91
- from camoufox.sync_api import Camoufox
92
- except ImportError:
93
- die("Camoufox tidak terinstall")
94
-
95
- log(f"Login ke CF sebagai {args.email}...")
96
- with Camoufox(headless=args.headless, os="windows") as browser:
97
- page = browser.new_page()
98
-
99
- # Navigate to login
100
- page.goto("https://dash.cloudflare.com/login", wait_until="domcontentloaded", timeout=30000)
101
- time.sleep(3)
102
-
103
- # Fill login form
104
- try:
105
- email_inp = page.locator("input[name='email'], input[type='email']").first
106
- if email_inp.is_visible(timeout=5000):
107
- email_inp.fill(args.email)
108
- time.sleep(0.3)
109
-
110
- pass_inp = page.locator("input[type='password']").first
111
- if pass_inp.is_visible(timeout=3000):
112
- pass_inp.fill(args.password)
113
- time.sleep(0.3)
114
-
115
- for sel in ["button[type='submit']", "button:has-text('Log in')", "button:has-text('Sign in')"]:
116
- try:
117
- btn = page.locator(sel).first
118
- if btn.is_visible(timeout=2000):
119
- btn.click()
120
- break
121
- except Exception:
122
- continue
123
-
124
- log("Login submitted, waiting...")
125
- time.sleep(6)
126
- except Exception as e:
127
- log(f"Login form error: {e}")
128
-
129
- log(f"Current URL: {page.url}")
130
- page.screenshot(path="/tmp/cf_login_state.png")
131
-
132
- # Check if logged in
133
- if "dash.cloudflare.com" not in page.url:
134
- die(f"Login failed, URL: {page.url}")
135
-
136
- # Extract cookies
137
- cookies_list = page.context.cookies()
138
- cookies = {c['name']: c['value'] for c in cookies_list}
139
- log(f"Got {len(cookies)} cookies: {list(cookies.keys())[:10]}")
140
-
141
- # Create token via API
142
- token = create_token_with_cookies(cookies, args.account_id)
143
-
144
- if token:
145
- log(f"TOKEN BERHASIL: {token[:12]}...")
146
- print(json.dumps({
147
- "status": "success",
148
- "workers_ai_token": token,
149
- "account_id": args.account_id,
150
- "email": args.email
151
- }), flush=True)
152
- else:
153
- page.screenshot(path="/tmp/cf_session_debug.png")
154
- die("Token creation via session failed")
155
-
156
-
157
- if __name__ == "__main__":
158
- main()