local-operator-ui 0.31.10 → 0.31.12

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.
Files changed (81) hide show
  1. package/README.md +110 -126
  2. package/out/main/index.js +193 -7
  3. package/out/renderer/assets/{_basePickBy-9ROOwmxL.js → _basePickBy-BmRJBnL9.js} +1 -1
  4. package/out/renderer/assets/{_baseUniq-eE-r5ZZ4.js → _baseUniq-7Lo40p8f.js} +1 -1
  5. package/out/renderer/assets/{agent-details-page-Vs9AZBwz.js → agent-details-page-Dr0_4VXU.js} +2 -2
  6. package/out/renderer/assets/{agent-hub-page-CGJbElaY.js → agent-hub-page-CWhlINHl.js} +2 -2
  7. package/out/renderer/assets/agents-page-CfRvRbxk.js +2 -0
  8. package/out/renderer/assets/{arc-DN4pJSIq.js → arc-BzsVSHwA.js} +1 -1
  9. package/out/renderer/assets/{architectureDiagram-IEHRJDOE-uYP9fQLK.js → architectureDiagram-IEHRJDOE-Bw5-VATM.js} +1 -1
  10. package/out/renderer/assets/{blockDiagram-JOT3LUYC-CmsXiKwe.js → blockDiagram-JOT3LUYC-BGagYddg.js} +1 -1
  11. package/out/renderer/assets/{book-open-CNgko0U5.js → book-open-6gMxcFCn.js} +1 -1
  12. package/out/renderer/assets/browser-page-B6fHPjNZ.js +1 -0
  13. package/out/renderer/assets/{browser-webauthn-prompt-CCZq7UZY.js → browser-webauthn-prompt-7nbHSC-s.js} +1 -1
  14. package/out/renderer/assets/{c4Diagram-VJAJSXHY-DSjqK2s4.js → c4Diagram-VJAJSXHY-baq7nLZj.js} +1 -1
  15. package/out/renderer/assets/channel-DwqaTZnF.js +1 -0
  16. package/out/renderer/assets/{chunk-4BMEZGHF-C1_SAsaq.js → chunk-4BMEZGHF-g1gXYo4V.js} +1 -1
  17. package/out/renderer/assets/{chunk-A2AXSNBT-Cq0zFbJT.js → chunk-A2AXSNBT--A5WU7y8.js} +1 -1
  18. package/out/renderer/assets/{chunk-AEK57VVT-BcKdPjvg.js → chunk-AEK57VVT-DzJ3UVbN.js} +1 -1
  19. package/out/renderer/assets/{chunk-D6G4REZN-DRByxdTc.js → chunk-D6G4REZN-C-t_FTD9.js} +1 -1
  20. package/out/renderer/assets/{chunk-RZ5BOZE2-Duscm0Bg.js → chunk-RZ5BOZE2-BYlLeO8W.js} +1 -1
  21. package/out/renderer/assets/{chunk-XZIHB7SX-BWEXg8AD.js → chunk-XZIHB7SX-lFzFuHTM.js} +1 -1
  22. package/out/renderer/assets/classDiagram-GIVACNV2-D8nKB_Ad.js +1 -0
  23. package/out/renderer/assets/classDiagram-v2-COTLJTTW-D8nKB_Ad.js +1 -0
  24. package/out/renderer/assets/clone-C2OvbMzz.js +1 -0
  25. package/out/renderer/assets/{console-mirror-DjoNpI7u.js → console-mirror-Dw_ms9Pv.js} +13 -13
  26. package/out/renderer/assets/{consoleCapture-BTYJP9Fs.js → consoleCapture-BSLdNXhT.js} +1 -1
  27. package/out/renderer/assets/{dagre-OKDRZEBW-Cn7pFiK5.js → dagre-OKDRZEBW-BVSzUiqR.js} +1 -1
  28. package/out/renderer/assets/{diagram-SSKATNLV-LyA2eojc.js → diagram-SSKATNLV-B2vAnmRK.js} +1 -1
  29. package/out/renderer/assets/{diagram-VNBRO52H-LPidUHcc.js → diagram-VNBRO52H-DWMJqgBu.js} +1 -1
  30. package/out/renderer/assets/{erDiagram-Q7BY3M3F-CTjCMg7T.js → erDiagram-Q7BY3M3F-CUzlaIt5.js} +1 -1
  31. package/out/renderer/assets/{error-boundary-Be38BRVp.js → error-boundary-CAHYSCjh.js} +2 -2
  32. package/out/renderer/assets/{flowDiagram-4HSFHLVR-BmWZ8kNr.js → flowDiagram-4HSFHLVR-DD5CmZz9.js} +1 -1
  33. package/out/renderer/assets/{ganttDiagram-APWFNJXF-CfDRHTNJ.js → ganttDiagram-APWFNJXF-C1VcnkEW.js} +4 -4
  34. package/out/renderer/assets/{gitGraphDiagram-7IBYFJ6S-CEYh6RxP.js → gitGraphDiagram-7IBYFJ6S-hEwYc4s_.js} +1 -1
  35. package/out/renderer/assets/{graph-_dzRI-I_.js → graph-iFEIY46g.js} +1 -1
  36. package/out/renderer/assets/{index-tykR0DI9.js → index-BZY6SC3C.js} +396 -395
  37. package/out/renderer/assets/index-DLdCxgd4.css +1 -0
  38. package/out/renderer/assets/{index-DPFT_2au.js → index-Dws1_64y.js} +1 -1
  39. package/out/renderer/assets/{index-BKqWQaFB.js → index-xYgenPjE.js} +1 -1
  40. package/out/renderer/assets/{infoDiagram-PH2N3AL5-BRBj70Iz.js → infoDiagram-PH2N3AL5-Fb3LRSKa.js} +1 -1
  41. package/out/renderer/assets/{installer-DOOu7eM6.js → installer-CzcDRHum.js} +1 -1
  42. package/out/renderer/assets/{journeyDiagram-U35MCT3I-kHoqijou.js → journeyDiagram-U35MCT3I-CQoYP1F8.js} +1 -1
  43. package/out/renderer/assets/{kanban-definition-NDS4AKOZ-DEpxCCmE.js → kanban-definition-NDS4AKOZ-CteRmk-W.js} +1 -1
  44. package/out/renderer/assets/{layout-C8CaQmGy.js → layout-DYvIW0gu.js} +1 -1
  45. package/out/renderer/assets/{legacy-agents-page-TbzorhDj.js → legacy-agents-page-1fM02PQU.js} +3 -3
  46. package/out/renderer/assets/{mermaid.core-xrJy-S5f.js → mermaid.core-CF1Z-P7X.js} +5 -5
  47. package/out/renderer/assets/mesh-page-DI8M-fjS.js +2 -0
  48. package/out/renderer/assets/{mindmap-definition-ALO5MXBD-BUYs8m2Y.js → mindmap-definition-ALO5MXBD-DndDQMdO.js} +1 -1
  49. package/out/renderer/assets/{org-surface-gate-9zY-gRI7.js → org-surface-gate-BfrGIWSX.js} +1 -1
  50. package/out/renderer/assets/{page-header-BXIWLsA1.js → page-header-DuARVVAM.js} +1 -1
  51. package/out/renderer/assets/{pieDiagram-IB7DONF6-DLyxJ6_z.js → pieDiagram-IB7DONF6-CBrJnfZ7.js} +1 -1
  52. package/out/renderer/assets/{projects-page-pUwqeYdO.js → projects-page-MsZOyOOD.js} +3 -3
  53. package/out/renderer/assets/{quadrantDiagram-7GDLP6J5-DH7de3iA.js → quadrantDiagram-7GDLP6J5-BVP2IC46.js} +1 -1
  54. package/out/renderer/assets/{radar-MK3ICKWK-07d8mdrE.js → radar-MK3ICKWK-DjZTeeh9.js} +1 -1
  55. package/out/renderer/assets/{radient-auth-buttons-8U1osKLe.js → radient-auth-buttons-D7orNp3U.js} +1 -1
  56. package/out/renderer/assets/{requirementDiagram-KVF5MWMF-B32qFUIl.js → requirementDiagram-KVF5MWMF-lJJAXVZZ.js} +1 -1
  57. package/out/renderer/assets/{sankeyDiagram-QLVOVGJD-sbqxByYH.js → sankeyDiagram-QLVOVGJD-LQXNh0UX.js} +1 -1
  58. package/out/renderer/assets/{schedules-page-XTDmccje.js → schedules-page-CtMM9uV6.js} +10 -10
  59. package/out/renderer/assets/{sequenceDiagram-X6HHIX6F-B73M11ec.js → sequenceDiagram-X6HHIX6F-1jw8SEPO.js} +1 -1
  60. package/out/renderer/assets/{settings-page-CK0UZY_b.js → settings-page-DRypESjz.js} +10 -10
  61. package/out/renderer/assets/{settings-section-BeU3N8-t.js → settings-section-j4eB5zEy.js} +1 -1
  62. package/out/renderer/assets/{stateDiagram-DGXRK772-B4WvgL0d.js → stateDiagram-DGXRK772-B0lcRZrZ.js} +1 -1
  63. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-BfR0vu7W.js +1 -0
  64. package/out/renderer/assets/{timeline-definition-BDJGKUSR-C4QiFAyx.js → timeline-definition-BDJGKUSR-TbB_U2GW.js} +1 -1
  65. package/out/renderer/assets/{use-download-agent-mutation-uGo2AQXO.js → use-download-agent-mutation-BdgO7Jmf.js} +2 -2
  66. package/out/renderer/assets/{use-memberships-query-CjIPH1YX.js → use-memberships-query-f3E2Ca39.js} +1 -1
  67. package/out/renderer/assets/{xychartDiagram-VJFVF3MP-wcBKtlf2.js → xychartDiagram-VJFVF3MP-CGIvaGsN.js} +1 -1
  68. package/out/renderer/console-capture.html +4 -4
  69. package/out/renderer/index.html +5 -5
  70. package/out/renderer/installer.html +4 -4
  71. package/package.json +4 -2
  72. package/out/renderer/assets/agents-page-tftebSEL.js +0 -2
  73. package/out/renderer/assets/browser-page-Bu7bkVij.js +0 -1
  74. package/out/renderer/assets/channel-bHKgHeS_.js +0 -1
  75. package/out/renderer/assets/classDiagram-GIVACNV2-CU02kTw_.js +0 -1
  76. package/out/renderer/assets/classDiagram-v2-COTLJTTW-CU02kTw_.js +0 -1
  77. package/out/renderer/assets/clone-ARdM9vXs.js +0 -1
  78. package/out/renderer/assets/index-BjeBA__i.css +0 -1
  79. package/out/renderer/assets/mesh-page-Do2sLCUJ.js +0 -1
  80. package/out/renderer/assets/stateDiagram-v2-YXO3MK2T-DNtZ1inO.js +0 -1
  81. /package/out/renderer/assets/{index-C-P4UaBn.js → index-Ci5ykqkd.js} +0 -0
