claude-access-for-all 0.1.1 → 0.3.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 CHANGED
@@ -1,4 +1,4 @@
1
- # Claude Access for All
1
+ <h1><img src="docs/images/logo.png" width="36" height="36" align="absmiddle" alt="" />&nbsp; Claude Access for All</h1>
2
2
 
3
3
  **Frontier intelligence should serve humanity, in every corner of the world.**
4
4
 
@@ -35,6 +35,22 @@ Open knowledge can reduce confusion, expose misleading promises, help people pro
35
35
  | Local tools | Offline diagnostics, scoped launchers, browser signal inspector, and calendar reminder |
36
36
  | Evidence | Dated official sources and a contribution standard |
37
37
 
38
+ ## Session location example
39
+
40
+ An example of an account's active-session view showing a reported location:
41
+
42
+ ![Active session showing Los Angeles, California, US as the reported location](docs/images/active-session-example.png)
43
+
44
+ The displayed location is provided by the service. A process timezone setting alone does not change the network exit IP or guarantee a particular session location.
45
+
46
+ ## Claude Pro subscription example
47
+
48
+ An example of the Claude Desktop usage screen after subscribing to Pro:
49
+
50
+ ![Claude Desktop usage settings showing an active Pro subscription](docs/images/pro-subscription-example.png)
51
+
52
+ Plan availability and usage limits depend on your account and the provider's current terms.
53
+
38
54
  ## Install and run
39
55
 
40
56
  Requires macOS, Node.js 18+, Python 3.9+, and Git for the GitHub install. Install Claude Code or Claude Desktop separately when needed. Install the CLI from the npm registry or directly from GitHub.
@@ -52,24 +68,54 @@ npm install -g claude-access-for-all
52
68
  claude-access
53
69
  ```
54
70
 
55
- Choose an action from the interactive menu, or call one directly:
71
+ ## Command reference
72
+
73
+ Run `claude-access` without arguments to open the interactive menu, or use a command directly:
74
+
75
+ | Command | What it does |
76
+ | --- | --- |
77
+ | `claude-access` or `claude-access menu` | Open the interactive menu for diagnostics, launching clients, and reminders. |
78
+ | `claude-access score` | Score the current terminal's timezone and UTC offset using a partial, offline heuristic. Browser-only signals are not measured. |
79
+ | `claude-access doctor` | Check the system, Claude Code installation, and whether common environment variables are set. |
80
+ | `claude-access audit` | Inspect the current terminal's timezone names and whether locale, proxy, endpoint, and authentication variables are set. |
81
+ | `claude-access cli` | Launch Claude Code with process-scoped settings, requesting the Los Angeles timezone by default. |
82
+ | `claude-access app` | Request a new Claude Desktop instance with process-scoped settings and the Los Angeles timezone by default. |
83
+ | `claude-access desktop-shortcut` | Create a standalone macOS launcher in the current directory that requests the same Desktop settings. |
84
+ | `claude-access web` | Open Claude in a separate Chrome profile, requesting the Los Angeles timezone by default; use `--browser edge` for Microsoft Edge. |
85
+ | `claude-access inspect` | Open the local browser signal inspector in the default browser. |
86
+ | `claude-access reminder --renewal YYYY-MM-DD` | Print a one-time renewal-review calendar event. Redirect the output to an `.ics` file and import it into Calendar. |
87
+ | `claude-access guide` | Open the English privacy and access guide. |
88
+ | `claude-access help` | Show the command list and examples. |
89
+ | `claude-access version` | Show the installed package version. |
90
+
91
+ The score is not a complete browser score, a ban prediction, or an eligibility check. Desktop and browsers may not honor the requested timezone. For available options, run a supported subcommand with `--help`, such as `claude-access web --help`.
92
+
93
+ ## Open Claude from the Dock
94
+
95
+ Run `claude-access desktop-shortcut` to create `Claude Access Launcher.app` in the current directory. Quit Claude first, then double-click the launcher. Drag the launcher to the Dock for future launches; after Claude opens, use Command-Tab to switch to it as usual. The launcher is a separate app, and does not replace or modify Claude. An existing Claude instance may reuse state or services; the launcher cannot change an already-running process. It requests the Los Angeles timezone and English locale, but Claude or its services may read system settings independently. No change to network routing or account eligibility is made.
96
+
97
+ Run `claude-access desktop-shortcut --help` to select a different output path, timezone, or locale. The launcher refuses to overwrite an existing path. To remove it, quit it and delete its `.app` bundle.
98
+
99
+ ## Examples
100
+
56
101
 
57
102
  ```sh
