claude-access-for-all 0.1.0 → 0.2.0
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/README.md +6 -5
- package/bin/claude-access.js +9 -6
- package/docs/privacy-inspector.md +11 -7
- package/package.json +3 -2
- package/third-party/FuckClaude-LICENSE.txt +21 -0
- package/tools/access.py +5 -3
- package/tools/privacy.py +6 -4
- package/tools/report.py +85 -0
- package/tools/score.py +99 -0
package/README.md
CHANGED
|
@@ -57,19 +57,20 @@ Choose an action from the interactive menu, or call one directly:
|
|
|
57
57
|
```sh
|
|
58
58
|
claude-access doctor
|
|
59
59
|
claude-access audit
|
|
60
|
-
claude-access
|
|
61
|
-
claude-access
|
|
62
|
-
claude-access
|
|
60
|
+
claude-access score
|
|
61
|
+
claude-access cli --timezone America/Los_Angeles
|
|
62
|
+
claude-access app --timezone America/Los_Angeles
|
|
63
|
+
claude-access web --browser chrome --timezone America/Los_Angeles
|
|
63
64
|
claude-access inspect
|
|
64
65
|
claude-access reminder --renewal 2026-12-01 --days-before 3 > renewal.ics
|
|
65
66
|
open renewal.ics
|
|
66
67
|
```
|
|
67
68
|
|
|
68
|
-
To install directly from GitHub, use `npm install -g github:AMark-CS/claude-access-for-all`. Use `claude-access help` to see the complete command list. `inspect` opens the local graphical browser inspector in your default browser. To check a separate browser profile, open its local file in that profile instead; see the [privacy inspector guide](docs/privacy-inspector.md).
|
|
69
|
+
To install directly from GitHub, use `npm install -g github:AMark-CS/claude-access-for-all`. Use `claude-access help` to see the complete command list. Diagnostics display a readable summary by default. `inspect` opens the local graphical browser inspector in your default browser. To check a separate browser profile, open its local file in that profile instead; see the [privacy inspector guide](docs/privacy-inspector.md).
|
|
69
70
|
|
|
70
71
|
For the calendar reminder, replace the example date with the next billing date shown by your billing provider. The file contains one all-day review event with an alert; import it into Apple Calendar and check your notification settings. It neither cancels nor renews anything, and must be regenerated when the billing date changes.
|
|
71
72
|
|
|
72
|
-
The diagnostics run offline and do not print environment-variable values. The scoped launchers request process-local timezone and language settings; Desktop and browsers may behave differently. The browser launcher opens Claude in a separate profile and prints its private data directory. Review the [privacy guide](docs/privacy-inspector.md) for limitations before use.
|
|
73
|
+
The diagnostics run offline and do not print environment-variable values. The `score` command uses the third-party reference weights for timezone and UTC offset only, with its partial denominator and unmeasured browser signals clearly labeled; it is not a ban prediction. Scoped launchers default to the Los Angeles timezone. The scoped launchers request process-local timezone and language settings; Desktop and browsers may behave differently. The browser launcher opens Claude in a separate profile and prints its private data directory. Review the [privacy guide](docs/privacy-inspector.md) for limitations before use.
|
|
73
74
|
|
|
74
75
|
To run the source version directly without npm, use `python3 tools/access.py doctor` or `python3 tools/privacy.py audit` from a clone. Review the short [source](tools/access.py) before running it.
|
|
75
76
|
|
package/bin/claude-access.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
3
4
|
import { createInterface } from 'node:readline/promises';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { dirname, join } from 'node:path';
|
|
@@ -12,6 +13,7 @@ Usage: claude-access [command] [options]
|
|
|
12
13
|
menu Open the interactive menu (default)
|
|
13
14
|
doctor Check local installation and configuration presence
|
|
14
15
|
audit Inspect CLI environment signals locally
|
|
16
|
+
score Score locally observable timezone signals
|
|
15
17
|
cli [options] [-- ...] Launch Claude Code with scoped locale settings
|
|
16
18
|
app [options] Launch Claude Desktop with scoped locale settings
|
|
17
19
|
web [options] Launch Claude in a separate browser profile
|
|
@@ -22,8 +24,8 @@ Usage: claude-access [command] [options]
|
|
|
22
24
|
version Show the package version
|
|
23
25
|
|
|
24
26
|
Examples:
|
|
25
|
-
claude-access cli --timezone
|
|
26
|
-
claude-access web --browser edge --timezone
|
|
27
|
+
claude-access cli --timezone America/Los_Angeles -- --version
|
|
28
|
+
claude-access web --browser edge --timezone America/Los_Angeles
|
|
27
29
|
claude-access reminder --renewal 2026-12-01 --days-before 3 > renewal.ics
|
|
28
30
|
|
|
29
31
|
Requires macOS, Node.js 18+, and Python 3.9+. Launching Claude requires the
|
|
@@ -46,6 +48,7 @@ function dispatch(command, args = []) {
|
|
|
46
48
|
switch (command) {
|
|
47
49
|
case 'doctor': return run('python3', [join(root, 'tools/access.py'), 'doctor', ...args]);
|
|
48
50
|
case 'audit': return run('python3', [join(root, 'tools/privacy.py'), 'audit', ...args]);
|
|
51
|
+
case 'score': return run('python3', [join(root, 'tools/score.py'), ...args]);
|
|
49
52
|
case 'cli': case 'app': case 'web':
|
|
50
53
|
return run('python3', [join(root, 'tools/privacy.py'), command, ...args]);
|
|
51
54
|
case 'reminder': return run('python3', [join(root, 'tools/access.py'), 'reminder', ...args]);
|
|
@@ -56,7 +59,7 @@ function dispatch(command, args = []) {
|
|
|
56
59
|
if (process.platform !== 'darwin') { console.error('The guide launcher requires macOS.'); return 1; }
|
|
57
60
|
return run('/usr/bin/open', ['https://github.com/AMark-CS/claude-access-for-all/blob/main/docs/privacy-inspector.md']);
|
|
58
61
|
case 'help': case '--help': case '-h': console.log(help); return 0;
|
|
59
|
-
case 'version': case '--version': case '-v': console.log('
|
|
62
|
+
case 'version': case '--version': case '-v': console.log(JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')).version); return 0;
|
|
60
63
|
default: console.error(`Unknown command: ${command}\n\n${help}`); return 2;
|
|
61
64
|
}
|
|
62
65
|
}
|
|
@@ -68,10 +71,10 @@ async function menu() {
|
|
|
68
71
|
}
|
|
69
72
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
70
73
|
try {
|
|
71
|
-
console.log(`\nClaude Access for All\n\n 1 Check local environment\n 2 Inspect CLI signals\n 3 Start Claude Code\n 4 Start Claude Desktop\n 5 Start Claude in a separate browser profile\n 6 Open browser signal inspector\n 7 Open the privacy guide\n 8 Create a renewal calendar file\n 0 Exit\n`);
|
|
74
|
+
console.log(`\nClaude Access for All\n\n 1 Check local environment\n 2 Inspect CLI signals\n 3 Start Claude Code\n 4 Start Claude Desktop\n 5 Start Claude in a separate browser profile\n 6 Open browser signal inspector\n 7 Open the privacy guide\n 8 Create a renewal calendar file\n 9 Score local timezone signals\n 0 Exit\n`);
|
|
72
75
|
const choice = (await rl.question('Choose an option: ')).trim();
|
|
73
76
|
if (choice === '0') return 0;
|
|
74
|
-
const simple = { '1': 'doctor', '2': 'audit', '3': 'cli', '4': 'app', '6': 'inspect', '7': 'guide' };
|
|
77
|
+
const simple = { '1': 'doctor', '2': 'audit', '3': 'cli', '4': 'app', '6': 'inspect', '7': 'guide', '9': 'score' };
|
|
75
78
|
if (simple[choice]) { rl.close(); return dispatch(simple[choice]); }
|
|
76
79
|
if (choice === '5') {
|
|
77
80
|
const browser = (await rl.question('Browser (chrome/edge) [chrome]: ')).trim() || 'chrome';
|
|
@@ -92,7 +95,7 @@ async function menu() {
|
|
|
92
95
|
console.log(`Created ${output}. Import it into Calendar and check notification settings.`);
|
|
93
96
|
return 0;
|
|
94
97
|
}
|
|
95
|
-
console.error('Unknown option. Run the menu again to choose 0–
|
|
98
|
+
console.error('Unknown option. Run the menu again to choose 0–9.');
|
|
96
99
|
return 2;
|
|
97
100
|
} finally { rl.close(); }
|
|
98
101
|
}
|
|
@@ -1,26 +1,30 @@
|
|
|
1
1
|
# Client privacy inspector and scoped launchers
|
|
2
2
|
|
|
3
|
-
Reviewed October 27, 2026. This toolkit borrows the idea of independently inspecting timezone, language, browser identity, and fonts from [Fuck Claude](https://fuck-claude.com/). Its scoring and claims are third-party interpretations, not verified Anthropic enforcement rules.
|
|
3
|
+
Reviewed October 27, 2026. This toolkit borrows the idea of independently inspecting timezone, language, browser identity, and fonts from [Fuck Claude](https://fuck-claude.com/). Its scoring and claims are third-party interpretations, not verified Anthropic enforcement rules. The CLI adapts its timezone and offset scoring rules with attribution; the limited result is not a ban prediction. See the bundled [MIT notice](../third-party/FuckClaude-LICENSE.txt).
|
|
4
4
|
|
|
5
5
|
Each client has its own process and network path. A change applied to Terminal does not necessarily affect Desktop or an existing browser. Nothing here changes the macOS system timezone, system language, global proxy settings, the installed Claude apps, or existing browser profiles.
|
|
6
6
|
|
|
7
|
+
## Local partial score
|
|
8
|
+
|
|
9
|
+
Run `claude-access score` in the terminal to score the current process timezone and current UTC offset. The result uses the reference project's 24-point timezone rule and 3-point offset rule. It shows earned points and only the maximum for signals actually available, so it is **not** the reference browser's 100-point score. Other reference signals require a browser and are marked unmeasured; IP, credentials, billing and account state are not checked. A missing timezone is excluded rather than treated as zero. `TZ=America/Los_Angeles claude-access score` checks the requested launcher timezone in a child process, not your existing browser or Desktop application. Rules are third-party heuristics and do not establish an Anthropic enforcement decision.
|
|
10
|
+
|
|
7
11
|
## 1. Claude Code CLI
|
|
8
12
|
|
|
9
13
|
```sh
|
|
10
14
|
python3 tools/privacy.py audit
|
|
11
|
-
python3 tools/privacy.py cli --timezone
|
|
15
|
+
python3 tools/privacy.py cli --timezone America/Los_Angeles --locale en_US.UTF-8
|
|
12
16
|
# Pass CLI arguments after --, for example:
|
|
13
|
-
python3 tools/privacy.py cli --timezone
|
|
17
|
+
python3 tools/privacy.py cli --timezone America/Los_Angeles -- --version
|
|
14
18
|
```
|
|
15
19
|
|
|
16
20
|
The audit reports the current shell's timezone names and whether known locale, routing, and authentication environment variables are set. It does not print their values. Treat even local timezone output as private. It cannot inspect values in Claude settings, other running processes, system proxies, DNS, or server-side checks.
|
|
17
21
|
|
|
18
|
-
The launcher starts `claude` with `TZ`, `LANG`, and `LC_ALL` changed only in the new process and its child processes. Select another installed IANA timezone with `--timezone`, and another available locale with `--locale`. An invalid timezone is rejected. Locale availability and application behavior vary. Inherited endpoint, proxy, and authentication variables are preserved; check `/status` for the actual authentication route. Shell hooks, settings files, plugins and background services may affect behavior. In particular, a previously running Claude Code background supervisor can have its own environment.
|
|
22
|
+
The launcher defaults to `America/Los_Angeles` and starts `claude` with `TZ`, `LANG`, and `LC_ALL` changed only in the new process and its child processes. Select another installed IANA timezone with `--timezone`, and another available locale with `--locale`. An invalid timezone is rejected. Locale availability and application behavior vary. Inherited endpoint, proxy, and authentication variables are preserved; check `/status` for the actual authentication route. Shell hooks, settings files, plugins and background services may affect behavior. In particular, a previously running Claude Code background supervisor can have its own environment.
|
|
19
23
|
|
|
20
24
|
## 2. Claude Desktop for macOS
|
|
21
25
|
|
|
22
26
|
```sh
|
|
23
|
-
python3 tools/privacy.py app --timezone
|
|
27
|
+
python3 tools/privacy.py app --timezone America/Los_Angeles --locale en_US.UTF-8
|
|
24
28
|
# If installed under a different name:
|
|
25
29
|
python3 tools/privacy.py app --app '/Applications/Claude.app'
|
|
26
30
|
```
|
|
@@ -30,9 +34,9 @@ This asks macOS Launch Services to open a new app instance with the specified en
|
|
|
30
34
|
## 3. Claude on the web
|
|
31
35
|
|
|
32
36
|
```sh
|
|
33
|
-
python3 tools/privacy.py web --browser chrome --timezone
|
|
37
|
+
python3 tools/privacy.py web --browser chrome --timezone America/Los_Angeles --language en-US
|
|
34
38
|
# Or use Microsoft Edge:
|
|
35
|
-
python3 tools/privacy.py web --browser edge --timezone
|
|
39
|
+
python3 tools/privacy.py web --browser edge --timezone America/Los_Angeles --language en-US
|
|
36
40
|
```
|
|
37
41
|
|
|
38
42
|
The launcher creates a fresh, private browser data directory and opens `https://claude.ai/` in a separate profile. It prints that path locally. The profile can contain login cookies and other sensitive data; it is not deleted automatically. To reuse it, pass `--profile '/absolute/path/printed/earlier'`. Close the browser before manually deleting a profile you no longer need. Browser launches require the chosen browser to be installed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-access-for-all",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "A macOS guide and local privacy toolkit for Claude Code, Desktop, and web",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -14,7 +14,8 @@
|
|
|
14
14
|
"docs/",
|
|
15
15
|
"README.md",
|
|
16
16
|
"LICENSE",
|
|
17
|
-
"SECURITY.md"
|
|
17
|
+
"SECURITY.md",
|
|
18
|
+
"third-party/"
|
|
18
19
|
],
|
|
19
20
|
"engines": {
|
|
20
21
|
"node": ">=18"
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LinXiaoTao (https://github.com/LinXiaoTao/FuckClaude)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/tools/access.py
CHANGED
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
"""Offline environment checks and a one-time subscription review calendar."""
|
|
3
3
|
import argparse
|
|
4
4
|
from datetime import date, datetime, timedelta, timezone
|
|
5
|
-
import json
|
|
6
5
|
import os
|
|
7
6
|
import platform
|
|
8
7
|
import shutil
|
|
9
8
|
import sys
|
|
10
9
|
import uuid
|
|
11
10
|
|
|
11
|
+
from report import render_doctor
|
|
12
|
+
|
|
12
13
|
VARIABLES = (
|
|
13
14
|
'ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN', 'ANTHROPIC_BASE_URL',
|
|
14
15
|
'CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', 'CLAUDE_CODE_USE_FOUNDRY',
|
|
@@ -92,13 +93,14 @@ def reminder(renewal, lead, today=None):
|
|
|
92
93
|
def main():
|
|
93
94
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
94
95
|
commands = parser.add_subparsers(dest='command', required=True)
|
|
95
|
-
commands.add_parser('doctor', help='Print an offline
|
|
96
|
+
commands.add_parser('doctor', help='Print an offline environment report.')
|
|
96
97
|
calendar = commands.add_parser('reminder', help='Print a one-time .ics calendar to stdout.')
|
|
97
98
|
calendar.add_argument('--renewal', type=parse_date, required=True)
|
|
98
99
|
calendar.add_argument('--days-before', type=days_before, default=3)
|
|
99
100
|
args = parser.parse_args()
|
|
100
101
|
if args.command == 'doctor':
|
|
101
|
-
|
|
102
|
+
result = doctor()
|
|
103
|
+
print(render_doctor(result))
|
|
102
104
|
else:
|
|
103
105
|
try:
|
|
104
106
|
content = reminder(args.renewal, args.days_before)
|
package/tools/privacy.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
"""Inspect and launch Claude clients with explicit, process-scoped locale settings."""
|
|
3
3
|
import argparse
|
|
4
|
-
import json
|
|
5
4
|
import os
|
|
6
5
|
from pathlib import Path
|
|
7
6
|
import platform
|
|
@@ -12,6 +11,8 @@ import sys
|
|
|
12
11
|
import tempfile
|
|
13
12
|
import time
|
|
14
13
|
|
|
14
|
+
from report import render_audit
|
|
15
|
+
|
|
15
16
|
ROUTING = ('ANTHROPIC_BASE_URL', 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
|
|
16
17
|
'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy')
|
|
17
18
|
AUTH = ('ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN')
|
|
@@ -65,10 +66,10 @@ def browser_command(browser, profile, language, timezone, locale):
|
|
|
65
66
|
def main():
|
|
66
67
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
67
68
|
sub = parser.add_subparsers(dest='command', required=True)
|
|
68
|
-
sub.add_parser('audit', help='Show local
|
|
69
|
+
sub.add_parser('audit', help='Show local signals without exposing secret values.')
|
|
69
70
|
for name in ('cli', 'app', 'web'):
|
|
70
71
|
command = sub.add_parser(name, help='Launch ' + name + ' with process-scoped settings.')
|
|
71
|
-
command.add_argument('--timezone', type=validate_timezone, default='
|
|
72
|
+
command.add_argument('--timezone', type=validate_timezone, default='America/Los_Angeles')
|
|
72
73
|
command.add_argument('--locale', type=validate_locale, default='en_US.UTF-8')
|
|
73
74
|
if name == 'cli':
|
|
74
75
|
command.add_argument('claude_args', nargs=argparse.REMAINDER)
|
|
@@ -80,7 +81,8 @@ def main():
|
|
|
80
81
|
command.add_argument('--language', default='en-US', type=validate_locale)
|
|
81
82
|
args = parser.parse_args()
|
|
82
83
|
if args.command == 'audit':
|
|
83
|
-
|
|
84
|
+
result = audit()
|
|
85
|
+
print(render_audit(result))
|
|
84
86
|
return
|
|
85
87
|
if sys.platform != 'darwin':
|
|
86
88
|
parser.error('Launch commands require macOS.')
|
package/tools/report.py
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"""Readable, privacy-conscious CLI reports."""
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def style(text, code):
|
|
7
|
+
if sys.stdout.isatty() and not os.environ.get('NO_COLOR') and os.environ.get('TERM') != 'dumb':
|
|
8
|
+
return f'\033[{code}m{text}\033[0m'
|
|
9
|
+
return text
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def title(name, subtitle):
|
|
13
|
+
return [
|
|
14
|
+
'',
|
|
15
|
+
' ' + style('CLAUDE ACCESS', '1;36') + style(' / ' + name.upper(), '1'),
|
|
16
|
+
' ' + style(subtitle, '2'),
|
|
17
|
+
'',
|
|
18
|
+
]
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def row(label, value):
|
|
22
|
+
return f' {label:<20} {value}'
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def section(name):
|
|
26
|
+
return '\n ' + style(name, '1')
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def group(label, names, values):
|
|
30
|
+
active = [name for name in names if values.get(name)]
|
|
31
|
+
if active:
|
|
32
|
+
return row(label, style('Set', '33') + ' ' + ', '.join(active))
|
|
33
|
+
return row(label, style('Not set', '2'))
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def render_doctor(report):
|
|
37
|
+
env = report['environment_set']
|
|
38
|
+
system = report['system']
|
|
39
|
+
if system == 'Darwin':
|
|
40
|
+
system = 'macOS ' + (report['macos_version'] or '(version unavailable)')
|
|
41
|
+
lines = title('Environment check', 'Local diagnostic · no network requests')
|
|
42
|
+
lines += [
|
|
43
|
+
row('System', system + ' · ' + report['architecture']),
|
|
44
|
+
row('Claude Code', style('Found on PATH', '32') if report['claude_on_path'] else style('Not found on PATH', '33')),
|
|
45
|
+
section('Environment variables'),
|
|
46
|
+
group('Authentication', ('ANTHROPIC_API_KEY', 'ANTHROPIC_AUTH_TOKEN'), env),
|
|
47
|
+
group('Service endpoint', ('ANTHROPIC_BASE_URL',), env),
|
|
48
|
+
group('Cloud providers', ('CLAUDE_CODE_USE_BEDROCK', 'CLAUDE_CODE_USE_VERTEX', 'CLAUDE_CODE_USE_FOUNDRY'), env),
|
|
49
|
+
group('Proxy variables', ('HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY', 'http_proxy', 'https_proxy', 'all_proxy', 'no_proxy'), env),
|
|
50
|
+
group('TLS settings', ('NODE_EXTRA_CA_CERTS', 'NODE_TLS_REJECT_UNAUTHORIZED'), env),
|
|
51
|
+
'',
|
|
52
|
+
]
|
|
53
|
+
if report['warnings']:
|
|
54
|
+
lines.append(' ' + style('Things to review', '1;33'))
|
|
55
|
+
lines.extend(' • ' + warning for warning in report['warnings'])
|
|
56
|
+
else:
|
|
57
|
+
lines.append(' ' + style('No local configuration warnings.', '32'))
|
|
58
|
+
lines += ['', ' Values are never shown. System proxies, app settings, credentials,',
|
|
59
|
+
' billing, account eligibility and network access were not checked.', '']
|
|
60
|
+
return '\n'.join(lines)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def render_audit(report):
|
|
64
|
+
names = list(dict.fromkeys(name for name in report['process_timezone_names'] if name))
|
|
65
|
+
tz = ' / '.join(names) if names else 'Unavailable'
|
|
66
|
+
lines = title('CLI signal audit', 'Current terminal process · offline')
|
|
67
|
+
lines += [
|
|
68
|
+
row('System', report['system']),
|
|
69
|
+
row('Claude Code', style('Found on PATH', '32') if report['claude_on_path'] else style('Not found on PATH', '33')),
|
|
70
|
+
section('Locale & time'),
|
|
71
|
+
row('Timezone names', tz),
|
|
72
|
+
row('TZ override', style('Set', '33') if report['timezone_override_set'] else style('Not set', '2')),
|
|
73
|
+
group('Locale variables', ('LANG', 'LC_ALL', 'LC_TIME'), report['locale_overrides_set']),
|
|
74
|
+
section('Connection & authentication'),
|
|
75
|
+
group('Endpoint & proxies', report['routing_overrides_set'].keys(), report['routing_overrides_set']),
|
|
76
|
+
group('Credentials', report['credential_overrides_set'].keys(), report['credential_overrides_set']),
|
|
77
|
+
'',
|
|
78
|
+
' Values are never shown. HKT and other abbreviations are timezone names,',
|
|
79
|
+
' not proof of an exact location. Browser, Desktop, network, IP and',
|
|
80
|
+
' account status were not checked.',
|
|
81
|
+
'',
|
|
82
|
+
' Next: run ' + style('claude-access inspect', '1') + ' to inspect your default browser.',
|
|
83
|
+
'',
|
|
84
|
+
]
|
|
85
|
+
return '\n'.join(lines)
|
package/tools/score.py
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
"""Local partial signal score, adapted from LinXiaoTao/FuckClaude (MIT)."""
|
|
2
|
+
import argparse
|
|
3
|
+
import datetime
|
|
4
|
+
import os
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
from report import section, style, title
|
|
8
|
+
|
|
9
|
+
# Based on src/config/signals.ts in LinXiaoTao/FuckClaude, master as reviewed
|
|
10
|
+
# 2026-10-27. Browser-only measurements are deliberately not substituted.
|
|
11
|
+
MAINLAND_ZONES = frozenset(('Asia/Shanghai', 'Asia/Urumqi', 'Asia/Chongqing',
|
|
12
|
+
'Asia/Chungking', 'Asia/Harbin', 'Asia/Kashgar'))
|
|
13
|
+
OTHER_ZONES = frozenset(('Asia/Hong_Kong', 'Asia/Macau'))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def timezone_name(env=None, localtime='/etc/localtime'):
|
|
17
|
+
env = os.environ if env is None else env
|
|
18
|
+
if 'TZ' in env:
|
|
19
|
+
# POSIX strings and empty values are not necessarily IANA timezones.
|
|
20
|
+
value = env['TZ'].lstrip(':')
|
|
21
|
+
if value == 'UTC':
|
|
22
|
+
return value
|
|
23
|
+
parts = value.split('/')
|
|
24
|
+
if len(parts) > 1 and all(part and part not in ('.', '..') for part in parts):
|
|
25
|
+
if (Path('/usr/share/zoneinfo') / value).is_file():
|
|
26
|
+
return value
|
|
27
|
+
return None
|
|
28
|
+
try:
|
|
29
|
+
target = str(Path(localtime).resolve(strict=True))
|
|
30
|
+
except (OSError, RuntimeError):
|
|
31
|
+
return None
|
|
32
|
+
marker = '/zoneinfo/'
|
|
33
|
+
return target.split(marker, 1)[1] if marker in target else None
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def timezone_weight(name):
|
|
37
|
+
if name is None:
|
|
38
|
+
return None
|
|
39
|
+
if name in MAINLAND_ZONES:
|
|
40
|
+
return 1.0
|
|
41
|
+
if name in OTHER_ZONES:
|
|
42
|
+
return 0.6
|
|
43
|
+
return 0.0
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def collect():
|
|
47
|
+
name = timezone_name()
|
|
48
|
+
offset = datetime.datetime.now().astimezone().utcoffset()
|
|
49
|
+
minutes = int(offset.total_seconds() / 60) if offset is not None else None
|
|
50
|
+
return {'timezone': name, 'offset_minutes': minutes}
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def render_score(data):
|
|
54
|
+
name = data['timezone']
|
|
55
|
+
ratio = timezone_weight(name)
|
|
56
|
+
minutes = data['offset_minutes']
|
|
57
|
+
tz_points = None if ratio is None else ratio * 24
|
|
58
|
+
offset_points = None if minutes is None else (2.1 if minutes == 480 else 0)
|
|
59
|
+
values = [x for x in (tz_points, offset_points) if x is not None]
|
|
60
|
+
possible = (24 if tz_points is not None else 0) + (3 if offset_points is not None else 0)
|
|
61
|
+
points = sum(values)
|
|
62
|
+
bar_filled = round(points / possible * 24) if possible else 0
|
|
63
|
+
bar = '█' * bar_filled + '░' * (24 - bar_filled)
|
|
64
|
+
lines = title('Local signal score', 'Terminal-only snapshot · offline')
|
|
65
|
+
if possible:
|
|
66
|
+
lines += [' ' + style(f'{points:g} / {possible} points', '1;36'),
|
|
67
|
+
' ' + style(bar, '36'),
|
|
68
|
+
' Partial score · available signals only · not a 100-point result', '']
|
|
69
|
+
else:
|
|
70
|
+
lines += [' Score unavailable · no supported signals could be read', '']
|
|
71
|
+
lines += [section('Measured signals')]
|
|
72
|
+
lines += [f" {'Timezone':<16} {(name or 'Unavailable'):<28} " +
|
|
73
|
+
(f'{tz_points:g} / 24' if tz_points is not None else 'not scored')]
|
|
74
|
+
if minutes is None:
|
|
75
|
+
offset_text = 'Unavailable'
|
|
76
|
+
else:
|
|
77
|
+
hours, mins = divmod(abs(minutes), 60)
|
|
78
|
+
offset_text = f"UTC{'+' if minutes >= 0 else '-'}{hours:02d}:{mins:02d}"
|
|
79
|
+
lines += [f" {'UTC offset':<16} {offset_text:<28} " +
|
|
80
|
+
(f'{offset_points:g} / 3' if offset_points is not None else 'not scored')]
|
|
81
|
+
lines += [section('Not measured'),
|
|
82
|
+
' Browser language & Intl locale Browser fonts & vendor fonts',
|
|
83
|
+
' Browser / device identity WebRTC & emoji rendering',
|
|
84
|
+
'',
|
|
85
|
+
' This is a third-party heuristic, not an Anthropic risk score or',
|
|
86
|
+
' ban prediction. It cannot assess IP, billing or account status.',
|
|
87
|
+
' Browser signals need a browser; run claude-access inspect.',
|
|
88
|
+
' To inspect the launcher timezone, run:',
|
|
89
|
+
' TZ=America/Los_Angeles claude-access score', '']
|
|
90
|
+
return '\n'.join(lines)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def main():
|
|
94
|
+
argparse.ArgumentParser(description=__doc__).parse_args()
|
|
95
|
+
print(render_score(collect()))
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
if __name__ == '__main__':
|
|
99
|
+
main()
|