package/README.md CHANGED
@@ -5,70 +5,58 @@
5
5
  src="./resources/local-operator-icon-2-light-clear.png">
6
6
  </picture>
7
7
 
8
- <h1 align="center">Local Operator: AI Agent Assistants On Your Device</h1>
9
- <div align="center">
10
- <h2>🤖 Personal AI Assistants that Turn Ideas into Action</h2>
11
- <p><i>Real-time code execution on your device through natural conversation</i></p>
12
- </div>
8
+ <h1 align="center">Local Operator</h1>
13
9
 
14
- <br />
10
+ <p align="center"><i>AI agent assistants on your device — they plan, write, and run Python, and every step stays in the conversation.</i></p>
15
11
 
16
12
  <p align="center">
17
- <img src="./resources/preview-example.gif" alt="Local Operator UI Dashboard Example" style="width: 640px">
18
- </div>
13
+ <a href="https://github.com/damianvtran/local-operator-ui/releases/latest"><img alt="Latest release" src="https://img.shields.io/github/v/release/damianvtran/local-operator-ui"></a>
14
+ <a href="https://github.com/damianvtran/local-operator-ui/actions/workflows/ci.yml"><img alt="CI status" src="https://img.shields.io/github/actions/workflow/status/damianvtran/local-operator-ui/ci.yml?branch=main"></a>
15
+ <a href="https://www.npmjs.com/package/local-operator-ui"><img alt="npm version" src="https://img.shields.io/npm/v/local-operator-ui"></a>
16
+ <a href="./LICENSE"><img alt="License: MIT" src="https://img.shields.io/github/license/damianvtran/local-operator-ui"></a>
17
+ <img alt="Platforms: macOS, Windows, and Linux" src="https://img.shields.io/badge/platform-macOS%20%7C%20Windows%20%7C%20Linux-lightgrey">
18
+ </p>
19
19
 
20
20
  <br />
21
21
 
22
- **<span style="color: #38C96A">Local Operator</span>** empowers you to run Python code safely on your own machine through an intuitive chat interface. The AI agent:
23
-
24
- 🎯 **Plans & Executes** - Breaks down complex goals into manageable steps and executes them with precision.
25
-
26
- 🔒 **Prioritizes Security** - Built-in safety checks by independent AI review and user confirmations keep your system protected
27
-
28
- 🌐 **Flexible Deployment** - Run completely locally with Ollama models or leverage cloud providers like OpenAI
22
+ <p align="center">
23
+ <picture>
24
+ <source media="(prefers-color-scheme: dark)" srcset="./resources/readme/app-shell-chat-dark.webp">
25
+ <source media="(prefers-color-scheme: light)" srcset="./resources/readme/app-shell-chat-light.webp">
26
+ <img alt="The app's chat view: an agent's reply, the run details panel with two subagents running, and their shared to-do list." src="./resources/readme/app-shell-chat-light.webp">
27
+ </picture>
28
+ <br />
29
+ <sub><code>the app at work</code> — a conversation with an agent, two subagents running beside it, and the to-do list they share.</sub>
30
+ </p>
29
31
 