58
103
  claude-access doctor
59
104
  claude-access audit
60
- claude-access cli --timezone UTC
61
- claude-access app --timezone UTC
62
- claude-access web --browser chrome --timezone UTC
105
+ claude-access score
106
+ claude-access cli --timezone America/Los_Angeles
107
+ claude-access app --timezone America/Los_Angeles
108
+ claude-access web --browser chrome --timezone America/Los_Angeles
63
109
  claude-access inspect
64
110
  claude-access reminder --renewal 2026-12-01 --days-before 3 > renewal.ics
65
111
  open renewal.ics
66
112
  ```
67
113
 
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. Diagnostics display a readable summary by default; use `claude-access doctor --json` or `claude-access audit --json` for machine-readable output. `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).
114
+ 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
115
 
70
116
  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
117
 
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.
118
+ 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
119
 
74
120
  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
121
 
@@ -11,10 +11,12 @@ const help = `Claude Access for All
11
11
  Usage: claude-access [command] [options]
12
12
 
13
13
  menu Open the interactive menu (default)
14
- doctor [--json] Check local installation and configuration presence
15
- audit [--json] Inspect CLI environment signals locally
14
+ doctor Check local installation and configuration presence
15
+ audit Inspect CLI environment signals locally
16
+ score Score locally observable timezone signals
16
17
  cli [options] [-- ...] Launch Claude Code with scoped locale settings
17
18
  app [options] Launch Claude Desktop with scoped locale settings
19
+ desktop-shortcut Create a standalone macOS launcher app
18
20
  web [options] Launch Claude in a separate browser profile
19
21
  inspect Open the local browser signal inspector
20
22
  reminder [options] Write a one-time calendar event to stdout
@@ -23,8 +25,8 @@ Usage: claude-access [command] [options]
23
25
  version Show the package version
24
26
 
25
27
  Examples:
26
- claude-access cli --timezone UTC -- --version
27
- claude-access web --browser edge --timezone UTC
28
+ claude-access cli --timezone America/Los_Angeles -- --version
29
+ claude-access web --browser edge --timezone America/Los_Angeles
28
30
  claude-access reminder --renewal 2026-12-01 --days-before 3 > renewal.ics
29
31
 
30
32
  Requires macOS, Node.js 18+, and Python 3.9+. Launching Claude requires the
@@ -47,6 +49,8 @@ function dispatch(command, args = []) {
47
49
  switch (command) {
48
50
  case 'doctor': return run('python3', [join(root, 'tools/access.py'), 'doctor', ...args]);
49
51
  case 'audit': return run('python3', [join(root, 'tools/privacy.py'), 'audit', ...args]);
52
+ case 'score': return run('python3', [join(root, 'tools/score.py'), ...args]);
53
+ case 'desktop-shortcut': return run('python3', [join(root, 'tools/desktop_shortcut.py'), ...args]);
50
54
  case 'cli': case 'app': case 'web':
51
55
  return run('python3', [join(root, 'tools/privacy.py'), command, ...args]);
52
56
  case 'reminder': return run('python3', [join(root, 'tools/access.py'), 'reminder', ...args]);
@@ -69,10 +73,10 @@ async function menu() {
69
73
  }
70
74
  const rl = createInterface({ input: process.stdin, output: process.stdout });
71
75
  try {
72
- 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`);
76
+ 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 10 Create a macOS desktop launcher\n 0 Exit\n`);
73
77
  const choice = (await rl.question('Choose an option: ')).trim();
74
78
  if (choice === '0') return 0;
75
- const simple = { '1': 'doctor', '2': 'audit', '3': 'cli', '4': 'app', '6': 'inspect', '7': 'guide' };
79
+ const simple = { '1': 'doctor', '2': 'audit', '3': 'cli', '4': 'app', '6': 'inspect', '7': 'guide', '9': 'score', '10': 'desktop-shortcut' };
76
80
  if (simple[choice]) { rl.close(); return dispatch(simple[choice]); }
77
81
  if (choice === '5') {
78
82
  const browser = (await rl.question('Browser (chrome/edge) [chrome]: ')).trim() || 'chrome';
@@ -93,7 +97,7 @@ async function menu() {
93
97
  console.log(`Created ${output}. Import it into Calendar and check notification settings.`);
94
98
  return 0;
95
99
  }
96
- console.error('Unknown option. Run the menu again to choose 0–8.');
100
+ console.error('Unknown option. Run the menu again to choose 0–10.');
97
101
  return 2;
98
102
  } finally { rl.close(); }
99
103
  }
Binary file
@@ -1,38 +1,42 @@
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. This project does not copy its scoring algorithm or claim that these signals cause a ban.
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 UTC --locale en_US.UTF-8
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 UTC -- --version
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 UTC --locale en_US.UTF-8
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
  ```
