Create a persistent personal viewer on local CLI start.
Replace the fixed /v/local/ default with a reused secret viewer id so local runs get a bookmarkable personal URL like production. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -33,7 +33,7 @@ pip install -r requirements.txt
|
|||||||
python app.py fantasyidler_save.json
|
python app.py fantasyidler_save.json
|
||||||
```
|
```
|
||||||
|
|
||||||
The browser opens automatically at `http://127.0.0.1:5000/v/local/`.
|
The browser opens automatically at your personal viewer URL (e.g. `http://127.0.0.1:5000/v/<id>/`). The id is stored in `data/cli-viewer-id` and reused on the next start.
|
||||||
|
|
||||||
### Docker (host for other players)
|
### Docker (host for other players)
|
||||||
|
|
||||||
@@ -93,7 +93,7 @@ Each viewer has its own SQLite database at `data/viewers/<viewer_id>.db`.
|
|||||||
|
|
||||||
The `viewer_id` is a random URL-safe token. Anyone with the link has access — there is no password and no recovery if the link is lost.
|
The `viewer_id` is a random URL-safe token. Anyone with the link has access — there is no password and no recovery if the link is lost.
|
||||||
|
|
||||||
Local CLI usage defaults to the `local` viewer (`/v/local/`). In Docker/production, `/v/local/` is disabled (`DISABLE_LOCAL_VIEWER=1`).
|
Local CLI usage creates a persistent personal viewer (`data/cli-viewer-id`). Use `--viewer local` for the shared dev viewer at `/v/local/`. In Docker/production, `/v/local/` is disabled (`DISABLE_LOCAL_VIEWER=1`).
|
||||||
|
|
||||||
## Security
|
## Security
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ from viewers import (
|
|||||||
LOCAL_VIEWER_ID,
|
LOCAL_VIEWER_ID,
|
||||||
create_viewer,
|
create_viewer,
|
||||||
ensure_local_viewer,
|
ensure_local_viewer,
|
||||||
|
get_or_create_cli_viewer,
|
||||||
is_valid_viewer_id,
|
is_valid_viewer_id,
|
||||||
viewer_db_path,
|
viewer_db_path,
|
||||||
)
|
)
|
||||||
@@ -193,7 +194,11 @@ def main() -> int:
|
|||||||
parser.add_argument("--host", default="127.0.0.1", help="Bind host (use 0.0.0.0 in Docker)")
|
parser.add_argument("--host", default="127.0.0.1", help="Bind host (use 0.0.0.0 in Docker)")
|
||||||
parser.add_argument("--no-browser", action="store_true")
|
parser.add_argument("--no-browser", action="store_true")
|
||||||
parser.add_argument("--db", type=Path, help="SQLite path (legacy single-file mode)")
|
parser.add_argument("--db", type=Path, help="SQLite path (legacy single-file mode)")
|
||||||
parser.add_argument("--viewer", default=LOCAL_VIEWER_ID, help="Viewer id for CLI (default: local)")
|
parser.add_argument(
|
||||||
|
"--viewer",
|
||||||
|
metavar="ID",
|
||||||
|
help="Viewer id for CLI (default: persistent personal viewer; use 'local' for shared dev viewer)",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
global DATA_DIR, DB_PATH
|
global DATA_DIR, DB_PATH
|
||||||
@@ -204,7 +209,12 @@ def main() -> int:
|
|||||||
db_path = DB_PATH
|
db_path = DB_PATH
|
||||||
viewer_id = None
|
viewer_id = None
|
||||||
else:
|
else:
|
||||||
viewer_id = ensure_local_viewer(DATA_DIR) if args.viewer == LOCAL_VIEWER_ID else args.viewer
|
if args.viewer == LOCAL_VIEWER_ID:
|
||||||
|
viewer_id = ensure_local_viewer(DATA_DIR)
|
||||||
|
elif args.viewer:
|
||||||
|
viewer_id = args.viewer
|
||||||
|
else:
|
||||||
|
viewer_id = get_or_create_cli_viewer(DATA_DIR)
|
||||||
if not is_valid_viewer_id(viewer_id):
|
if not is_valid_viewer_id(viewer_id):
|
||||||
print(f"Error: invalid viewer id: {viewer_id}", file=sys.stderr)
|
print(f"Error: invalid viewer id: {viewer_id}", file=sys.stderr)
|
||||||
return 1
|
return 1
|
||||||
@@ -250,6 +260,8 @@ def main() -> int:
|
|||||||
else:
|
else:
|
||||||
url = f"http://{args.host}:{args.port}/"
|
url = f"http://{args.host}:{args.port}/"
|
||||||
print(f"Starting server at {url}")
|
print(f"Starting server at {url}")
|
||||||
|
if viewer_id and viewer_id != LOCAL_VIEWER_ID:
|
||||||
|
print(f"Personal viewer URL: {url}")
|
||||||
if not args.no_browser and args.host in ("127.0.0.1", "localhost"):
|
if not args.no_browser and args.host in ("127.0.0.1", "localhost"):
|
||||||
webbrowser.open(url)
|
webbrowser.open(url)
|
||||||
app.run(host=args.host, port=args.port, debug=False)
|
app.run(host=args.host, port=args.port, debug=False)
|
||||||
|
|||||||
+19
-1
@@ -53,7 +53,7 @@ def create_viewer(data_dir: Path) -> str:
|
|||||||
|
|
||||||
|
|
||||||
def ensure_local_viewer(data_dir: Path) -> str:
|
def ensure_local_viewer(data_dir: Path) -> str:
|
||||||
"""CLI default viewer – not secret, for local single-user use."""
|
"""Shared dev viewer – predictable path, not secret."""
|
||||||
db_path = viewer_db_path(LOCAL_VIEWER_ID, data_dir)
|
db_path = viewer_db_path(LOCAL_VIEWER_ID, data_dir)
|
||||||
if db_path.exists():
|
if db_path.exists():
|
||||||
return LOCAL_VIEWER_ID
|
return LOCAL_VIEWER_ID
|
||||||
@@ -61,3 +61,21 @@ def ensure_local_viewer(data_dir: Path) -> str:
|
|||||||
init_db(conn)
|
init_db(conn)
|
||||||
conn.close()
|
conn.close()
|
||||||
return LOCAL_VIEWER_ID
|
return LOCAL_VIEWER_ID
|
||||||
|
|
||||||
|
|
||||||
|
def cli_viewer_marker(data_dir: Path) -> Path:
|
||||||
|
return data_dir / "cli-viewer-id"
|
||||||
|
|
||||||
|
|
||||||
|
def get_or_create_cli_viewer(data_dir: Path) -> str:
|
||||||
|
"""Persistent personal viewer for local CLI starts."""
|
||||||
|
data_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
marker = cli_viewer_marker(data_dir)
|
||||||
|
if marker.exists():
|
||||||
|
viewer_id = marker.read_text(encoding="utf-8").strip()
|
||||||
|
if is_valid_viewer_id(viewer_id) and viewer_exists(viewer_id, data_dir):
|
||||||
|
return viewer_id
|
||||||
|
|
||||||
|
viewer_id = create_viewer(data_dir)
|
||||||
|
marker.write_text(viewer_id, encoding="utf-8")
|
||||||
|
return viewer_id
|
||||||
|
|||||||
Reference in New Issue
Block a user