30
- 🔧 **Problem Solving** - Intelligently handles errors and roadblocks by adapting approaches and finding alternative solutions
32
+ <br />
31
33
 
32
- This project is proudly open source under the MIT license. We believe AI tools should be accessible to everyone, given their transformative impact on productivity. Your contributions and feedback help make this vision a reality!
34
+ **Local Operator** runs AI agent assistants on your own machine, from a desktop chat app. You give an agent a goal; it plans the work, writes and runs Python on your device, reads and writes files, and asks before it takes anything risky. The whole run — messages, tool calls, commands, and results — stays in the conversation in front of you.
33
35
 
34
- > "Democratizing AI-powered productivity, one conversation at a time."
36
+ For the agent environment CLI and server backend, see the [Local Operator repository](https://github.com/damianvtran/local-operator).
35
37
 
36
- <div align="center">
37
- <a href="https://github.com/damianvtran/local-operator">Agent Backend</a> •
38
- <a href="https://local-operator.com">Learn More</a> •
38
+ <p align="center">
39
+ <a href="https://github.com/damianvtran/local-operator">Agent backend</a> •
40
+ <a href="https://local-operator.com">Website</a> •
39
41
  <a href="https://github.com/damianvtran/local-operator/tree/main/examples/notebooks">Examples</a>
40
- </div>
42
+ </p>
41
43
 
42
- ## 💡 Overview
43
-
44
- The Local Operator UI is a user interface for managing and interacting with the Local Operator agent environment. It is built using Electron, React, and TypeScript, leveraging modern web technologies for a rich and responsive user experience.
45
-
46
- 👉 For the agent environment CLI and Server backend, see the [Local Operator GitHub repository](https://github.com/damianvtran/local-operator).
47
-
48
- ## 🚀 Getting Started
44
+ ## Getting started
49
45
 
50
46
  ### Prerequisites
51
47
 
52
- Before you begin, ensure you have the following installed:
53
-
54
- - **Node.js**: Version 22.13.1 or higher. It's recommended to use [nvm](https://github.com/nvm-sh/nvm) for managing Node.js versions.
55
-
56
- The Local Operator backend is now bundled with the application and will be installed automatically when you first run the application. If you prefer to use an existing Local Operator backend installation, the application will detect it and use it instead.
57
-
58
- ### NPM Installation
48
+ - **Node.js** 22.13.1 or newer, for the npm install paths below. [nvm](https://github.com/nvm-sh/nvm) is a good way to manage Node versions. The desktop installers need no toolchain.
59
49
 
60
- You can install and run Local Operator UI directly using [npx](https://docs.n8n.io/hosting/installation/npm/).
50
+ ### Run with npx
61
51
 
62
52
  ```bash
63
- # Install and run in one command
53
+ # Download and run in one command
64
54
  npx local-operator-ui
65
55
  ```
66
56
 
67
- This will download and execute the latest version of the Local Operator UI, launching the application immediately.
57
+ This runs the latest version and launches the app.
68
58
 
69
- ### Manual Installation
70
-
71
- Alternatively, without npx, you can install the package globally with standard npm:
59
+ ### Install with npm
72
60
 
73
61
  ```bash
74
62
  # Install globally
@@ -78,52 +66,67 @@ npm install -g local-operator-ui
78
66
  local-operator-ui
79
67
  ```
80
68
 
81
- After installation, the application will automatically connect to the Local Operator backend API at `http://localhost:1111` by default.
82
-
83
- ### Desktop Applications
69
+ ### Desktop applications
84
70
 
85
- Pre-built desktop applications are available for macOS, Windows, and Linux. Visit the [Releases](https://github.com/damianvtran/local-operator-ui/releases) page to download the latest version for your platform.
71
+ Prebuilt apps are on the [download page](https://local-operator.com/download) and the [releases page](https://github.com/damianvtran/local-operator-ui/releases):
86
72
 
87
- - **macOS**: Download the `.dmg` file and drag the application to your Applications folder.
73
+ - **macOS**: Download the `.dmg` (or `.zip`) and open it.
88
74
  - **Windows**: Download the `.exe` installer and follow the installation prompts.
89
- - **Linux**: Download the appropriate package (`.deb`, `.rpm`, or `.AppImage`) for your distribution.
75
+ - **Linux**: Download the `.deb`, `.rpm`, or `.AppImage` for your distribution.
90
76
 
91
- ### Building from Source
77
+ The Local Operator backend is bundled with the application and is installed automatically on first run. If you already have a Local Operator backend installed, the application detects it and uses it instead; by default it connects to the backend API at `http://localhost:1111`.
92
78
 
93
- If you want to build the application from source, see the [BUILD.md](./docs/BUILD.md) file for detailed instructions.
79
+ ## What it looks like
94
80
 
95
- #### Python Bundling
81
+ ### Chat and agents
96
82
 
97
- For macOS builds, we now bundle a standalone Python directly with the application instead of requiring Homebrew installation. This approach:
83
+ | Light | Dark |
84
+ | :---: | :---: |
85
+ | <img src="./resources/readme/chat-trace-light.webp" alt="Light theme: a conversation showing tool rows, a blocked risky action, and a question the agent asked." width="420"> | <img src="./resources/readme/chat-trace-dark.webp" alt="Dark theme: the same conversation." width="420"> |
98
86
 
99
- - Eliminates the need for admin privileges during installation
100
- - Makes the application more self-contained
101
- - Works offline
102
- - Provides a more reliable user experience
103
- - Supports installation of Python packages via pip
87
+ <sub><code>chat</code> — a working agent's tool rows, a blocked risky action, and the question it asked before continuing.</sub>
104
88
 
105
- To set up the standalone Python for development:
89
+ ### Agent hub
106
90
 
107
- ```bash
108
- # Run the setup script to download and configure standalone Python
109
- pnpm setup-python-standalone
110
- ```
91
+ | Light | Dark |
92
+ | :---: | :---: |
93
+ | <img src="./resources/readme/agent-hub-light.webp" alt="Light theme: the agent hub with categories and a grid of community agents." width="420"> | <img src="./resources/readme/agent-hub-dark.webp" alt="Dark theme: the agent hub grid." width="420"> |
111
94
 
112
- This uses [python-build-standalone](https://github.com/indygreg/python-build-standalone), the same approach used by Datasette Desktop and PyOxidizer.
95
+ <sub><code>agent hub</code> — community agents you can browse and download, by category.</sub>
113
96
 
114
- For more details, see the [PYTHON_BUNDLING.md](./docs/PYTHON_BUNDLING.md) documentation.
97
+ ### Schedules
115
98
 
116
- ### Code Signing and Notarization
99
+ | Light | Dark |
100
+ | :---: | :---: |
101
+ | <img src="./resources/readme/schedules-light.webp" alt="Light theme: the schedules page, listing scheduled wakes with their cadence and run history." width="420"> | <img src="./resources/readme/schedules-dark.webp" alt="Dark theme: the schedules page." width="420"> |
117
102
 
118
- All desktop applications are code signed and notarized to ensure security and trust:
103
+ <sub><code>schedules</code> — conversations that wake on a timer, with what ran and when they run next.</sub>
119
104
 
120
- - **macOS**: Applications are signed with an Apple Developer ID and notarized with Apple's notarization service
121
- - **Windows**: Applications are signed with a trusted code signing certificate
122
- - **Linux**: While code signing is less common on Linux, packages are built with integrity checks
105
+ ### Projects
123
106
 
124
- For detailed information about the code signing and notarization process, see the [CODE_SIGNING.md](./docs/CODE_SIGNING.md) document.
107
+ | Light | Dark |
108
+ | :---: | :---: |
109
+ | <img src="./resources/readme/projects-board-light.webp" alt="Light theme: the projects board with active, paused, and done columns." width="420"> | <img src="./resources/readme/projects-board-dark.webp" alt="Dark theme: the projects board." width="420"> |
110
+
111
+ <sub><code>projects</code> — workstreams you and your agents track across sessions, as a board or a timeline.</sub>
112
+
113
+ ### Appearance
114
+
115
+ | Light | Dark |
116
+ | :---: | :---: |
117
+ | <img src="./resources/readme/appearance-light.webp" alt="Light theme: the appearance picker showing the Local Operator themes as swatches." width="420"> | <img src="./resources/readme/appearance-dark.webp" alt="Dark theme: the appearance picker." width="420"> |
118
+
119
+ <sub><code>appearance</code> — 59 colour themes ship with the app; Local Operator Dark is the default.</sub>
125
120
 
126
- ## 🗂️ Project Structure
121
+ ## Features
122
+
123
+ - **Chat with agents** — real-time conversation with markdown rendering for code blocks and formatted text, syntax highlighting, and per-conversation history.
124
+ - **Agent management** — create, update, and delete agents, and configure their settings: model and description (general), temperature and top_p (chat), and security prompt and execution permissions (security).
125
+ - **Settings** — system prompt configuration, API credentials management, and application configuration in one place.
126
+ - **Local Operator API integration** — the app talks to the Local Operator backend API and shows real-time status updates for long-running operations.
127
+ - **Bundled backend** — the Local Operator backend is installed automatically on first run on every platform; an existing backend installation is detected and used instead.
128
+
129
+ ## Project structure
127
130
 
128
131
  The codebase is organized for modularity and code reuse:
129
132
 
@@ -132,59 +135,55 @@ The codebase is organized for modularity and code reuse:
132
135
  - `src/renderer/src/app.tsx`, `main.tsx`, etc.: Entry points for the Electron renderer process.
133
136
  - `build/`, `resources/`, `scripts/`: Build assets, static resources, and build scripts.
134
137
 
135
- **Import Conventions:**
138
+ **Import conventions:**
136
139
 
137
140
  - Use `@shared/` for shared modules (e.g., `import { useAgents } from "@shared/hooks/use-agents"`).
138
141
  - Use `@features/` for feature-specific modules.
139
- - The old aliases (`@renderer`, `@components`, `@hooks`, etc.) have been removed in favor of this unified structure.
142
+ - The build also resolves the older aliases (`@renderer`, `@components`, `@hooks`, etc.) for the files that still import them.
140
143
 
141
144
  For more details on building or contributing, see the [Contributing Guide](./CONTRIBUTING.md) and [BUILD.md](./docs/BUILD.md).
142
145
 
143
- ## ✨ Features
146
+ ## Building from source
147
+
148
+ If you want to build the application from source, see the [BUILD.md](./docs/BUILD.md) file for detailed instructions.
144
149
 
145
- The Local Operator UI provides a comprehensive interface for interacting with AI agents:
150
+ ### Python bundling
146
151
 
147
- ### Chat Interface
152
+ For macOS builds, the app bundles a standalone Python directly instead of requiring a Homebrew installation. This approach:
148
153
 
149
- - Real-time chat with AI agents
150
- - Markdown rendering for code blocks and formatted text
151
- - Syntax highlighting for code snippets
152
- - Message history and conversation management
154
+ - Needs no admin privileges during installation
155
+ - Keeps the application self-contained
156
+ - Works offline
157
+ - Supports installing Python packages with pip
153
158
 
154
- ### Agent Management
159
+ To set up the standalone Python for development:
155
160
 
156
- - Create, update, and delete AI agents
157
- - Configure agent settings:
158
- - General settings (name, description, model)
159
- - Chat settings (temperature, top_p, etc.)
160
- - Security settings (security prompt, execution permissions)
161
+ ```bash
162
+ # Run the setup script to download and configure standalone Python
163
+ pnpm setup-python-standalone
164
+ ```
161
165
 
162
- ### Settings
166
+ This uses [python-build-standalone](https://github.com/indygreg/python-build-standalone), the same approach used by Datasette Desktop and PyOxidizer.
163
167
 
164
- - System prompt configuration
165
- - API credentials management
166
- - Application configuration
168
+ For more details, see the [PYTHON_BUNDLING.md](./docs/PYTHON_BUNDLING.md) documentation.
167
169
 
168
- ### API Integration
170
+ ### Code signing and notarization
169
171
 
170
- - Seamless integration with the Local Operator backend API
171
- - Real-time status updates for long-running operations
172
- - Error handling and retry mechanisms
172
+ All desktop applications are code signed and notarized to ensure security and trust:
173
173
 
174
- ### Bundled Backend
174
+ - **macOS**: Applications are signed with an Apple Developer ID and notarized with Apple's notarization service
175
+ - **Windows**: Applications are signed with a trusted code signing certificate
176
+ - **Linux**: While code signing is less common on Linux, packages are built with integrity checks
175
177
 
176
- - The Local Operator backend is now bundled with the application
177
- - Automatic installation of the backend when first running the application
178
- - Automatic detection and use of existing backend installations
179
- - Cross-platform support for Windows, macOS, and Linux
178
+ For detailed information about the code signing and notarization process, see the [CODE_SIGNING.md](./docs/CODE_SIGNING.md) document.
180
179
 
181
- ## 🤝 Contributing
180
+ ## Contributing
182
181
 
183
182
  Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md) for details on how to get started with development, code style guidelines, and our contribution process.
184
183
 
185
- ## 🐛 Troubleshooting
184
+ ## Troubleshooting
186
185
 
187
- ### Common Issues
186
+ ### Common issues
188
187
 
189
188
  #### Application fails to connect to the backend
190
189
 
@@ -197,7 +196,7 @@ Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md
197
196
  #### Backend installation fails
198
197
 
199
198
  - Check the application logs for error messages
200
- - For macOS, the application now uses a bundled Python framework instead of requiring Homebrew and pyenv
199
+ - For macOS, the application uses a bundled Python framework instead of requiring Homebrew and pyenv
201
200
  - If you encounter issues with the bundled Python, see the [PYTHON_BUNDLING.md](./docs/PYTHON_BUNDLING.md) documentation
202
201
  - As a fallback, you can try installing the backend manually with `pip install local-operator` and then start it with `local-operator serve`
203
202
 
@@ -215,38 +214,23 @@ Contributions are welcome! Please see our [Contributing Guide](./CONTRIBUTING.md
215
214
 
216
215
  #### The window came to the front on its own, or a run never appeared
217
216
 
218
- Every time the app brings a window to the front it writes one line to its own
219
- backend log (`~/Library/Application Support/Local Operator/logs/backend-service.log`
220
- on macOS; the `LOCAL_OPERATOR_LOG_DIR` environment variable moves it):
217
+ Every time the app brings a window to the front it writes one line to its own backend log (`~/Library/Application Support/Local Operator/logs/backend-service.log` on macOS; the `LOCAL_OPERATOR_LOG_DIR` environment variable moves it):
221
218
 
222
219
  ```
223
220
  [window-raise] trigger=second-instance mode=normal requested=focus pid=9182 cwd=/Users/you/project applied=restore+show+focus
224
221
  ```
225
222
 
226
- `trigger` names what asked: `initial-present` (the app starting up),
227
- `second-instance` (a second launch sharing this profile), `banner-click`,
228
- `viewer-focus` or `viewer-resume`. `pid` and `cwd`, when they are there, name the
229
- process that asked — that is the one to stop if something keeps doing it. A run
230
- that raises nothing writes nothing, so an app that never came forward has no line
231
- at all — although a conversation that is WAITING for a window does: a `headless`
232
- launch against an app with no window open writes `applied=parked`, `parked=<id>`
233
- rather than a raise, and that conversation opens the next window you give the app.
234
- In the line above, `mode` is the window mode the raise ran under and `applied` is
235
- what it actually did.
236
-
237
- A second launch only brings the window as far as IT asked: a `headless` run never
238
- raises it (it can still load the conversation it names), an `inactive` one orders
239
- the window without activating the app and without pulling it back out of the
240
- Dock, and a launch that declares nothing — you double-clicking the app while it is
241
- already running — still comes to the front.
242
-
243
- ### Getting Help
223
+ `trigger` names what asked: `initial-present` (the app starting up), `second-instance` (a second launch sharing this profile), `banner-click`, `viewer-focus` or `viewer-resume`. `pid` and `cwd`, when they are there, name the process that asked — that is the one to stop if something keeps doing it. A run that raises nothing writes nothing, so an app that never came forward has no line at all — although a conversation that is WAITING for a window does: a `headless` launch against an app with no window open writes `applied=parked`, `parked=<id>` rather than a raise, and that conversation opens the next window you give the app. In the line above, `mode` is the window mode the raise ran under and `applied` is what it actually did.
224
+
225
+ A second launch only brings the window as far as IT asked: a `headless` run never raises it (it can still load the conversation it names), an `inactive` one orders the window without activating the app and without pulling it back out of the Dock, and a launch that declares nothing — you double-clicking the app while it is already running — still comes to the front.
226
+
227
+ ## Getting help
244
228
 
245
229
  If you encounter issues not covered here, please:
246
230
 
247
231
  1. Check the [GitHub Issues](https://github.com/damianvtran/local-operator-ui/issues) for similar problems
248
232
  2. Open a new issue if your problem hasn't been reported
249
233
 
250
- ## 📄 License
234
+ ## License
251
235
 
252
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
236
+ This project is licensed under the MIT License — see the [LICENSE](LICENSE) file for details. It is open source because AI tools should be accessible to everyone, and your contributions and feedback help make that real.
package/out/main/index.js CHANGED
@@ -67,6 +67,7 @@ const mcpOperationId = zod.z.string().regex(/^[a-f0-9]{32}$/);
67
67
  const mcpCatalogCwd = zod.z.string().min(1).max(4096).regex(/^(\/|[A-Za-z]:[\\/])/);
68
68
  const SUBSCRIPTION_ID_PATTERN = /^[a-f0-9]{32}$/;
69
69
  const requestId = zod.z.string().regex(/^[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}$/);
70
+ const meshId = zod.z.string().regex(/^[A-Za-z0-9_-]{1,128}$/);
70
71
  const sessionImage = zod.z.object({
71
72
  data_b64: zod.z.string().min(1).max(1e6),
72
73
  mime_type: zod.z.enum(["image/png", "image/jpeg", "image/gif", "image/webp"])
@@ -296,6 +297,24 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
296
297
  * archived state, and an unarchive control on a row found by search.
297
298
  */
298
299
  include_archived: zod.z.boolean().optional(),
300
+ /*
301
+ * Whether conversations OTHER devices hold belong in the answer.
302
+ *
303
+ * ABSENT MEANS `false`, the same compatibility promise `include_archived`
304
+ * makes and for the same reason: the app's own sidebar fetch has always
305
+ * meant "this device's catalogue", and a client that predates the mesh must
306
+ * keep reading exactly that. The Mesh tab is the ONE surface that asks for
307
+ * the federated list, because "which conversation is on which device" is its
308
+ * question and it cannot answer it from `session_count` alone.
309
+ *
310
+ * THE COST IS NOT ZERO, which is why only that surface asks: the backend's
311
+ * peer projection dials each peer's relay under a 12 s fan-out budget and is
312
+ * TTL-cached at 20 s (`network/relay.py`, `session/peer_rows.py`), while a
313
+ * machine in no network short-circuits to no call at all — so the flag costs
314
+ * a paired device one cached fan-out per cadence, and an unpaired one
315
+ * nothing. The sidebar's two-second poll must never carry it.
316
+ */
317
+ include_peers: zod.z.boolean().optional(),
299
318
  /*
300
319
  * The four parameters that make the catalogue PAGEABLE, and the switch that
301
320
  * makes the daemon count it.
@@ -1384,14 +1403,81 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1384
1403
  * UI that could would have to re-implement the relay's authorisation model in
1385
1404
  * JavaScript.
1386
1405
  *
1387
- * THE MUTATING MESH OPS ARE NOT HERE YET, deliberately: slice 1 of the Mesh tab is
1388
- * a read surface, and a request schema entry with no caller is a capability this
1389
- * app advertises but cannot exercise. `networks.invite`,
1390
- * `networks.member.remove` and `sessions.transfer` land with the surfaces that use
1391
- * them (the invite action, the member table, the drag layer).
1406
+ * THE MUTATING MESH OPS LANDED WITH THE SURFACES THAT USE THEM (slice 2: the drag
1407
+ * layer, the invite action, the member list), which is the rule this block stated
1408
+ * while they were still absent: a request schema entry with no caller is a
1409
+ * capability this app advertises but cannot exercise.
1410
+ *
1411
+ * `features.session_transfer` gates the TRANSFER and only it (`capabilities.py`):
1412
+ * a backend can host a network, mint invites and remove members without being able
1413
+ * to move a conversation, and the surfaces that gate on the wrong key draw a
1414
+ * control that 404s. The three ops below therefore sit behind different keys —
1415
+ * `networks.invite`/`networks.member.remove` behind `features.peers` (they are
1416
+ * routes the mesh itself introduced), `sessions.transfer` behind
1417
+ * `features.session_transfer`.
1418
+ *
1419
+ * WHAT TRAVELS, AND WHAT DOES NOT. `to` is the destination device id or the
1420
+ * literal `"local"` (a RECALL), because that is the route's own shape: one route,
1421
+ * two protocols, and the direction is decided by which of the two ends is asking
1422
+ * — see `guide://network`. `request_id` is minted by the CALLER and is what makes
1423
+ * a retry replay a recorded outcome instead of starting a second move for a
1424
+ * request that may still be running, so it is sent on every drop rather than kept
1425
+ * for a retry path this surface does not have.
1392
1426
  */
1393
1427
  zod.z.object({ op: zod.z.literal("peers.list") }).strict(),
1394
1428
  zod.z.object({ op: zod.z.literal("networks.list") }).strict(),
1429
+ /*
1430
+ * Mint an invite. `role` is the joined device's own role in the network and
1431
+ * `device` BINDS the token to one device id, so a token intercepted on its way to
1432
+ * another machine cannot be redeemed by a third one. The token itself NEVER
1433
+ * crosses this API (the receipt carries a path, and it is written where the
1434
+ * renderer cannot read it) — which is why the answer is a receipt and not a
1435
+ * secret.
1436
+ */
1437
+ zod.z.object({
1438
+ op: zod.z.literal("networks.invite"),
1439
+ networkId: meshId,
1440
+ role: zod.z.enum(["read", "drive", "admin"]),
1441
+ deviceId: meshId.optional()
1442
+ }).strict(),
1443
+ /*
1444
+ * Revoke a membership. `confirm` is the NETWORK'S NAME, typed by the user, and
1445
+ * the route compares it exactly: this is the one mesh act that changes other
1446
+ * devices' state (every peer is rekeyed and the removed device is locked out on
1447
+ * its next handshake), so the request must carry what the user was shown rather
1448
+ * than a bool a stray retry could also send.
1449
+ */
1450
+ zod.z.object({
1451
+ op: zod.z.literal("networks.member.remove"),
1452
+ networkId: meshId,
1453
+ deviceId: meshId,
1454
+ confirm: zod.z.string().min(1).max(256)
1455
+ }).strict(),
1456
+ /*
1457
+ * Ask a device to take a conversation, or ask THIS device to take one back.
1458
+ *
1459
+ * THE DIRECTION IS THE PROTOCOL'S, not a UI preference: there is no push verb, so
1460
+ * a drop on a peer is this device asking that peer to PULL (`to: <device_id>`),
1461
+ * and a drop on this device is a recall (`to: "local"`). `keep` is the reversible
1462
+ * half — it mints a new id at the destination and leaves the source running —
1463
+ * while a move deletes the source's copy once the handoff commits, which is why
1464
+ * `source_retired = (mode == "move")` on the receipt.
1465
+ *
1466
+ * `wait_s` is a CEILING ON WAITING INSIDE THE REQUEST, not a promise: the route
1467
+ * returns as soon as it has a definite outcome, and a `busy` source refuses
1468
+ * rather than being interrupted. The desktop's own deadline for this op is
1469
+ * derived from the route's published bound rather than from the 20 s control
1470
+ * budget (see `moveClientBoundMs`) — the defect this avoids is a client that gives
1471
+ * up first and reports its own timeout for a move the backend was about to answer.
1472
+ */
1473
+ zod.z.object({
1474
+ op: zod.z.literal("sessions.transfer"),
1475
+ sessionId,
1476
+ to: zod.z.union([zod.z.literal("local"), meshId]),
1477
+ keep: zod.z.boolean().optional(),
1478
+ waitS: zod.z.number().min(0).max(300).optional(),
1479
+ requestId: requestId.optional()
1480
+ }).strict(),
1395
1481
  /*
1396
1482
  * The Projects surface (`/v1/desktop/projects*`), APPENDED to the union
1397
1483
  * rather than inserted beside the other catalogue ops: the backend serves
@@ -1466,6 +1552,30 @@ const desktopRequestUnion = zod.z.discriminatedUnion("op", [
1466
1552
  op: zod.z.literal("projects.milestone.remove"),
1467
1553
  key: projectKey,
1468
1554
  name: zod.z.string().min(1).max(PROJECT_MILESTONE_NAME_MAX_CHARS)
1555
+ }).strict(),
1556
+ /*
1557
+ * AIDA'S CONTROL PLANE: one read and one control op on the same route
1558
+ * (`/v1/desktop/aida`), because the rail's row and the composer's `/aida`
1559
+ * need the SAME state and a second spelling of it would be a second answer
1560
+ * about her one long session (`design.md` § 4 freezes the route).
1561
+ *
1562
+ * The feature is gated by its OWN capability key (`features.aida`), never a
1563
+ * bump of `commands`: a renderer that does not read it keeps working against
1564
+ * this backend, and this renderer must not call the route while the key is
1565
+ * absent or 0 (§ 3.4's version skew).
1566
+ *
1567
+ * Deliberately NOT a `MESSAGE_OPS` member (see `desktopRequestByteBudget`):
1568
+ * an enum word and a receipt are not prose, so this costs the control budget.
1569
+ */
1570
+ zod.z.object({ op: zod.z.literal("aida.status") }).strict(),
1571
+ zod.z.object({
1572
+ op: zod.z.literal("aida.control"),
1573
+ /*
1574
+ * The route's own op vocabulary, held to it here: a word the backend does
1575
+ * not serve must fail at this boundary rather than travel as a 422 the
1576
+ * user reads as a defect of their press.
1577
+ */
1578
+ action: zod.z.enum(["open", "pause", "resume", "greet", "status"])
1469
1579
  }).strict()
1470
1580
  ]);
1471
1581
  const desktopRequestSchema = desktopRequestUnion.superRefine(
@@ -1568,7 +1678,23 @@ const LONG_READ_OPS = /* @__PURE__ */ new Set([
1568
1678
  ...PROVIDER_READ_OPS,
1569
1679
  ...MESH_READ_OPS
1570
1680
  ]);
1571
- function desktopRequestDeadlineMs(op) {
1681
+ const MOVE_OP_DEADLINE_S = 90;
1682
+ const MOVE_OFFLOAD_CONFIRM_S = 30;
1683
+ const MOVE_COPY_WAIT_S = 300;
1684
+ const MOVE_CONTROL_SLACK_S = 10;
1685
+ const MOVE_CLIENT_MARGIN_S = 15;
1686
+ const MOVE_APP_MARGIN_MS = 1e4;
1687
+ function moveClientBoundMs(shape) {
1688
+ const recall = shape.to === "local";
1689
+ const hold = recall || shape.keep ? MOVE_COPY_WAIT_S : MOVE_OFFLOAD_CONFIRM_S;
1690
+ const waitS = Math.max(0, shape.waitS ?? 0);
1691
+ return (MOVE_OP_DEADLINE_S + hold + MOVE_CONTROL_SLACK_S + MOVE_CLIENT_MARGIN_S) * 1e3 + waitS * 1e3;
1692
+ }
1693
+ function desktopRequestDeadlineMs(request) {
1694
+ if (typeof request !== "string" && request.op === "sessions.transfer") {
1695
+ return moveClientBoundMs(request) + MOVE_APP_MARGIN_MS;
1696
+ }
1697
+ const op = typeof request === "string" ? request : request.op;
1572
1698
  return LONG_READ_OPS.has(op) ? DESKTOP_LONG_READ_DEADLINE_MS : DESKTOP_CONTROL_DEADLINE_MS;
1573
1699
  }
1574
1700
  const DESKTOP_DEADLINE_EXCEEDED_CODE = "deadline_exceeded";
@@ -1630,6 +1756,12 @@ const PANEL_READ_OPS = /* @__PURE__ */ new Set([
1630
1756
  function desktopRequestDeadlineDetail(op, deadlineMs) {
1631
1757
  const seconds = Math.round(deadlineMs / 1e3);
1632
1758
  const code = DESKTOP_DEADLINE_EXCEEDED_CODE;
1759
+ if (op === "sessions.transfer") {
1760
+ return {
1761
+ code,
1762
+ message: `The app waits up to ${seconds} seconds for a move, and it was still running when the app stopped waiting. The move was asked for, so its outcome is unknown from here: read the session again before moving it anywhere else.`
1763
+ };
1764
+ }
1633
1765
  if (READ_ONLY_OPS.has(op)) {
1634
1766
  return {
1635
1767
  code,
@@ -1689,6 +1821,46 @@ function desktopEndpoint(request) {
1689
1821
  return { path: "/v1/desktop/peers", method: "GET" };
1690
1822
  case "networks.list":
1691
1823
  return { path: "/v1/desktop/networks", method: "GET" };
1824
+ /*
1825
+ * THE THREE MESH WRITES. Each path segment is `encodeURIComponent`ed even
1826
+ * though `meshId` already refuses `/`, `.` and `%`: the schema is this
1827
+ * client's check, and a redirect or a hand-built request must not be able to
1828
+ * turn a device id into a path fragment. `device: null` on an invite is the
1829
+ * route's own "unbound token" — an invite any device may redeem once.
1830
+ */
1831
+ case "networks.invite":
1832
+ return {
1833
+ path: `/v1/desktop/networks/${encodeURIComponent(request.networkId)}/invite`,
1834
+ method: "POST",
1835
+ body: { role: request.role, device: request.deviceId ?? null }
1836
+ };
1837
+ case "networks.member.remove":
1838
+ return {
1839
+ path: `/v1/desktop/networks/${encodeURIComponent(request.networkId)}/members/${encodeURIComponent(request.deviceId)}`,
1840
+ method: "DELETE",
1841
+ // The NETWORK'S NAME, as typed: see the op's own comment for why the
1842
+ // route takes a name rather than a bool.
1843
+ body: { confirm: request.confirm }
1844
+ };
1845
+ case "sessions.transfer":
1846
+ return {
1847
+ path: `/v1/desktop/sessions/${encodeURIComponent(request.sessionId)}/transfer`,
1848
+ method: "POST",
1849
+ body: {
1850
+ to: request.to,
1851
+ // Sent explicitly rather than omitted-when-false: `keep` decides whether
1852
+ // the SOURCE'S COPY IS DELETED, so the request says which move it is
1853
+ // rather than leaving the route's default to answer for a drop a user
1854
+ // made from a menu that offered the copy.
1855
+ keep: request.keep ?? false,
1856
+ wait_s: request.waitS ?? 0,
1857
+ // Omitted while absent, and this one is load-bearing: the route
1858
+ // journals an UNCONFIRMED move under this key so a retry replays the
1859
+ // recorded outcome instead of moving twice, and a request with no key
1860
+ // is a different (unjournalled) request on purpose.
1861
+ ...request.requestId ? { request_id: request.requestId } : {}
1862
+ }
1863
+ };
1692
1864
  case "profiles.list":
1693
1865
  return { path: "/v1/desktop/profiles", method: "GET" };
1694
1866
  case "profiles.get":
@@ -1745,6 +1917,7 @@ function desktopEndpoint(request) {
1745
1917
  if (request.scope_name) params.set("scope_name", request.scope_name);
1746
1918
  if (request.cursor) params.set("cursor", request.cursor);
1747
1919
  if (request.with_counts) params.set("with_counts", "true");
1920
+ if (request.include_peers) params.set("include_peers", "true");
1748
1921
  return {
1749
1922
  path: `/v1/desktop/sessions?${params}`,
1750
1923
  method: "GET"
@@ -2526,6 +2699,19 @@ function desktopEndpoint(request) {
2526
2699
  path: `/v1/desktop/projects/${encodeURIComponent(request.key)}/milestones/${encodeURIComponent(request.name)}`,
2527
2700
  method: "DELETE"
2528
2701
  };
2702
+ case "aida.status":
2703
+ return { path: "/v1/desktop/aida", method: "GET" };
2704
+ case "aida.control":
2705
+ return {
2706
+ path: "/v1/desktop/aida",
2707
+ method: "POST",
2708
+ /*
2709
+ * The route's body is `{"op": ...}` — its own word, not this envelope's
2710
+ * — so the ACTION travels under the route's field name and the two `op`s
2711
+ * cannot be read as one.
2712
+ */
2713
+ body: { op: request.action }
2714
+ };
2529
2715
  }
2530
2716
  }
2531
2717
  const MAX_PROBE_PATHS = 64;
@@ -6129,7 +6315,7 @@ async function requestDesktopOutcome(input, backendUrl, token) {
6129
6315
  }
6130
6316
  const target2 = desktopEndpoint(request);
6131
6317
  let answered = false;
6132
- const deadlineMs = desktopRequestDeadlineMs(request.op);
6318
+ const deadlineMs = desktopRequestDeadlineMs(request);
6133
6319
  try {
6134
6320
  const body = target2.body === void 0 ? void 0 : JSON.stringify(target2.body);
6135
6321
  if (body && Buffer.byteLength(body) > desktopRequestByteBudget(request.op)) {
@@ -1 +1 @@
1
- import{e as o,c as b,g as v,k as O,h as P,j as p,l as w,m as A,n as x,t as c,o as N}from"./_baseUniq-eE-r5ZZ4.js";import{as as g,aa as y,at as E,au as F,av as M,aw as I,ax as _,ay as $,az as B,aA as T}from"./mermaid.core-xrJy-S5f.js";var S=/\s/;function z(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var G=/^\s+/;function H(n){return n&&n.slice(0,z(n)+1).replace(G,"")}var m=NaN,L=/^[-+]0x[0-9a-f]+$/i,R=/^0b[01]+$/i,q=/^0o[0-7]+$/i,C=parseInt;function K(n){if(typeof n=="number")return n;if(o(n))return m;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=H(n);var t=R.test(n);return t||q.test(n)?C(n.slice(2),t?2:8):L.test(n)?m:+n}var W=1/0,X=17976931348623157e292;function Y(n){if(!n)return n===0?n:0;if(n=K(n),n===W||n===-1/0){var r=n<0?-1:1;return r*X}return n===n?n:0}function D(n){var r=Y(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?b(n):[]}var l=Object.prototype,J=l.hasOwnProperty,dn=y(function(n,r){n=Object(n);var t=-1,e=r.length,a=e>2?r[2]:void 0;for(a&&E(r[0],r[1],a)&&(e=1);++t<e;)for(var f=r[t],i=F(f),s=-1,d=i.length;++s<d;){var u=i[s],h=n[u];(h===void 0||M(h,l[u])&&!J.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function Q(n){return function(r,t,e){var a=Object(r);if(!I(r)){var f=v(t);r=O(r),t=function(s){return f(a[s],s,a)}}var i=n(r,t,e);return i>-1?a[f?r[i]:i]:void 0}}var U=Math.max;function Z(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var a=t==null?0:D(t);return a<0&&(a=U(e+a,0)),P(n,v(r),a)}var hn=Q(Z);function V(n,r){var t=-1,e=I(n)?Array(n.length):[];return p(n,function(a,f,i){e[++t]=r(a,f,i)}),e}function gn(n,r){var t=_(n)?w:V;return t(n,v(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function vn(n,r){return n!=null&&A(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,a=n.length;++e<a;){var f=n[e],i=r(f);if(i!=null&&(s===void 0?i===i&&!o(i):t(i,s)))var s=i,d=f}return d}function mn(n){return n&&n.length?tn(n,$,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=x(r,n);for(var a=-1,f=r.length,i=f-1,s=n;s!=null&&++a<f;){var d=c(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=i){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[a+1])?[]:{})}T(s,d,u),s=s[d]}return n}function on(n,r,t){for(var e=-1,a=r.length,f={};++e<a;){var i=r[e],s=N(n,i);t(s,i)&&an(f,x(i,n),s)}return f}export{rn as a,tn as b,V as c,on as d,mn as e,fn as f,hn as g,vn as h,dn as i,D as j,un as l,gn as m,Y as t};
1
+ import{e as o,c as b,g as v,k as O,h as P,j as p,l as w,m as A,n as x,t as c,o as N}from"./_baseUniq-7Lo40p8f.js";import{as as g,aa as y,at as E,au as F,av as M,aw as I,ax as _,ay as $,az as B,aA as T}from"./mermaid.core-CF1Z-P7X.js";var S=/\s/;function z(n){for(var r=n.length;r--&&S.test(n.charAt(r)););return r}var G=/^\s+/;function H(n){return n&&n.slice(0,z(n)+1).replace(G,"")}var m=NaN,L=/^[-+]0x[0-9a-f]+$/i,R=/^0b[01]+$/i,q=/^0o[0-7]+$/i,C=parseInt;function K(n){if(typeof n=="number")return n;if(o(n))return m;if(g(n)){var r=typeof n.valueOf=="function"?n.valueOf():n;n=g(r)?r+"":r}if(typeof n!="string")return n===0?n:+n;n=H(n);var t=R.test(n);return t||q.test(n)?C(n.slice(2),t?2:8):L.test(n)?m:+n}var W=1/0,X=17976931348623157e292;function Y(n){if(!n)return n===0?n:0;if(n=K(n),n===W||n===-1/0){var r=n<0?-1:1;return r*X}return n===n?n:0}function D(n){var r=Y(n),t=r%1;return r===r?t?r-t:r:0}function fn(n){var r=n==null?0:n.length;return r?b(n):[]}var l=Object.prototype,J=l.hasOwnProperty,dn=y(function(n,r){n=Object(n);var t=-1,e=r.length,a=e>2?r[2]:void 0;for(a&&E(r[0],r[1],a)&&(e=1);++t<e;)for(var f=r[t],i=F(f),s=-1,d=i.length;++s<d;){var u=i[s],h=n[u];(h===void 0||M(h,l[u])&&!J.call(n,u))&&(n[u]=f[u])}return n});function un(n){var r=n==null?0:n.length;return r?n[r-1]:void 0}function Q(n){return function(r,t,e){var a=Object(r);if(!I(r)){var f=v(t);r=O(r),t=function(s){return f(a[s],s,a)}}var i=n(r,t,e);return i>-1?a[f?r[i]:i]:void 0}}var U=Math.max;function Z(n,r,t){var e=n==null?0:n.length;if(!e)return-1;var a=t==null?0:D(t);return a<0&&(a=U(e+a,0)),P(n,v(r),a)}var hn=Q(Z);function V(n,r){var t=-1,e=I(n)?Array(n.length):[];return p(n,function(a,f,i){e[++t]=r(a,f,i)}),e}function gn(n,r){var t=_(n)?w:V;return t(n,v(r))}var j=Object.prototype,k=j.hasOwnProperty;function nn(n,r){return n!=null&&k.call(n,r)}function vn(n,r){return n!=null&&A(n,r,nn)}function rn(n,r){return n<r}function tn(n,r,t){for(var e=-1,a=n.length;++e<a;){var f=n[e],i=r(f);if(i!=null&&(s===void 0?i===i&&!o(i):t(i,s)))var s=i,d=f}return d}function mn(n){return n&&n.length?tn(n,$,rn):void 0}function an(n,r,t,e){if(!g(n))return n;r=x(r,n);for(var a=-1,f=r.length,i=f-1,s=n;s!=null&&++a<f;){var d=c(r[a]),u=t;if(d==="__proto__"||d==="constructor"||d==="prototype")return n;if(a!=i){var h=s[d];u=void 0,u===void 0&&(u=g(h)?h:B(r[a+1])?[]:{})}T(s,d,u),s=s[d]}return n}function on(n,r,t){for(var e=-1,a=r.length,f={};++e<a;){var i=r[e],s=N(n,i);t(s,i)&&an(f,x(i,n),s)}return f}export{rn as a,tn as b,V as c,on as d,mn as e,fn as f,hn as g,vn as h,dn as i,D as j,un as l,gn as m,Y as t};