@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 +10 -2
- package/scripts/postinstall.cjs +92 -0
- package/src/automation/cf_token_via_session.py +0 -158
- package/src/automation/cloudflare_signup.py +0 -3689
- package/src/automation/codebuddy_signup.py +0 -636
- package/src/automation/flashloop_signup.py +0 -338
- package/src/automation/kimi_signup.py +0 -401
- package/src/automation/leonardo_signup.py +0 -902
- package/src/automation/openvecta_signup.py +0 -652
- package/src/automation/qoder_signup.py +0 -323
- package/src/automation/test_proxy.py +0 -86
- package/src/automation/weavy_signup.py +0 -1964
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fudrouter/fsrouter",
|
|
3
|
-
"version": "0.6.
|
|
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()
|