dulus 3.13.6__tar.gz → 3.13.7__tar.gz
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.
- {dulus-3.13.6/dulus.egg-info → dulus-3.13.7}/PKG-INFO +1 -1
- {dulus-3.13.6 → dulus-3.13.7}/docs/CHANGELOG.md +10 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/news.md +4 -0
- {dulus-3.13.6 → dulus-3.13.7/dulus.egg-info}/PKG-INFO +1 -1
- {dulus-3.13.6 → dulus-3.13.7}/dulus.py +478 -38
- {dulus-3.13.6 → dulus-3.13.7}/pyproject.toml +1 -1
- {dulus-3.13.6 → dulus-3.13.7}/LICENSE +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/MANIFEST.in +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/README.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/agent.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/algolia_search.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/analytics.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/agents_bridge.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/compressor.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/context.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/githook.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/marketplace.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/mempalace_bridge.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/paths.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/personas.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/plugins.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/server.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/backend/tasks.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/banners/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/banners/banner_rotator.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/banners/banners_dulus_ai.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/batch_api.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/checkpoint/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/checkpoint/hooks.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/checkpoint/store.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/checkpoint/types.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/__main__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/ansi.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/assets/ascii/dulus_logo.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/assets/ascii/eagle.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/assets/ascii/terminal_art.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/banners.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/demo.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/effects.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/effort_slider.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/progress.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/py.typed +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/spinners.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cli_animations/status.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/clipboard_utils.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/cloudsave.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/common.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/compaction.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/config.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/context.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/active_persona.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/marketplace.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/personas.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/composio_plugin/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/composio_plugin/session_manager.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/composio_plugin/tool_generator.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/plugin.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/plugins/composio/plugin_tool.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/data/tasks.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/display_blocks.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/API.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/BRAND_GUIDE.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/CONTRIBUTING.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/DEPLOYMENT.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/FAQ.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/GETTING_STARTED.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_AR.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_EN.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_ES.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_FR.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_JA.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_KO.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_PT.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_RU.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/README_ZH.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/SECURITY.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/api.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/architecture.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/azure-speech-template.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/dashboard/index.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/divider.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/dulus-bird.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/generate.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/index.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/nvidia-models.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/particle-playground.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/personas/index.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/poetry-banner.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/preview.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/python-console.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/auto-adapter.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/dulus-os.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/hero.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/lookback_two_windows.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/pyc_heap.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/pyc_receipts.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/pyc_trap.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/roundtable.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/task-board.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/readme/webchat.png +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-agents.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-brainstorm.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-bridges.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-features.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-freetier.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-memory.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-models.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-perms.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-plugins.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-quickstart.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/sec-ssj.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/spinners.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/split-pane.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/terminal-boot.svg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/docs/termux-edge.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/SOURCES.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/dependency_links.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/entry_points.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/requires.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus.egg-info/top_level.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_account.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_bar_client.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_gui.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/client.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/commands.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/config.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/hub.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_mcp/types.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_quick_menu.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/add_dir_manager.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/afk_mode.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/approval_runtime.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/background_tasks.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/clipboard_utils.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/diff_visualizer.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/export_import.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/hook_engine.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/notification_manager.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/session_fork.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/shell_mode.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/todo_visualizer.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/tree_ls.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/wire_events.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/dulus_tools/yolo_mode.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/file_filter.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/fs_compass.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/governance.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/agent_bridge.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/chat_widget.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/main_window.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/personas.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/sandbox_server.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/session_utils.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/settings_dialog.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/sidebar.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/tasks_view.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/themes.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/gui/tool_panel.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/input.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/license_manager.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/lookback.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/audit.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/consolidator.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/context.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/mempalace_bridge.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/offload.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/palace.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/scan.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/adaptation_guides.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/askuserquestion_after_numbered_list.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/background_jobs_no_extra_timers.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/cleverness_over_resources.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/corporate_tls_breaks_curl_cffi.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/hardened_rules.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/investigator_workflow_heuristic.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/native_code_over_sketchy_plugins.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/no_personal_info_in_code.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/searches_include_modified_date.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/tmux_send_keys_power_patterns.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/token_optimization_technique.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/tool_chaining_capability.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/insights/webbridge_evaluate_first_pattern.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/seeds/short_memory.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/sessions.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/store.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/types.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/memory/vector_search.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/model_params.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/multi_agent/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/multi_agent/subagent.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/multi_agent/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/paste_placeholders.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/personality_config.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/plugin/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/plugin/autoadapter.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/plugin/loader.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/plugin/recommend.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/plugin/store.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/plugin/types.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/profiles.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/providers.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/pyconsole.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/README.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/assets/index-DE51D6wI.css +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/assets/index-DMCCNE9Y.js +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/index.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpaper-default.jpg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/default.jpeg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/light.jpeg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/nature.jpeg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/dist/wallpapers/tech.jpeg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox/info.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/sandbox_bootstrap.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/setup.cfg +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/builtin.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/LICENSE-obsidian-skills +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/README.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/defuddle/SKILL.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/json-canvas/SKILL.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/json-canvas/references/EXAMPLES.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-bases/SKILL.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-bases/references/FUNCTIONS_REFERENCE.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-cli/SKILL.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/SKILL.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/references/CALLOUTS.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/references/EMBEDS.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/obsidian-markdown/references/PROPERTIES.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/README.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/SKILL.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/addresses.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/advancedbackgroundchecks.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/beenverified.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/clustal.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/clustrmaps.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/cyberbackgroundchecks.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/familytreenow.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/fastpeoplesearch.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/intelius.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/mylife.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/nuwber.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/peekyou.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/peoplefinders.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/radaris.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/rehold.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/searchpeoplefree.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/socialcatfish.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/spokeo.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/thatsthem.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/truepeoplesearch.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/usphonebook.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/brokers/whitepages.json +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/legal/ccpa.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/legal/drop.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/legal/gdpr.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/methods.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/site-playbooks.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/references/state-machine.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/autopilot.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/badbool.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/brokers.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/cdp.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/config.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/crypto.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/dossier.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/email_modes.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/emailer.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/ledger.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/legal.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/paths.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/pdd.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/registry.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/report.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/scan.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/storage.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/tiers.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/scripts/vectors.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/consent/authorization.md +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/ccpa-authorized-agent.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/ccpa-deletion.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/ccpa-indirect-deletion.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/gdpr-erasure.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/bundled/unbroker/templates/emails/generic-optout.txt +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/clawhub.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/executor.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/loader.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skill/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/skills.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/soul.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/spinner.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/steer_input.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/subagent.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/task/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/task/store.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/task/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/task/types.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_afk_yolo.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_approval_runtime.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_background_task_tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_background_tasks.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_chatgpt_oauth.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_checkpoint.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_cli_animations_integration.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_clipboard_utils.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_compaction.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_composio_tool_generator.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_diff_view.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_diff_visualization.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_display_blocks.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_dulus_account.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_export_import.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_gold_memory_preload.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_grok_usage.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_hook_engine.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_injection_fix.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_json_protocol.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_license.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_lookback_cache.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_lookback_compact.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_mcp.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_mcp_algolia.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_memory.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_memory_home_resolution.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_notification_manager.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_plugin.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_profile_isolation.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_profile_plugin_scope.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_provider_errors.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_session_fork.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_shell_detection.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_shell_mode.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_skills.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_skillssh_algolia.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_steer_input.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_subagent.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_task.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_telegram_buffer.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_think_tool.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_todo_tool.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_todo_visualization.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_tool_registry.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_tree_ls.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_voice.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_webbridge_tabs.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tests/test_wire_events.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tmux_tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/token_budget.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tool_registry.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tools_background.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tools_think.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/tools_todo.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/ui/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/ui/input.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/ui/render.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/updater.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/audio_utils.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/keyterms.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/recorder.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/stt.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/tts.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/voice/wake_word.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webbridge/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webbridge/core.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webbridge/tools.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webchat.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webchat_server.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/__init__.py +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/app.js +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/index.html +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/webchat_ui/style.css +0 -0
- {dulus-3.13.6 → dulus-3.13.7}/welcome.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.7
|
|
4
4
|
Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
|
|
5
5
|
Author-email: KevRojo <dev@dulus.ai>
|
|
6
6
|
Maintainer-email: KevRojo <dev@dulus.ai>
|
|
@@ -7,6 +7,16 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [3.13.7] - 2026-09-01
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- **EU/GDPR Gemini consent solver**: pre-seed Google `CONSENT` + `SOCS` cookies; multi-locale Accept clicks across main page and consent iframes so headless EEA boxes (OVH/Hetzner/…) reach the chat composer
|
|
14
|
+
- **`/news` Dulus Radio**: live broadcast console — latest 3 by default, `/news N` / `/news more` / `/news all` (archive opt-in); auto-shows on interactive boot
|
|
15
|
+
- **Boot Fuel nudge**: points at `/fuel`, OpenAI-compatible endpoint `https://control.dulus.ai/v1`, and **Qwen UNCENSORED** drop — no premium/binary/GUI pitch
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- Public boot nag no longer pitches the install binary / Premium GUI / MCP-skills trial wall
|
|
19
|
+
|
|
10
20
|
## [3.13.6] - 2026-08-31
|
|
11
21
|
|
|
12
22
|
### Fixed
|
|
@@ -2,6 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
## 🔥🔥🔥 News (Pacific Time)
|
|
4
4
|
|
|
5
|
+
- September 1, 2026 (**v3.13.7 — 🔥 TODAY — Qwen UNCENSORED on the endpoint**): **Stock Fuel. Now.** Run `/login dulus`, then `/fuel` for balance + a console QR you can scan with Phantom/Solflare. Our router is **OpenAI-compatible** at `https://control.dulus.ai/v1` — drop it into anything that speaks `/v1/chat/completions`. **Today** we put **Qwen UNCENSORED** on that endpoint. Buy `$DULUS`, load Fuel, go. No premium pitch, no binary wall — the open runtime stays free; Fuel pays for hosted compute. `pip install -U dulus`. 🦅🇩🇴⛽
|
|
6
|
+
|
|
7
|
+
- August 31, 2026 (**Community — EU & the world**): **Something is happening — and we’re not slowing down.** **~25,000 pip installs** and climbing. Git clones just ripped: **2,058 clones / 326 unique cloners in 14 days**, with a vertical spike at the end of the month. To our people in **Europe**, this one’s for you. Google’s **GDPR / “Before you continue” consent walls**, cookie banners, and privacy popups used to brick keyless Gemini harvest on EEA and headless EU boxes (OVH, Hetzner, and friends) — no human to click **Accept**, composer never mounts, intercept times out. Dulus now ships a real **popup / consent solver**: pre-seeds Google `CONSENT` + `SOCS` cookies, dismisses EU privacy gates across the main page **and** consent iframes, multi-locale Accept clicks (`Accept all` / `Aceptar todo` / `Tout accepter` / `Alle akzeptieren` / …), so the agent can actually reach the chat. `/news` is now a live **broadcast** (latest 3 by default — no more dumping the whole archive into your console). Same posture as always — **no API key, no credit card, your machine**. Humans built this for humans. Europe, you’re in. Everybody else too. We’ll keep shipping like this. Love you. `pip install -U dulus`. 🦅🇩🇴
|
|
8
|
+
|
|
5
9
|
- August 31, 2026 (**v3.13.6**): **CI quality locked green.** WebChat guards `CONFIG` before pop (pyright clean) and background workers launch Windows-safe without bash-quoted `python -c` — win-3.13 flake gone. Hotfix patch on top of 3.13.5. `pip install -U dulus`. 🦅
|
|
6
10
|
|
|
7
11
|
- August 31, 2026 (**v3.13.5**): **🏆 short_memory stopped vanishing.** Gold memories + soul now live in the **system prompt** every turn (always-on — `/mem_palace off` no longer starves them). WebChat seeds gold for the transcript; the agent/lookback strip those display blobs before the provider call so first-turn consolidate can’t eat your context. Second `/compact` no longer wipes the Loopback archive. Engine + WebChat fix — not a desktop-GUI rewrite. `pip install -U dulus`. 🦅
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.2
|
|
2
2
|
Name: dulus
|
|
3
|
-
Version: 3.13.
|
|
3
|
+
Version: 3.13.7
|
|
4
4
|
Summary: Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS.
|
|
5
5
|
Author-email: KevRojo <dev@dulus.ai>
|
|
6
6
|
Maintainer-email: KevRojo <dev@dulus.ai>
|
|
@@ -395,7 +395,7 @@ try:
|
|
|
395
395
|
from importlib.metadata import version as _pkg_version
|
|
396
396
|
VERSION = _pkg_version("dulus")
|
|
397
397
|
except Exception:
|
|
398
|
-
VERSION = "3.13.
|
|
398
|
+
VERSION = "3.13.7" # dev fallback — keep in sync with pyproject.toml
|
|
399
399
|
|
|
400
400
|
# ── Machine-readable protocol output (`--output json`) ─────────────────────
|
|
401
401
|
# Dulus is increasingly run as a child process by another agent runtime (an
|
|
@@ -1659,12 +1659,12 @@ def _theme_accent(config: dict):
|
|
|
1659
1659
|
|
|
1660
1660
|
|
|
1661
1661
|
def _print_login_nag(config: dict) -> None:
|
|
1662
|
-
"""
|
|
1662
|
+
"""Boot nudge: /fuel + OpenAI-compatible endpoint + live drops.
|
|
1663
1663
|
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1664
|
+
No premium / binary / GUI pitch — public stays free. Points people at
|
|
1665
|
+
`/fuel`, the OpenAI-compatible control plane, and today's model drop.
|
|
1666
|
+
Best-effort — never breaks boot. Still skips when already signed in
|
|
1667
|
+
(they already know the path).
|
|
1668
1668
|
"""
|
|
1669
1669
|
try:
|
|
1670
1670
|
import dulus_account
|
|
@@ -1672,31 +1672,28 @@ def _print_login_nag(config: dict) -> None:
|
|
|
1672
1672
|
if store.get("access_token") and not dulus_account._token_expired(store):
|
|
1673
1673
|
return # signed in — no nag
|
|
1674
1674
|
except Exception:
|
|
1675
|
-
pass # can't tell → show it
|
|
1675
|
+
pass # can't tell → show it
|
|
1676
1676
|
try:
|
|
1677
1677
|
accent = _theme_accent(config) or "cyan"
|
|
1678
1678
|
except Exception:
|
|
1679
1679
|
accent = "cyan"
|
|
1680
1680
|
try:
|
|
1681
|
-
# Import gradient effect for URL
|
|
1682
1681
|
from cli_animations.ansi import gradient_text, ORANGE, CYAN
|
|
1683
|
-
# Create gradient URL
|
|
1684
1682
|
dx_url = "https://tinyurl.com/DulusDexScreener"
|
|
1685
1683
|
styled_dx_url = gradient_text(dx_url, ORANGE, CYAN)
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
+
|
|
1693
|
-
+
|
|
1694
|
-
|
|
1695
|
-
print(
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
print(clr("
|
|
1699
|
-
print(clr(" 2,000 MCPS + 60,000 skills!", "green"))
|
|
1684
|
+
api_url = gradient_text("https://control.dulus.ai/v1", ORANGE, CYAN)
|
|
1685
|
+
|
|
1686
|
+
# Fuel + OpenAI-compatible + today's uncensored drop. No premium pitch.
|
|
1687
|
+
print(clr(" ⛽ Fuel is LIVE — buy $DULUS, load Fuel, run the router.", accent))
|
|
1688
|
+
print(" " + clr("/login dulus", "cyan", "bold")
|
|
1689
|
+
+ " → free account · "
|
|
1690
|
+
+ clr("/fuel", "cyan", "bold")
|
|
1691
|
+
+ " → balance + reload QR (Phantom/Solflare)")
|
|
1692
|
+
print(" OpenAI-compatible endpoint → " + api_url)
|
|
1693
|
+
print(" " + clr("TODAY", "yellow", "bold")
|
|
1694
|
+
+ ": " + clr("Qwen UNCENSORED", "cyan", "bold")
|
|
1695
|
+
+ " drops on our endpoint. Stock Fuel now.")
|
|
1696
|
+
print(clr(" Local & web models keep working without it.", "dim"))
|
|
1700
1697
|
print()
|
|
1701
1698
|
print(" Buy $DULUS now:")
|
|
1702
1699
|
print(f" {styled_dx_url}")
|
|
@@ -3895,6 +3892,238 @@ def _launch_harvest_browser(p, pw_profile, headless):
|
|
|
3895
3892
|
return p.chromium.launch_persistent_context(**common)
|
|
3896
3893
|
|
|
3897
3894
|
|
|
3895
|
+
|
|
3896
|
+
def _seed_google_consent_cookies(context) -> None:
|
|
3897
|
+
"""Pre-seed Google CONSENT/SOCS cookies so EEA walls often never appear.
|
|
3898
|
+
|
|
3899
|
+
Must run on the browser context BEFORE the first navigation to
|
|
3900
|
+
gemini.google.com. Safe no-op if the context rejects the cookies.
|
|
3901
|
+
"""
|
|
3902
|
+
try:
|
|
3903
|
+
seed = []
|
|
3904
|
+
for domain in (".google.com", ".gemini.google.com", ".youtube.com"):
|
|
3905
|
+
seed.append({
|
|
3906
|
+
"name": "CONSENT", "value": "YES+cb.20210328-17-p0.en+FX+710",
|
|
3907
|
+
"domain": domain, "path": "/",
|
|
3908
|
+
})
|
|
3909
|
+
seed.append({
|
|
3910
|
+
"name": "SOCS",
|
|
3911
|
+
"value": "CAESHAgBEhJnd3NfMjAyNTA4MzEtMF9SQzIaAmVuIAEaBgiA_LWmBg",
|
|
3912
|
+
"domain": domain, "path": "/",
|
|
3913
|
+
})
|
|
3914
|
+
context.add_cookies(seed)
|
|
3915
|
+
except Exception:
|
|
3916
|
+
pass
|
|
3917
|
+
|
|
3918
|
+
|
|
3919
|
+
def _dismiss_eu_privacy_gates(page, log=None) -> bool:
|
|
3920
|
+
"""Click through EU/GDPR consent walls that block Gemini on headless EU IPs.
|
|
3921
|
+
|
|
3922
|
+
Google (and gemini.google.com) show a "Before you continue" / cookie consent
|
|
3923
|
+
dialog for EEA users. On a headless OVH/Hetzner/etc box there is no human to
|
|
3924
|
+
press Accept — the chat composer never mounts, and the intercept times out.
|
|
3925
|
+
|
|
3926
|
+
Strategy (best-effort, multi-locale, multi-frame):
|
|
3927
|
+
1. Seed CONSENT cookies so some Google properties skip the wall.
|
|
3928
|
+
2. Click known Accept selectors on the main page + every consent iframe.
|
|
3929
|
+
3. JS sweep: any button whose label matches Accept/Aceptar/Tout accepter/…
|
|
3930
|
+
Returns True if something was clicked or cookies were seeded.
|
|
3931
|
+
"""
|
|
3932
|
+
def _log(msg: str) -> None:
|
|
3933
|
+
if log:
|
|
3934
|
+
try:
|
|
3935
|
+
log(msg)
|
|
3936
|
+
except Exception:
|
|
3937
|
+
pass
|
|
3938
|
+
|
|
3939
|
+
clicked = False
|
|
3940
|
+
|
|
3941
|
+
# ── 1) Seed consent cookies (domain-wide) ──────────────────────────────
|
|
3942
|
+
# CONSENT=YES+… is the classic "already accepted" signal Google still
|
|
3943
|
+
# honours on many properties. SOCS is the newer companion cookie; a
|
|
3944
|
+
# permissive value stops the SOCS interstitial on recent Google UIs.
|
|
3945
|
+
try:
|
|
3946
|
+
ctx = page.context
|
|
3947
|
+
seed = []
|
|
3948
|
+
for domain in (".google.com", ".gemini.google.com", ".youtube.com"):
|
|
3949
|
+
seed.append({
|
|
3950
|
+
"name": "CONSENT", "value": "YES+cb.20210328-17-p0.en+FX+710",
|
|
3951
|
+
"domain": domain, "path": "/",
|
|
3952
|
+
})
|
|
3953
|
+
seed.append({
|
|
3954
|
+
"name": "SOCS",
|
|
3955
|
+
"value": "CAESHAgBEhJnd3NfMjAyNTA4MzEtMF9SQzIaAmVuIAEaBgiA_LWmBg",
|
|
3956
|
+
"domain": domain, "path": "/",
|
|
3957
|
+
})
|
|
3958
|
+
ctx.add_cookies(seed)
|
|
3959
|
+
clicked = True # cookies count as progress even if UI still shows
|
|
3960
|
+
except Exception:
|
|
3961
|
+
pass
|
|
3962
|
+
|
|
3963
|
+
# Labels Google actually paints on the Accept button across EEA locales.
|
|
3964
|
+
# Keep these short + case-insensitive; we match as substring on button text.
|
|
3965
|
+
_ACCEPT_LABELS = (
|
|
3966
|
+
"accept all", "i agree", "agree", "accept", "got it", "continue",
|
|
3967
|
+
"aceptar todo", "acepto", "aceptar", "de acuerdo", "continuar",
|
|
3968
|
+
"tout accepter", "j'accepte", "accepter", "tout autoriser",
|
|
3969
|
+
"alle akzeptieren", "ich stimme zu", "akzeptieren", "zustimmen",
|
|
3970
|
+
"accetta tutto", "accetto", "accetta",
|
|
3971
|
+
"aceitar tudo", "aceito", "aceitar",
|
|
3972
|
+
"alles accepteren", "akkoord",
|
|
3973
|
+
"zaakceptuj wszystko", "przyjmuję",
|
|
3974
|
+
"přijmout vše", "přijmout",
|
|
3975
|
+
"acceptați tot", "accept all cookies",
|
|
3976
|
+
"alle ablehnen", # never — we only click accept-side below
|
|
3977
|
+
)
|
|
3978
|
+
# Only the ACCEPT side — never "Reject all" / "Reject".
|
|
3979
|
+
_ACCEPT_ONLY = tuple(
|
|
3980
|
+
t for t in _ACCEPT_LABELS
|
|
3981
|
+
if not t.startswith("alle ablehnen") and "reject" not in t and "ablehnen" not in t
|
|
3982
|
+
and "rechazar" not in t and "refuser" not in t
|
|
3983
|
+
)
|
|
3984
|
+
|
|
3985
|
+
_CSS_SELECTORS = (
|
|
3986
|
+
"#L2AGLb", # classic Google "I agree"
|
|
3987
|
+
"button#L2AGLb",
|
|
3988
|
+
"button[aria-label*='Accept' i]",
|
|
3989
|
+
"button[aria-label*='Aceptar' i]",
|
|
3990
|
+
"button[aria-label*='Agree' i]",
|
|
3991
|
+
"button[aria-label*='Akzeptieren' i]",
|
|
3992
|
+
"button[aria-label*='Accetta' i]",
|
|
3993
|
+
"button[aria-label*='Accepter' i]",
|
|
3994
|
+
"form[action*='consent'] button",
|
|
3995
|
+
"div[role='dialog'] button",
|
|
3996
|
+
"[data-testid='accept-button']",
|
|
3997
|
+
"button.tHlp3d", # Google consent CSS class (varies)
|
|
3998
|
+
)
|
|
3999
|
+
|
|
4000
|
+
def _try_click_in(target) -> bool:
|
|
4001
|
+
"""Click Accept inside a Page or Frame. Returns True on success."""
|
|
4002
|
+
# CSS selectors first (fast path)
|
|
4003
|
+
for sel in _CSS_SELECTORS:
|
|
4004
|
+
try:
|
|
4005
|
+
loc = target.locator(sel).first
|
|
4006
|
+
if loc.count() > 0 and loc.is_visible(timeout=400):
|
|
4007
|
+
txt = ""
|
|
4008
|
+
try:
|
|
4009
|
+
txt = (loc.inner_text(timeout=300) or "").strip().lower()
|
|
4010
|
+
except Exception:
|
|
4011
|
+
pass
|
|
4012
|
+
# Skip obvious Reject buttons if a selector was too broad
|
|
4013
|
+
if any(bad in txt for bad in ("reject", "rechazar", "refuser", "ablehnen", "rifiuta", "recusar")):
|
|
4014
|
+
continue
|
|
4015
|
+
loc.click(timeout=2000)
|
|
4016
|
+
return True
|
|
4017
|
+
except Exception:
|
|
4018
|
+
continue
|
|
4019
|
+
|
|
4020
|
+
# Text / aria-label sweep via JS (covers locale variants + shadow-ish UIs)
|
|
4021
|
+
try:
|
|
4022
|
+
labels = list(_ACCEPT_ONLY)
|
|
4023
|
+
hit = target.evaluate(
|
|
4024
|
+
r"""(labels) => {
|
|
4025
|
+
const bad = /reject|rechazar|refuser|ablehnen|rifiuta|recusar|nie\s+akcept/i;
|
|
4026
|
+
const nodes = [
|
|
4027
|
+
...document.querySelectorAll('button, [role="button"], input[type="submit"], input[type="button"]')
|
|
4028
|
+
];
|
|
4029
|
+
for (const el of nodes) {
|
|
4030
|
+
const t = (
|
|
4031
|
+
(el.innerText || el.textContent || el.value || '') + ' ' +
|
|
4032
|
+
(el.getAttribute('aria-label') || '') + ' ' +
|
|
4033
|
+
(el.getAttribute('data-mdc-dialog-action') || '')
|
|
4034
|
+
).replace(/\s+/g, ' ').trim().toLowerCase();
|
|
4035
|
+
if (!t || bad.test(t)) continue;
|
|
4036
|
+
for (const lab of labels) {
|
|
4037
|
+
if (t === lab || t.includes(lab)) {
|
|
4038
|
+
el.click();
|
|
4039
|
+
return t.slice(0, 60);
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
}
|
|
4043
|
+
const el = document.querySelector('#L2AGLb');
|
|
4044
|
+
if (el) { el.click(); return 'L2AGLb'; }
|
|
4045
|
+
return null;
|
|
4046
|
+
}""",
|
|
4047
|
+
labels,
|
|
4048
|
+
)
|
|
4049
|
+
if hit:
|
|
4050
|
+
return True
|
|
4051
|
+
except Exception:
|
|
4052
|
+
pass
|
|
4053
|
+
return False
|
|
4054
|
+
|
|
4055
|
+
# ── 2) Main page + consent iframes, a few rounds (dialog can re-paint) ──
|
|
4056
|
+
for round_i in range(4):
|
|
4057
|
+
try:
|
|
4058
|
+
page.wait_for_timeout(600 if round_i == 0 else 900)
|
|
4059
|
+
except Exception:
|
|
4060
|
+
pass
|
|
4061
|
+
|
|
4062
|
+
# Main document
|
|
4063
|
+
if _try_click_in(page):
|
|
4064
|
+
clicked = True
|
|
4065
|
+
_log(f" EU consent dismissed (page, round {round_i + 1})")
|
|
4066
|
+
try:
|
|
4067
|
+
page.wait_for_timeout(800)
|
|
4068
|
+
except Exception:
|
|
4069
|
+
pass
|
|
4070
|
+
|
|
4071
|
+
# Iframes (consent.google.com often lives here)
|
|
4072
|
+
try:
|
|
4073
|
+
frames = list(page.frames)
|
|
4074
|
+
except Exception:
|
|
4075
|
+
frames = []
|
|
4076
|
+
for fr in frames:
|
|
4077
|
+
try:
|
|
4078
|
+
fu = (fr.url or "").lower()
|
|
4079
|
+
except Exception:
|
|
4080
|
+
fu = ""
|
|
4081
|
+
# Always try consent frames; also try unnamed frames on first rounds
|
|
4082
|
+
if "consent" in fu or "accounts.google" in fu or round_i == 0:
|
|
4083
|
+
if _try_click_in(fr):
|
|
4084
|
+
clicked = True
|
|
4085
|
+
_log(f" EU consent dismissed (iframe, round {round_i + 1})")
|
|
4086
|
+
try:
|
|
4087
|
+
page.wait_for_timeout(800)
|
|
4088
|
+
except Exception:
|
|
4089
|
+
pass
|
|
4090
|
+
|
|
4091
|
+
# If we landed ON consent.google.com as top-level, Accept then wait for redirect back
|
|
4092
|
+
try:
|
|
4093
|
+
u = (page.url or "").lower()
|
|
4094
|
+
except Exception:
|
|
4095
|
+
u = ""
|
|
4096
|
+
if "consent.google" in u or "consent.youtube" in u:
|
|
4097
|
+
if _try_click_in(page):
|
|
4098
|
+
clicked = True
|
|
4099
|
+
_log(" EU consent page accepted — waiting for redirect...")
|
|
4100
|
+
try:
|
|
4101
|
+
page.wait_for_url("**gemini.google.com**", timeout=15000)
|
|
4102
|
+
except Exception:
|
|
4103
|
+
try:
|
|
4104
|
+
page.wait_for_timeout(2000)
|
|
4105
|
+
except Exception:
|
|
4106
|
+
pass
|
|
4107
|
+
|
|
4108
|
+
# Stop early once the Gemini composer is reachable (consent is gone)
|
|
4109
|
+
try:
|
|
4110
|
+
for sel in (
|
|
4111
|
+
"rich-textarea .ql-editor",
|
|
4112
|
+
"div.ql-editor[contenteditable='true']",
|
|
4113
|
+
"div[contenteditable='true']",
|
|
4114
|
+
"textarea",
|
|
4115
|
+
):
|
|
4116
|
+
loc = page.locator(sel).first
|
|
4117
|
+
if loc.count() > 0 and loc.is_visible(timeout=300):
|
|
4118
|
+
return True
|
|
4119
|
+
except Exception:
|
|
4120
|
+
pass
|
|
4121
|
+
|
|
4122
|
+
if clicked:
|
|
4123
|
+
_log(" EU privacy gates handled (cookies and/or Accept click)")
|
|
4124
|
+
return clicked
|
|
4125
|
+
|
|
4126
|
+
|
|
3898
4127
|
def cmd_harvest_gemini(_args: str, _state, config) -> "bool | None":
|
|
3899
4128
|
"""Harvest fresh session data from gemini.google.com using Playwright.
|
|
3900
4129
|
|
|
@@ -3934,6 +4163,10 @@ def cmd_harvest_gemini(_args: str, _state, config) -> "bool | None":
|
|
|
3934
4163
|
browser = _launch_harvest_browser(p, pw_profile, headless)
|
|
3935
4164
|
|
|
3936
4165
|
page = browser.pages[0] if browser.pages else browser.new_page()
|
|
4166
|
+
try:
|
|
4167
|
+
_seed_google_consent_cookies(page.context)
|
|
4168
|
+
except Exception:
|
|
4169
|
+
pass
|
|
3937
4170
|
|
|
3938
4171
|
intercepted = []
|
|
3939
4172
|
|
|
@@ -3962,6 +4195,14 @@ def cmd_harvest_gemini(_args: str, _state, config) -> "bool | None":
|
|
|
3962
4195
|
except Exception:
|
|
3963
4196
|
pass
|
|
3964
4197
|
|
|
4198
|
+
# EU/EEA GDPR wall: without this, headless servers in Europe hang
|
|
4199
|
+
# forever because the Accept button never gets pressed and the
|
|
4200
|
+
# Gemini composer never mounts.
|
|
4201
|
+
try:
|
|
4202
|
+
_dismiss_eu_privacy_gates(page, log=info)
|
|
4203
|
+
except Exception as _cg:
|
|
4204
|
+
warn(f" Consent dismiss skipped: {_cg}")
|
|
4205
|
+
|
|
3965
4206
|
# Auto-send "DULUS" so headless servers / VMs don't need a human in the browser.
|
|
3966
4207
|
info("Sending 'DULUS' to Gemini chat (headless auto-send)...")
|
|
3967
4208
|
auto_send_ok = False
|
|
@@ -10172,24 +10413,215 @@ def cmd_update(args: str, state, config) -> bool:
|
|
|
10172
10413
|
return True
|
|
10173
10414
|
|
|
10174
10415
|
|
|
10175
|
-
def
|
|
10176
|
-
"""
|
|
10416
|
+
def _load_news_entries() -> list[str]:
|
|
10417
|
+
"""Parse docs/news.md into ordered dispatch strings (newest first)."""
|
|
10177
10418
|
news_file = Path(__file__).parent / "docs" / "news.md"
|
|
10178
10419
|
if not news_file.exists():
|
|
10179
|
-
|
|
10180
|
-
return True
|
|
10181
|
-
|
|
10420
|
+
return []
|
|
10182
10421
|
try:
|
|
10183
10422
|
content = news_file.read_text(encoding="utf-8")
|
|
10423
|
+
except Exception:
|
|
10424
|
+
return []
|
|
10425
|
+
entries: list[str] = []
|
|
10426
|
+
buf: list[str] = []
|
|
10427
|
+
for line in content.splitlines():
|
|
10428
|
+
if line.startswith("- "):
|
|
10429
|
+
if buf:
|
|
10430
|
+
entries.append("\n".join(buf).strip())
|
|
10431
|
+
buf = [line]
|
|
10432
|
+
elif buf:
|
|
10433
|
+
if line.startswith("#"):
|
|
10434
|
+
entries.append("\n".join(buf).strip())
|
|
10435
|
+
buf = []
|
|
10436
|
+
else:
|
|
10437
|
+
buf.append(line)
|
|
10438
|
+
if buf:
|
|
10439
|
+
entries.append("\n".join(buf).strip())
|
|
10440
|
+
return entries
|
|
10441
|
+
|
|
10442
|
+
|
|
10443
|
+
def _news_strip_md(s: str) -> str:
|
|
10444
|
+
s = s.replace("**", "").replace("__", "")
|
|
10445
|
+
s = s.replace("`", "")
|
|
10446
|
+
s = re.sub(r"\[([^\]]+)\]\([^)]+\)", r"\1", s)
|
|
10447
|
+
return re.sub(r"\s+", " ", s).strip()
|
|
10448
|
+
|
|
10449
|
+
|
|
10450
|
+
def _news_parse_entry(entry: str) -> tuple[str, str, str, str]:
|
|
10451
|
+
"""Return (date, tag, headline, body) from a news bullet."""
|
|
10452
|
+
text = entry[2:].strip() if entry.startswith("- ") else entry.strip()
|
|
10453
|
+
text = text.replace("\n", " ")
|
|
10454
|
+
date, tag, headline, body = "", "", "", text
|
|
10455
|
+
m = re.match(
|
|
10456
|
+
r"^(?P<date>.+?)\s*(?:\(\*\*(?P<tag>.*?)\*\*\)|\((?P<tag2>[^)]+)\))?\s*:\s*(?P<rest>.*)$",
|
|
10457
|
+
text,
|
|
10458
|
+
)
|
|
10459
|
+
if m:
|
|
10460
|
+
date = (m.group("date") or "").strip()
|
|
10461
|
+
tag = (m.group("tag") or m.group("tag2") or "").strip()
|
|
10462
|
+
rest = (m.group("rest") or "").strip()
|
|
10463
|
+
hm = re.match(r"^\*\*(?P<head>.*?)\*\*\s*(?P<body>.*)$", rest)
|
|
10464
|
+
if hm:
|
|
10465
|
+
headline = hm.group("head").strip().rstrip(".")
|
|
10466
|
+
body = hm.group("body").strip()
|
|
10467
|
+
else:
|
|
10468
|
+
parts = re.split(r"(?<=[.!?])\s+", rest, maxsplit=1)
|
|
10469
|
+
headline = parts[0].strip().rstrip(".")
|
|
10470
|
+
body = parts[1].strip() if len(parts) > 1 else ""
|
|
10471
|
+
else:
|
|
10472
|
+
headline = text[:80]
|
|
10473
|
+
body = text[80:]
|
|
10474
|
+
return (
|
|
10475
|
+
_news_strip_md(date),
|
|
10476
|
+
_news_strip_md(tag),
|
|
10477
|
+
_news_strip_md(headline),
|
|
10478
|
+
_news_strip_md(body),
|
|
10479
|
+
)
|
|
10480
|
+
|
|
10481
|
+
|
|
10482
|
+
def _news_wrap(text: str, width: int = 72, max_chars: int = 360) -> list[str]:
|
|
10483
|
+
text = (text or "").strip()
|
|
10484
|
+
if not text:
|
|
10485
|
+
return []
|
|
10486
|
+
if len(text) > max_chars:
|
|
10487
|
+
cut = text[: max_chars - 1]
|
|
10488
|
+
sp = cut.rfind(" ")
|
|
10489
|
+
if sp > max_chars // 2:
|
|
10490
|
+
cut = cut[:sp]
|
|
10491
|
+
text = cut.rstrip(".,;:") + "…"
|
|
10492
|
+
words = text.split()
|
|
10493
|
+
out: list[str] = []
|
|
10494
|
+
cur = ""
|
|
10495
|
+
for w in words:
|
|
10496
|
+
trial = (cur + " " + w).strip()
|
|
10497
|
+
if len(trial) > width and cur:
|
|
10498
|
+
out.append(cur)
|
|
10499
|
+
cur = w
|
|
10500
|
+
else:
|
|
10501
|
+
cur = trial
|
|
10502
|
+
if cur:
|
|
10503
|
+
out.append(cur)
|
|
10504
|
+
return out
|
|
10505
|
+
|
|
10506
|
+
|
|
10507
|
+
def _print_news_broadcast(
|
|
10508
|
+
limit: int = 3,
|
|
10509
|
+
*,
|
|
10510
|
+
boot: bool = False,
|
|
10511
|
+
quiet_footer: bool = False,
|
|
10512
|
+
) -> bool:
|
|
10513
|
+
"""Radio-format Dulus dispatch board. Used by /news and interactive boot.
|
|
10514
|
+
|
|
10515
|
+
boot=True → slightly tighter body (ON AIR teaser, still 3 tracks).
|
|
10516
|
+
"""
|
|
10517
|
+
entries = _load_news_entries()
|
|
10518
|
+
if not entries:
|
|
10519
|
+
if not boot:
|
|
10520
|
+
err("News file not found or empty.")
|
|
10521
|
+
return False
|
|
10522
|
+
|
|
10523
|
+
limit = max(1, min(30, int(limit or 3)))
|
|
10524
|
+
picked = entries[:limit]
|
|
10525
|
+
total = len(entries)
|
|
10526
|
+
# Boot stays punchy; manual /news lets the body breathe a bit more.
|
|
10527
|
+
body_cap = 220 if boot else 420
|
|
10528
|
+
width = 70 if boot else 74
|
|
10529
|
+
|
|
10530
|
+
rule = "━" * 48
|
|
10531
|
+
print()
|
|
10532
|
+
print(clr(f" ◆{rule}◆", "cyan", "dim"))
|
|
10533
|
+
print(
|
|
10534
|
+
clr(" 📡 DULUS RADIO", "cyan", "bold")
|
|
10535
|
+
+ clr(" · ON AIR", "green", "bold")
|
|
10536
|
+
+ clr(f" · {len(picked)}/{total}", "dim")
|
|
10537
|
+
)
|
|
10538
|
+
print(clr(f" ◆{rule}◆", "cyan", "dim"))
|
|
10539
|
+
print()
|
|
10540
|
+
|
|
10541
|
+
for idx, entry in enumerate(picked, 1):
|
|
10542
|
+
date, tag, headline, body = _news_parse_entry(entry)
|
|
10543
|
+
label = "NOW" if idx == 1 else "WAS"
|
|
10544
|
+
tag_bit = tag if tag else "dispatch"
|
|
10545
|
+
# ♪ NOW · Aug 31 · Community — EU & the world
|
|
10546
|
+
meta = f" ♪ {label} · {date}"
|
|
10547
|
+
if tag_bit:
|
|
10548
|
+
meta += f" · {tag_bit}"
|
|
10549
|
+
print(clr(meta, "green", "bold"))
|
|
10550
|
+
if headline:
|
|
10551
|
+
print(clr(f" » {headline}", "white", "bold"))
|
|
10552
|
+
for bl in _news_wrap(body, width=width, max_chars=body_cap):
|
|
10553
|
+
print(clr(f" {bl}", "dim"))
|
|
10554
|
+
if idx < len(picked):
|
|
10555
|
+
print(clr(" · · · · · · · · · · · · · · · · · · · · · ·", "dim"))
|
|
10556
|
+
|
|
10557
|
+
print()
|
|
10558
|
+
print(clr(f" ◆{rule}◆", "cyan", "dim"))
|
|
10559
|
+
if not quiet_footer:
|
|
10560
|
+
if boot:
|
|
10561
|
+
print(
|
|
10562
|
+
clr(" stay tuned", "dim")
|
|
10563
|
+
+ clr(" · ", "dim")
|
|
10564
|
+
+ clr("/news", "cyan")
|
|
10565
|
+
+ clr(" more · humans only · ", "dim")
|
|
10566
|
+
+ clr("pip install -U dulus", "cyan")
|
|
10567
|
+
+ clr(" 🦅", "dim")
|
|
10568
|
+
)
|
|
10569
|
+
else:
|
|
10570
|
+
print(
|
|
10571
|
+
clr(" /news", "cyan")
|
|
10572
|
+
+ clr(" · ", "dim")
|
|
10573
|
+
+ clr("/news 5", "cyan")
|
|
10574
|
+
+ clr(" · ", "dim")
|
|
10575
|
+
+ clr("/news more", "cyan")
|
|
10576
|
+
+ clr(" · ", "dim")
|
|
10577
|
+
+ clr("/news all", "yellow")
|
|
10578
|
+
+ clr(" · humans only 🦅", "dim")
|
|
10579
|
+
)
|
|
10580
|
+
print()
|
|
10581
|
+
return True
|
|
10582
|
+
|
|
10583
|
+
|
|
10584
|
+
def cmd_news(args: str, state, config) -> bool:
|
|
10585
|
+
"""Dulus Radio — latest dispatches from docs/news.md.
|
|
10586
|
+
|
|
10587
|
+
/news — latest 3 (radio console) [also auto on boot]
|
|
10588
|
+
/news 5 — latest N (1–30)
|
|
10589
|
+
/news all — full archive (opt-in; can be huge)
|
|
10590
|
+
/news more — latest 10
|
|
10591
|
+
"""
|
|
10592
|
+
raw_arg = (args or "").strip().lower()
|
|
10593
|
+
show_all = raw_arg in ("all", "full", "archive", "everything", "*")
|
|
10594
|
+
if raw_arg in ("more", "long"):
|
|
10595
|
+
limit = 10
|
|
10596
|
+
elif raw_arg.isdigit():
|
|
10597
|
+
limit = max(1, min(30, int(raw_arg)))
|
|
10598
|
+
else:
|
|
10599
|
+
limit = 3 # default radio: top 3 :)
|
|
10600
|
+
|
|
10601
|
+
if show_all:
|
|
10602
|
+
news_file = Path(__file__).parent / "docs" / "news.md"
|
|
10603
|
+
if not news_file.exists():
|
|
10604
|
+
err("News file not found.")
|
|
10605
|
+
return True
|
|
10606
|
+
try:
|
|
10607
|
+
content = news_file.read_text(encoding="utf-8")
|
|
10608
|
+
except Exception as e:
|
|
10609
|
+
err(f"Failed to read news: {e}")
|
|
10610
|
+
return True
|
|
10184
10611
|
if _RICH:
|
|
10185
|
-
|
|
10186
|
-
|
|
10187
|
-
|
|
10188
|
-
|
|
10612
|
+
try:
|
|
10613
|
+
from rich.console import Console
|
|
10614
|
+
from rich.markdown import Markdown
|
|
10615
|
+
Console().print(Markdown(content))
|
|
10616
|
+
except Exception:
|
|
10617
|
+
print(content)
|
|
10189
10618
|
else:
|
|
10190
10619
|
print(content)
|
|
10191
|
-
|
|
10192
|
-
|
|
10620
|
+
print()
|
|
10621
|
+
print(clr(" (full archive) · /news for Dulus Radio", "dim"))
|
|
10622
|
+
return True
|
|
10623
|
+
|
|
10624
|
+
_print_news_broadcast(limit, boot=False)
|
|
10193
10625
|
return True
|
|
10194
10626
|
|
|
10195
10627
|
|
|
@@ -11799,7 +12231,7 @@ _CMD_META: dict[str, tuple[str, list[str]]] = {
|
|
|
11799
12231
|
"quit": ("Exit (alias for /exit)", []),
|
|
11800
12232
|
"resume": ("Resume last session", []),
|
|
11801
12233
|
"update": ("Self-update Dulus from PyPI", ["now", "check", "status", "on", "off"]),
|
|
11802
|
-
"news": ("
|
|
12234
|
+
"news": ("Dulus Radio — latest 3 (also auto on boot)", ["all", "more", "5"]),
|
|
11803
12235
|
"claude_chats": ("List Claude.ai conversations", ["all"]),
|
|
11804
12236
|
"gemini_chats": ("Manage Gemini Web conversations", ["new"]),
|
|
11805
12237
|
"gemini_harvest": ("Harvest Gemini Web cookies (alias)", []),
|
|
@@ -12340,7 +12772,7 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
12340
12772
|
"⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠉⠛⠻⢾⣯⣧⣬⣭⣤⡶⠖⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀",
|
|
12341
12773
|
]
|
|
12342
12774
|
_DULUS_LOGO.append(" " + clr("v" + VERSION, "green", "bold"))
|
|
12343
|
-
_DULUS_LOGO.append(" " + clr("
|
|
12775
|
+
_DULUS_LOGO.append(" " + clr("📡 Dulus Radio is ON AIR at boot — EU consent solver live", "cyan", "dim"))
|
|
12344
12776
|
_DULUS_LOGO.append(" ")
|
|
12345
12777
|
|
|
12346
12778
|
# Shared Dulus animation language for the short boot beat. Redirected
|
|
@@ -12424,13 +12856,21 @@ def repl(config: dict, initial_prompt: str | None = None):
|
|
|
12424
12856
|
print(msg)
|
|
12425
12857
|
print()
|
|
12426
12858
|
|
|
12427
|
-
#
|
|
12428
|
-
#
|
|
12859
|
+
# Fuel + OpenAI-compat + live drop nudge (until signed in).
|
|
12860
|
+
# Premium / binary / GUI pitch intentionally removed — public stays free.
|
|
12429
12861
|
try:
|
|
12430
12862
|
_print_login_nag(config)
|
|
12431
12863
|
except Exception:
|
|
12432
12864
|
pass
|
|
12433
12865
|
|
|
12866
|
+
# ── Dulus Radio ON AIR (default) ────────────────────────────────────
|
|
12867
|
+
# Don't make humans type /news to hear us. Latest 3 dispatches, radio
|
|
12868
|
+
# format, every interactive boot. /news still works for more/all.
|
|
12869
|
+
try:
|
|
12870
|
+
_print_news_broadcast(3, boot=True)
|
|
12871
|
+
except Exception:
|
|
12872
|
+
pass
|
|
12873
|
+
|
|
12434
12874
|
# First-run /doctor — runs LAST, after every boot print so it's the
|
|
12435
12875
|
# final thing the user sees before the prompt. Welcome wizard set
|
|
12436
12876
|
# the flag; we cleared it into _first_run_doctor_pending earlier.
|
|
@@ -10,7 +10,7 @@ build-backend = "setuptools.build_meta"
|
|
|
10
10
|
|
|
11
11
|
[project]
|
|
12
12
|
name = "dulus"
|
|
13
|
-
version = "3.13.
|
|
13
|
+
version = "3.13.7"
|
|
14
14
|
description = "Provider-independent agent runtime for the terminal — cloud and local models, real tools, MCP, plugins, memory, sub-agents, and Dulus OS."
|
|
15
15
|
readme = "README.md"
|
|
16
16
|
requires-python = ">=3.10"
|
|
File without changes
|