27
31
 
28
- This asks macOS Launch Services to open a new app instance with the specified environment values. Desktop may read OS preferences directly, reuse existing services, or use app-managed settings instead; these process overrides are an experiment, not a verified masking mechanism. Browser-based sign-in runs in its own browser process. Verify Desktop-specific behavior separately and use [official network settings](https://code.claude.com/docs/en/network-config) for network configuration.
32
+ This asks macOS Launch Services to open a new app instance with the specified environment values. To create a clickable launcher, run `claude-access desktop-shortcut`, quit Claude, and open the newly created `Claude Access Launcher.app`. Add it to the Dock for later launches; Command-Tab switches to Claude after it is open. Desktop may read OS preferences directly, reuse existing services, or use app-managed settings instead; these process overrides are an experiment, not a verified masking mechanism. Browser-based sign-in runs in its own browser process. Verify Desktop-specific behavior separately and use [official network settings](https://code.claude.com/docs/en/network-config) for network configuration.
29
33
 
30
34
  ## 3. Claude on the web
31
35
 
32
36
  ```sh
33
- python3 tools/privacy.py web --browser chrome --timezone UTC --language en-US
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 UTC --language en-US
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.1.1",
3
+ "version": "0.3.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,7 +2,6 @@
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
@@ -94,15 +93,14 @@ def reminder(renewal, lead, today=None):
94
93
  def main():
95
94
  parser = argparse.ArgumentParser(description=__doc__)
96
95
  commands = parser.add_subparsers(dest='command', required=True)
97
- diagnostic = commands.add_parser('doctor', help='Print an offline environment report.')
98
- diagnostic.add_argument('--json', action='store_true', help='Output machine-readable JSON.')
96
+ commands.add_parser('doctor', help='Print an offline environment report.')
99
97
  calendar = commands.add_parser('reminder', help='Print a one-time .ics calendar to stdout.')
100
98
  calendar.add_argument('--renewal', type=parse_date, required=True)
101
99
  calendar.add_argument('--days-before', type=days_before, default=3)
102
100
  args = parser.parse_args()
103
101
  if args.command == 'doctor':
104
102
  result = doctor()
105
- print(json.dumps(result, indent=2) if args.json else render_doctor(result))
103
+ print(render_doctor(result))
106
104
  else:
107
105
  try:
108
106
  content = reminder(args.renewal, args.days_before)
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env python3
2
+ """Create a standalone macOS app that requests the Claude Access environment."""
3
+ import argparse
4
+ from pathlib import Path
5
+ import plistlib
6
+ import shlex
7
+ import sys
8
+
9
+ from privacy import validate_locale, validate_timezone
10
+
11
+
12
+ def create_bundle(destination, timezone, locale):
13
+ """Create a new app bundle, refusing to overwrite an existing path."""
14
+ destination = Path(destination).expanduser().absolute()
15
+ destination.mkdir(parents=False, exist_ok=False)
16
+ try:
17
+ contents = destination / 'Contents'
18
+ macos = contents / 'MacOS'
19
+ macos.mkdir(parents=True)
20
+ info = {
21
+ 'CFBundleIdentifier': 'org.claudeaccess.launcher',
22
+ 'CFBundleName': 'Claude Access Launcher',
23
+ 'CFBundleDisplayName': 'Claude Access Launcher',
24
+ 'CFBundleExecutable': 'launch-claude',
25
+ 'CFBundlePackageType': 'APPL',
26
+ 'CFBundleShortVersionString': '1.0',
27
+ 'LSMinimumSystemVersion': '13.0',
28
+ 'LSUIElement': True,
29
+ }
30
+ (contents / 'Info.plist').write_bytes(plistlib.dumps(info))
31
+ args = ['/usr/bin/open', '-n', '-a', 'Claude',
32
+ '--env', 'TZ=' + timezone, '--env', 'LANG=' + locale,
33
+ '--env', 'LC_ALL=' + locale]
34
+ script = '#!/bin/sh\n# Launch Claude with explicitly scoped environment requests.\nexec ' + ' '.join(shlex.quote(arg) for arg in args) + '\n'
35
+ executable = macos / 'launch-claude'
36
+ executable.write_text(script, encoding='utf-8')
37
+ executable.chmod(0o755)
38
+ except BaseException:
39
+ # Only remove files created by this call; no pre-existing target is touched.
40
+ import shutil
41
+ shutil.rmtree(destination)
42
+ raise
43
+ return destination
44
+
45
+
46
+ def main():
47
+ parser = argparse.ArgumentParser(description=__doc__)
48
+ parser.add_argument('--output', type=Path, default=Path('Claude Access Launcher.app'),
49
+ help='New .app path; parent must exist. Defaults to the current directory.')
50
+ parser.add_argument('--timezone', type=validate_timezone, default='America/Los_Angeles')
51
+ parser.add_argument('--locale', type=validate_locale, default='en_US.UTF-8')
52
+ args = parser.parse_args()
53
+ if sys.platform != 'darwin':
54
+ parser.error('The launcher requires macOS.')
55
+ if args.output.suffix != '.app':
56
+ parser.error('Output must end in .app.')
57
+ try:
58
+ bundle = create_bundle(args.output, args.timezone, args.locale)
59
+ except FileExistsError:
60
+ parser.error(f'Target already exists: {args.output}. Choose a different --output path.')
61
+ except OSError as error:
62
+ parser.error(str(error))
63
+ print(f'Created: {bundle}')
64
+ print('Quit Claude first, then open the launcher. You can drag it to the Dock.')
65
+ print('Switch to the launched Claude app with Command-Tab.')
66
+ print('Launch Services requests process settings; Claude may use existing services or system settings.')
67
+
68
+
69
+ if __name__ == '__main__':
70
+ main()
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
@@ -67,11 +66,10 @@ def browser_command(browser, profile, language, timezone, locale):
67
66
  def main():
68
67
  parser = argparse.ArgumentParser(description=__doc__)
69
68
  sub = parser.add_subparsers(dest='command', required=True)
70
- diagnostic = sub.add_parser('audit', help='Show local signals without exposing secret values.')
71
- diagnostic.add_argument('--json', action='store_true', help='Output machine-readable JSON.')
69
+ sub.add_parser('audit', help='Show local signals without exposing secret values.')
72
70
  for name in ('cli', 'app', 'web'):
73
71
  command = sub.add_parser(name, help='Launch ' + name + ' with process-scoped settings.')
74
- command.add_argument('--timezone', type=validate_timezone, default='UTC')
72
+ command.add_argument('--timezone', type=validate_timezone, default='America/Los_Angeles')
75
73
  command.add_argument('--locale', type=validate_locale, default='en_US.UTF-8')
76
74
  if name == 'cli':
77
75
  command.add_argument('claude_args', nargs=argparse.REMAINDER)
@@ -84,7 +82,7 @@ def main():
84
82
  args = parser.parse_args()
85
83
  if args.command == 'audit':
86
84
  result = audit()
87
- print(json.dumps(result, indent=2) if args.json else render_audit(result))
85
+ print(render_audit(result))
88
86
  return
89
87
  if sys.platform != 'darwin':
90
88
  parser.error('Launch commands require macOS.')
package/tools/report.py CHANGED
@@ -1,4 +1,4 @@
1
- """Readable, privacy-conscious CLI reports. JSON remains available via --json."""
1
+ """Readable, privacy-conscious CLI reports."""
2
2
  import os
3
3
  import sys
4
4
 
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()