moondesk 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +327 -0
- package/npm/moondesk.js +27 -0
- package/npm/postinstall.js +96 -0
- package/package.json +33 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 xeift.eth
|
|
4
|
+
Copyright (c) 2026 Shattermoon
|
|
5
|
+
|
|
6
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
7
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
8
|
+
in the Software without restriction, including without limitation the rights
|
|
9
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
10
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
11
|
+
furnished to do so, subject to the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be included in all
|
|
14
|
+
copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
17
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
18
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
19
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
20
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
21
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
22
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
# MoonDesk
|
|
2
|
+
|
|
3
|
+
An open-source tool that lets you use ChatGPT Chat as a local coding agent. No reverse engineering, no API, no Codex, no Work mode. A ChatGPT Plus subscription is enough.
|
|
4
|
+
|
|
5
|
+
> [!NOTE]
|
|
6
|
+
> MoonDesk is maintained by **Shattermoon** and is built around a lightweight local MCP architecture. Its companion, **ClippyMoon**, selects a bright hand-tuned pixel-art identity from a deterministic random seed.
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<img src="docs/images/moondesk_preview.gif" alt="MoonDesk in ChatGPT Web"><br>
|
|
10
|
+
<em>MoonDesk in ChatGPT Web</em>
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
# Disclaimer
|
|
14
|
+
|
|
15
|
+
This is an independent open-source project and is not affiliated with or endorsed by OpenAI. I built it as a personal tool and decided to open-source it. Some features are still buggy and may cause unexpected behavior. Use it at your own risk. I am not responsible for any loss caused by this tool. It is strongly recommended to run it inside a VM or container.
|
|
16
|
+
|
|
17
|
+
# Why MoonDesk?
|
|
18
|
+
|
|
19
|
+
Codex has a very generous weekly quota (reset usage frequently) compared to Antigravity (good at good morning) and Claude Code (RIP 5h quota 💀), that's why I love OpenAI so much.
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="docs/images/codex_2x_usage.png" alt="Codex reset usage frequently🙏" width="700"><br>
|
|
23
|
+
<em>Codex reset usage frequently🙏</em>
|
|
24
|
+
</p>
|
|
25
|
+
|
|
26
|
+
However, the quota runs out very quickly if you work on a large project.
|
|
27
|
+
|
|
28
|
+
<p align="center">
|
|
29
|
+
<img src="docs/images/no_remaining_usage.png" alt="I used up my Codex quota on the first day after it reset" width="700"><br>
|
|
30
|
+
<em>I used up my Codex quota on the first day after it reset</em>
|
|
31
|
+
</p>
|
|
32
|
+
|
|
33
|
+
Then you need to wait another 7 days. What are you going to do for the rest of the week?
|
|
34
|
+
|
|
35
|
+
Here's the solution: most people with a Plus subscription do not use even 10% of their weekly thinking messages.
|
|
36
|
+
|
|
37
|
+
**_So why not use your 3,000 weekly messages for coding?_**
|
|
38
|
+
|
|
39
|
+
That's the idea behind MoonDesk! It gives ChatGPT Web tools like `write` and `run_command` to edit files on your computer.
|
|
40
|
+
|
|
41
|
+
<p align="center">
|
|
42
|
+
<img src="docs/images/thinking_usage_limits.png" alt="ChatGPT reasoning usage limits for GPT-5.5 and GPT-5.6" width="900"><br>
|
|
43
|
+
<em>GPT-5.5: <a href="https://web.archive.org/web/20260519111010/https://help.openai.com/en/articles/11909943-gpt-55-in-chatgpt">3,000 messages/week</a>, GPT-5.6: <a href="https://help.openai.com/en/articles/20001354-gpt-56-in-chatgpt">unknown</a> but I have never hit the limit</em>
|
|
44
|
+
</p>
|
|
45
|
+
|
|
46
|
+
# How does this work?
|
|
47
|
+
|
|
48
|
+
1. A ChatGPT Plus or above subscription is required.
|
|
49
|
+
2. MoonDesk runs as a local MCP server on your computer. It has the ability to run commands and edit files, just like Codex.
|
|
50
|
+
3. You can connect ChatGPT Web to MoonDesk using a Custom Connector, which is a feature available only to Plus and Pro users.
|
|
51
|
+
4. Done! Now ChatGPT Web can control your computer and code on it.
|
|
52
|
+
|
|
53
|
+
In short,
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
ChatGPT Web + MoonDesk
|
|
57
|
+
= a stripped-down version of Codex
|
|
58
|
+
= OpenClaw without cron and other active utilities
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
I tried this with GPT-5.2 before, and the results were poor. However, **GPT-5.4 Thinking is now really good at tool calling and computer use.** The first time I tried it with GPT-5.4, I was honestly surprised by how well it worked. GPT-5.5 and GPT-5.6 are even smoother, and GPT-5.6 is extremely good at using MoonDesk. It's also very fast.
|
|
62
|
+
|
|
63
|
+
# Differences between ChatGPT Chat + MoonDesk, Codex, and the API (let's say Plus plan)
|
|
64
|
+
|
|
65
|
+
| | ChatGPT Chat + MoonDesk | Codex | OpenAI API |
|
|
66
|
+
| ----- | -------------------------------------------------- | ----------------------- | -------------------- |
|
|
67
|
+
| Usage | 3,000 messages/week | Generous weekly quota | Pay as you go |
|
|
68
|
+
| Pros | Stable, no extra fee, and nearly unlimited\* quota | Stable and no extra fee | Stable |
|
|
69
|
+
| Cons | Not as smooth as native Codex | Runs out very quickly | Tokens are expensive |
|
|
70
|
+
|
|
71
|
+
\*Let's say you sleep 6 hours a day and use MoonDesk every day. In that case, you can send 3,000 / (24 - 6) / 7 = 23.8 messages per hour. Since thinking and tool calls take time, it is very difficult to use up your weekly 3,000 message limit.
|
|
72
|
+
|
|
73
|
+
# Who needs this?
|
|
74
|
+
|
|
75
|
+
- People who used up their Codex quota on the first few day after it reset (me🥺)
|
|
76
|
+
- People who are working on web development and crawlers. (MoonDesk enables ChatGPT Web to read elements and control your browser tab through chrome-devtools-mcp integration.)
|
|
77
|
+
|
|
78
|
+
# Quickstart
|
|
79
|
+
|
|
80
|
+
> [!CAUTION]
|
|
81
|
+
> This tool is very powerful and can potentially wipe your whole disk or produce unexpected results.
|
|
82
|
+
> Run it inside a VM or container (DevContainer is a good option).
|
|
83
|
+
> Treat it like OpenClaw, keep it containerized and isolated.
|
|
84
|
+
|
|
85
|
+
1. Install MoonDesk globally with npm.
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
npm install -g moondesk
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
2. Run MoonDesk from any terminal directory.
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
moondesk
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
When MoonDesk starts, choose `Control Computer`, `Control Browser`, or `Both`. If browser control is enabled, select a supported Chromium browser.
|
|
98
|
+
|
|
99
|
+
On first launch, MoonDesk will ask you to enter your **ngrok authtoken** and **ngrok static domain** (e.g. `my-app.ngrok-free.dev`). You can get both from the [ngrok dashboard](https://dashboard.ngrok.com/get-started/setup). These are saved to `~/.moondesk/config.toml` and reused on subsequent launches.
|
|
100
|
+
|
|
101
|
+
By default, MoonDesk listens on port `3200`. You can override it with `PORT`. On a first install (or when migrating the legacy single-workspace config), MoonDesk creates the first workspace from `WORKSPACE_ROOT` if it is set, otherwise from the directory where MoonDesk was launched. Once the multi-workspace registry exists, launching MoonDesk from a different directory never repoints an existing connector. If a MoonDesk host is already running on that port, running `moondesk` from another project directory securely attaches that directory as a new workspace to the existing host and exits; you can also add or change projects from the `[w] Workspaces` screen.
|
|
102
|
+
|
|
103
|
+
On macOS Terminal.app, MoonDesk manages a dedicated `MoonDesk` Terminal profile automatically. If the current Terminal tab is not already using that profile, MoonDesk applies it, closes any temporary helper window, and asks you to run the same command again in that tab. It only starts immediately when the current tab is already using `MoonDesk`. Set `MOONDESK_SKIP_MACOS_TERMINAL_PROFILE=1` if you want to keep the current Terminal session untouched.
|
|
104
|
+
|
|
105
|
+
3. Wait for the TUI to connect ngrok. Press `[w] Workspaces` to see the registered projects. Select a workspace and reveal/copy its MCP URL. The first workspace is created automatically; existing users keep their old workspace slug during migration, so the already configured connector URL does not change.
|
|
106
|
+
|
|
107
|
+
4. Open [ChatGPT connector settings](https://chatgpt.com/plugins#settings/Connectors?create-connector=true&redirectAfter=%2Fplugins).
|
|
108
|
+
|
|
109
|
+
5. In the pop-up window, fill in the connector form:
|
|
110
|
+
- Name: `MoonDesk · <workspace name>` (for example `MoonDesk · SiteAI`)
|
|
111
|
+
- MCP Server URL: the URL for that specific workspace from `[w] Workspaces`
|
|
112
|
+
- Authentication: `None`
|
|
113
|
+
|
|
114
|
+
6. Click `I understand and want to continue`.
|
|
115
|
+
|
|
116
|
+
7. Click `Create`, then click `Connect`.
|
|
117
|
+
|
|
118
|
+
- Permission defaults to **Allow read actions**. For the smoothest experience, I recommend **Allow all actions** (equivalent to Codex's `--yolo`; use with caution).
|
|
119
|
+
|
|
120
|
+
8. Add this to your ChatGPT `Custom instructions`:
|
|
121
|
+
|
|
122
|
+
```text
|
|
123
|
+
MoonDesk is a coding tool and a custom connector. Always use MoonDesk if the user wants to do anything related to file operations. Always call `moondesk_instruction` after `list_resources`, and follow the instructions it contains.
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
9. Start using the connector from ChatGPT Web. Some important tips:
|
|
127
|
+
|
|
128
|
+
- I recommend let ChatGPT to decide which connector automatically. You can manually selecting the connector using `/` or `@`. This way, ChatGPT can only access the connector you selected, which may improve stability. However, the downside is, `web.search` and `web.open` will be disabled. Which means it can't search latest info. The `web` tool and a custom connector cannot be used at the same time.
|
|
129
|
+
|
|
130
|
+
<table align="center">
|
|
131
|
+
<tr>
|
|
132
|
+
<td align="center">
|
|
133
|
+
<img src="docs/images/connector_slash.png" alt="Select MoonDesk from the slash command menu" width="300"><br>
|
|
134
|
+
<em>Select MoonDesk manually with <code>/</code></em>
|
|
135
|
+
</td>
|
|
136
|
+
<td align="center">
|
|
137
|
+
<img src="docs/images/connector_at.png" alt="Select MoonDesk from the at-sign menu" width="300"><br>
|
|
138
|
+
<em>Select MoonDesk manually with <code>@</code></em>
|
|
139
|
+
</td>
|
|
140
|
+
</tr>
|
|
141
|
+
</table>
|
|
142
|
+
|
|
143
|
+
- To improve performance and avoid high memory usage, I strongly recommend **opening a new session for every small feature**. If you need context, you can ask ChatGPT to create a handoff note and paste it into the new session. It will become extremely laggy after 50+ tool calls.
|
|
144
|
+
<p align="center">
|
|
145
|
+
<img src="docs/images/high_ram_usage.png" alt="3.9 GB Memory usage🥹" width="300"><br>
|
|
146
|
+
<em>3.9 GB Memory usage🥹</em>
|
|
147
|
+
</p>
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
## Working on multiple projects at the same time
|
|
151
|
+
|
|
152
|
+
One MoonDesk process can host multiple project roots concurrently. It still uses one local server, one port, one ngrok tunnel/domain, and one shared browser/DevTools bridge. Each workspace gets its own stable internal ID and its own random secret MCP path.
|
|
153
|
+
|
|
154
|
+
Press `[w] Workspaces` to add, rename, inspect, reveal/copy, rotate, or remove projects. You can paste/type a path with `[a] Path`, open the native folder picker with `[b] Browse`, and click project rows plus the Reveal/Copy actions with the mouse. Create one ChatGPT connector per workspace and select the matching connector in each ChatGPT chat. The endpoint itself selects the project; MoonDesk does not add workspace metadata or a workspace argument to every tool call.
|
|
155
|
+
|
|
156
|
+
```text
|
|
157
|
+
one MoonDesk process / port 3200 / ngrok domain
|
|
158
|
+
├── MoonDesk · SiteAI -> /<secret-A>/mcp -> D:\SiteAI
|
|
159
|
+
├── MoonDesk · KUBA -> /<secret-B>/mcp -> D:\KUBA
|
|
160
|
+
└── MoonDesk · MoonDesk -> /<secret-C>/mcp -> D:\CatDesk
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Important behavior:
|
|
164
|
+
|
|
165
|
+
- A workspace keeps its own normal command/job/output and local-history allowances; one busy project does not consume another project's normal quota.
|
|
166
|
+
- Browser control is intentionally shared. If two workspace connectors use Browser/Both mode, they control the same selected browser/DevTools bridge.
|
|
167
|
+
- Rotating a workspace secret immediately invalidates only that workspace's old URL. Update that ChatGPT connector to the newly revealed URL; other workspace URLs are unchanged.
|
|
168
|
+
- Removing a workspace first blocks new requests/jobs and lets already accepted foreground/file work drain safely. Only after the registry removal is durably saved does MoonDesk cancel that workspace's background jobs and purge retained state; an aborted/failed removal re-enables the workspace without killing its running jobs.
|
|
169
|
+
- A missing workspace directory (for example an unplugged drive) stays registered and is shown as unavailable. It becomes usable again when the directory returns.
|
|
170
|
+
- Duplicate and parent/child-overlapping workspace roots are rejected in V1 so two connectors cannot accidentally claim overlapping dedicated-file-tool authority.
|
|
171
|
+
- Starting `moondesk` from another project directory while the normal host is already running does not create a second host. The new invocation securely attaches its current directory to the existing host as a workspace/session, then exits. The same project can also be added from `[w] Workspaces`.
|
|
172
|
+
|
|
173
|
+
# Stack
|
|
174
|
+
|
|
175
|
+
| Part | Stack |
|
|
176
|
+
| --- | --- |
|
|
177
|
+
| Core | Rust |
|
|
178
|
+
| MCP server | Custom implementation (no SDK) |
|
|
179
|
+
| MCP protocolVersion | `2025-11-25` |
|
|
180
|
+
| Server | Axum + Tokio |
|
|
181
|
+
| TUI | Ratatui |
|
|
182
|
+
| Tunnel | ngrok |
|
|
183
|
+
| Browser control | chrome-devtools-mcp |
|
|
184
|
+
| Distribution | npm |
|
|
185
|
+
|
|
186
|
+
# Tools
|
|
187
|
+
|
|
188
|
+
MoonDesk has two local tool modes: `multi-tools` exposes 11 tools, and `read-only` exposes 3 tools.
|
|
189
|
+
|
|
190
|
+
MoonDesk's local tools in `multi-tools` mode are:
|
|
191
|
+
|
|
192
|
+
| Tool | Type | What it does |
|
|
193
|
+
| --------------------- | ----- | -------------------------------------------------------------------------- |
|
|
194
|
+
| `moondesk_instruction` | Guide | Returns MoonDesk usage instructions |
|
|
195
|
+
| `read` | Read | Reads bounded line ranges or byte chunks from a workspace text file |
|
|
196
|
+
| `search` | Read | Searches workspace text and returns compact bounded results |
|
|
197
|
+
| `write` | Write | Creates or overwrites a file |
|
|
198
|
+
| `edit` | Write | Replaces exact text inside a file |
|
|
199
|
+
| `delete` | Write | Deletes a file or directory |
|
|
200
|
+
| `run_command` | Shell | Runs a short shell command and waits for completion |
|
|
201
|
+
| `start_command` | Job | Starts a long-running shell command and immediately returns a job ID |
|
|
202
|
+
| `poll_command` | Job | Reads incremental output and status from a background command |
|
|
203
|
+
| `read_command_output` | Read | Reads bounded chunks from complete preserved command stdout/stderr |
|
|
204
|
+
| `cancel_command` | Job | Stops a background command and its child process tree |
|
|
205
|
+
|
|
206
|
+
Long-running commands are deliberately decoupled from the lifetime of an MCP HTTP request. Builds, compilation, dependency installation, long test suites, development servers, and commands expected to produce large output should use `start_command`, then `poll_command`. The first poll uses `after: 0`; every later poll must pass the previous `nextCursor`, so each response contains only new output. Poll responses stay bounded. Complete stdout/stderr is also preserved locally for the MoonDesk session, so if a poll reports `outputTruncated`, `read_command_output` can recover either full stream in bounded byte chunks using the same job ID. `run_command` remains the simpler path for short commands; if its inline 1 MiB-per-stream capture is exceeded, it returns an `outputId` that `read_command_output` can use instead of permanently discarding the overflow.
|
|
207
|
+
|
|
208
|
+
On wide terminals, the lower TUI is split into a compact Logs pane and a larger Shell Commands pane. Shell Commands is focused by default. `Tab`/`Shift+Tab` switches pane focus; arrow keys, Page Up/Down, and the mouse wheel select/scroll entries in the focused or pointed pane; Home/End jumps to the first/latest entry. Clicking a rendered row selects it. Long log messages and shell commands stay compact with an explicit ellipsis; press `Enter` or `Space` to expand the selected row and wrap its full locally stored text, then `Esc` to collapse it. Shell command entries always keep one blank line between executions for readability. Each pane keeps independent scroll/follow state, so inspecting command history no longer moves the Logs pane.
|
|
209
|
+
|
|
210
|
+
On sufficiently wide terminals, the bottom of the MoonDesk TUI is split into `Logs` and `Shell Commands`. The command panel is local-only: it shows `run_command`/`start_command` immediately when the request arrives, then updates that command with bounded progress/result previews from later responses and polls. This does not add command data or result previews to ChatGPT's MCP responses.
|
|
211
|
+
|
|
212
|
+
`read` defaults to 200 lines and exposes `nextStartLine` for normal pagination. If one line is too large for a single response, MoonDesk returns a bounded byte chunk and `nextStartByte`; continue with `start_byte`/`max_bytes` so minified files, source maps, and other long-line text remain fully inspectable without one huge tool result.
|
|
213
|
+
|
|
214
|
+
If browser mode is enabled, MoonDesk can also expose extra browser/devtools tools. Those are provided by the browser bridge, so the exact list depends on your environment.
|
|
215
|
+
|
|
216
|
+
`search` uses `rg` when it is available, falls back to `grep`, then falls back to MoonDesk's built-in scanner. Installing ripgrep is optional, but gives the best search performance and behavior.
|
|
217
|
+
|
|
218
|
+
# Context window
|
|
219
|
+
|
|
220
|
+
According to [the blog](<https://help.openai.com/en/articles/11909943-gpt-53-and-gpt-54-in-chatgpt#:~:text=Thinking%20(GPT%E2%80%915.4%20Thinking)>) and [the code](https://github.com/openai/codex/blob/main/codex-rs/models-manager/src/model_info.rs#L85), the context window in ChatGPT web is different from Codex.
|
|
221
|
+
|
|
222
|
+
| Tier | MoonDesk + ChatGPT Web (in + out = sum) | Codex CLI (sum) |
|
|
223
|
+
| ---- | -------------------------------------- | ---------------------- |
|
|
224
|
+
| Plus | 128K + 128K = 256K | 258K (1M experimental) |
|
|
225
|
+
| Pro | 272K + 128K = 400K | 258K (1M experimental) |
|
|
226
|
+
|
|
227
|
+
# FAQ
|
|
228
|
+
|
|
229
|
+
### I've already connected. Why do I need to connect again and again?
|
|
230
|
+
|
|
231
|
+
There doesn't seem to be any obvious pattern for when the connector triggers `Connect`. I'm sure it's not triggered by the tool call count, but I don't know the exact reason.
|
|
232
|
+
|
|
233
|
+
<table align="center">
|
|
234
|
+
<tr>
|
|
235
|
+
<td align="center">
|
|
236
|
+
<img src="docs/images/connect1.png" alt="Connector asks to connect again" width="700"><br>
|
|
237
|
+
<em>Connector asks to connect again</em>
|
|
238
|
+
</td>
|
|
239
|
+
<td align="center">
|
|
240
|
+
<img src="docs/images/connect2.png" alt="Connector asks to connect again (After you click Continue)" width="700"><br>
|
|
241
|
+
<em>Connector asks to connect again (After you click Continue)</em>
|
|
242
|
+
</td>
|
|
243
|
+
</tr>
|
|
244
|
+
</table>
|
|
245
|
+
|
|
246
|
+
I know it’s annoying. I’m trying to find a solution now.
|
|
247
|
+
|
|
248
|
+
### Can MoonDesk be used in other apps?
|
|
249
|
+
|
|
250
|
+
Yes, in theory. MoonDesk may also work with other apps that support custom remote MCP servers, including Claude. (I don't think anyone will use MoonDesk with Claude though, since Claude Chat mode and Claude Code share the same usage limits.)
|
|
251
|
+
|
|
252
|
+
However, MoonDesk is built specifically for ChatGPT Chat and its Custom Connector (They renamed it to _Apps_, and now they renamed it again and call it _Plugins_, but to prevent confusion with _Application_, I still prefer call it _Connector_) flow. ChatGPT Chat is the environment MoonDesk is designed and tested for, so other apps may not work as smoothly.
|
|
253
|
+
|
|
254
|
+
### How does the input/output token be calculated?
|
|
255
|
+
|
|
256
|
+
MoonDesk does not get official token usage numbers from ChatGPT Web. It estimates them locally with `o200k_base`, the same tokenizer family used by GPT-5.5-style models, so the numbers are useful, but still only estimates.
|
|
257
|
+
|
|
258
|
+
| Field | Symbol | What it means | Price |
|
|
259
|
+
| -------------- | ------ | ---------------------------- | ----------------------------- |
|
|
260
|
+
| `inputTokens` | `↓` | Tool input ≈ LLM output | ≈ `$30.00 / 1M` output tokens |
|
|
261
|
+
| `outputTokens` | `↑` | Tool output ≈ LLM input | ≈ `$5.00 / 1M` input tokens |
|
|
262
|
+
| `totalTokens` | `Σ` | `inputTokens + outputTokens` | `input price + output price` |
|
|
263
|
+
|
|
264
|
+
MoonDesk does not count:
|
|
265
|
+
|
|
266
|
+
- the full ChatGPT conversation
|
|
267
|
+
- hidden prompts or reasoning tokens
|
|
268
|
+
- other internal tokens on OpenAI's side
|
|
269
|
+
|
|
270
|
+
These estimates stay local to MoonDesk for its own counters and are not attached to MCP tool responses sent back to ChatGPT.
|
|
271
|
+
|
|
272
|
+
### What is a workspace?
|
|
273
|
+
|
|
274
|
+
A workspace is one registered project root served by MoonDesk. One running MoonDesk host can keep multiple workspaces active at the same time, and each workspace has its own secret MCP URL for its ChatGPT connector.
|
|
275
|
+
|
|
276
|
+
On first install/legacy migration, the initial workspace comes from `WORKSPACE_ROOT` when explicitly set, otherwise from the launch directory. After the workspace registry has been created, changing the launch directory or `WORKSPACE_ROOT` does not silently repoint existing workspace URLs. Use `[w] Workspaces` to add or manage projects.
|
|
277
|
+
|
|
278
|
+
Dedicated file tools use the selected endpoint's workspace as their base path and reject traversal, absolute paths outside that root, and symlink/junction escapes. `run_command` and `start_command` are different: they start a real shell with the workspace as its working directory, but CWD is **not an OS sandbox**. A deliberately written absolute-path shell command can access other paths allowed by the operating system. Use a VM/container if you need OS-level isolation.
|
|
279
|
+
|
|
280
|
+
### Where to put my AGENTS.md?
|
|
281
|
+
|
|
282
|
+
You can put it in 3 places.
|
|
283
|
+
|
|
284
|
+
1. Workspace root
|
|
285
|
+
2. `~/.moondesk/AGENTS.md`
|
|
286
|
+
3. `~/.codex/AGENTS.md`
|
|
287
|
+
|
|
288
|
+
MoonDesk checks these locations for `AGENTS.md` in this order. This happens every time `moondesk_instruction` is called. You can also manually choose which `AGENTS.md` to use.
|
|
289
|
+
|
|
290
|
+
<p align="center">
|
|
291
|
+
<img src="docs/images/set_agents_md.png" alt="Set AGENTS.md manually" width="500"><br>
|
|
292
|
+
<em>Set AGENTS.md manually</em>
|
|
293
|
+
</p>
|
|
294
|
+
|
|
295
|
+
# Safety
|
|
296
|
+
|
|
297
|
+
> [!CAUTION]
|
|
298
|
+
> Do **NOT** share any workspace's `MCP Server URL` with anyone. Each URL is a secret credential for that workspace and can expose powerful local tools.
|
|
299
|
+
|
|
300
|
+
A workspace URL is made of these parts:
|
|
301
|
+
|
|
302
|
+
| Part | Example | What it means |
|
|
303
|
+
| ------------ | ----------------------------- | -------------------------------------------------- |
|
|
304
|
+
| Public URL | `https://xxxx.ngrok-free.dev` | The one ngrok static domain shared by the host |
|
|
305
|
+
| Random path | `/Ab3kL9xQ2pTm7VhC` | A different random secret path for each workspace |
|
|
306
|
+
| MCP endpoint | `/mcp` | The actual MCP endpoint |
|
|
307
|
+
|
|
308
|
+
So one workspace URL looks like this:
|
|
309
|
+
|
|
310
|
+
```text
|
|
311
|
+
https://xxxx.ngrok-free.dev/Ab3kL9xQ2pTm7VhC/mcp
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The domain and workspace registry are persisted in `~/.moondesk/config.toml`, so workspace URLs remain stable across launches. Upgrading an old single-workspace installation preserves its existing slug. If a workspace URL is exposed, rotate **that workspace's** secret from `[w] Workspaces`; its previous URL stops working immediately and the corresponding ChatGPT connector must be updated. Other workspaces are unaffected.
|
|
315
|
+
|
|
316
|
+
# About ClippyMoon
|
|
317
|
+
|
|
318
|
+
<p align="center">
|
|
319
|
+
<img src="docs/images/clippymoon.gif" alt="ClippyMoon!" width="500"><br>
|
|
320
|
+
<em>ClippyMoon!</em>
|
|
321
|
+
</p>
|
|
322
|
+
|
|
323
|
+
ClippyMoon is MoonDesk's hand-tuned pixel-art lunar companion. It is rendered entirely in Rust from a random 64-bit seed when MoonDesk starts; no image-generation model or bundled sprite sheet is used.
|
|
324
|
+
|
|
325
|
+
A seed now selects from a curated library of known-good bright identities instead of independently randomizing every visual trait. Normal mascots use only quarter, gibbous, and full shapes, so a seed can no longer produce an almost-black new/crescent moon. The color families are pale ivory, silver, warm yellow, harvest orange, and coral red, with deliberately light same-hue shadow tones. Crater and star arrangements are hand-authored layouts rather than random placements, and per-pixel noise is avoided so every seed stays clean at terminal scale. Animation changes only temporary frame state such as blinking, subtle one-pixel bobbing, and star twinkling, so the same seed always recreates the same character.
|
|
326
|
+
|
|
327
|
+
Normal MoonDesk startup keeps ClippyMoon entirely in memory: nothing is archived or persisted and each launch creates a fresh random moon. The current moon's 16-digit hexadecimal seed is written once to the TUI log so it can be reproduced without saving mascot state. Export is explicit and opt-in. Run `moondesk clippymoon export` to create `clippymoon.png` and `clippymoon.gif` in the current directory, or use `--seed <hex>` to reproduce a specific moon and `--out <directory>` to choose the destination. Both exports are 512×512 pixel art; the GIF uses the same idle-animation sequence as the TUI and loops indefinitely. MoonDesk never writes these files unless the export command is invoked.
|
package/npm/moondesk.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require("node:child_process");
|
|
4
|
+
const path = require("node:path");
|
|
5
|
+
|
|
6
|
+
const executableName = process.platform === "win32" ? "moondesk.exe" : "moondesk";
|
|
7
|
+
const binaryPath = path.join(__dirname, "bin", executableName);
|
|
8
|
+
|
|
9
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
10
|
+
cwd: process.cwd(),
|
|
11
|
+
env: process.env,
|
|
12
|
+
stdio: "inherit",
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
child.on("error", (error) => {
|
|
16
|
+
console.error(`MoonDesk failed to start: ${error.message}`);
|
|
17
|
+
console.error("Reinstall MoonDesk after the matching GitHub Release binary is available.");
|
|
18
|
+
process.exit(1);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
child.on("exit", (code, signal) => {
|
|
22
|
+
if (signal) {
|
|
23
|
+
process.kill(process.pid, signal);
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
process.exit(code ?? 1);
|
|
27
|
+
});
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const crypto = require("node:crypto");
|
|
4
|
+
const fs = require("node:fs");
|
|
5
|
+
const path = require("node:path");
|
|
6
|
+
|
|
7
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
8
|
+
const packageJson = require(path.join(packageRoot, "package.json"));
|
|
9
|
+
const version = packageJson.version;
|
|
10
|
+
const releaseTag = `v${version}`;
|
|
11
|
+
const releaseBaseUrl = `https://github.com/Shattermoon/moondesk/releases/download/${releaseTag}`;
|
|
12
|
+
|
|
13
|
+
const supportedTargets = new Set([
|
|
14
|
+
"linux-x64",
|
|
15
|
+
"linux-arm64",
|
|
16
|
+
"darwin-x64",
|
|
17
|
+
"darwin-arm64",
|
|
18
|
+
"win32-x64",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
const platform = process.platform;
|
|
22
|
+
const arch = process.arch;
|
|
23
|
+
const target = `${platform}-${arch}`;
|
|
24
|
+
|
|
25
|
+
if (!supportedTargets.has(target)) {
|
|
26
|
+
console.error(`MoonDesk does not provide a prebuilt binary for ${target}.`);
|
|
27
|
+
console.error(`Supported targets: ${Array.from(supportedTargets).join(", ")}`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const assetName = platform === "win32" ? `moondesk-${target}.exe` : `moondesk-${target}`;
|
|
32
|
+
const executableName = platform === "win32" ? "moondesk.exe" : "moondesk";
|
|
33
|
+
const binDir = path.join(__dirname, "bin");
|
|
34
|
+
const installedBinary = path.join(binDir, executableName);
|
|
35
|
+
|
|
36
|
+
async function fetchRequired(url) {
|
|
37
|
+
const response = await fetch(url, {
|
|
38
|
+
headers: {
|
|
39
|
+
"User-Agent": `moondesk-npm-install/${version}`,
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
if (!response.ok) {
|
|
44
|
+
throw new Error(`${url} returned HTTP ${response.status}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return response;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function downloadBuffer(url) {
|
|
51
|
+
const response = await fetchRequired(url);
|
|
52
|
+
return Buffer.from(await response.arrayBuffer());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
async function downloadText(url) {
|
|
56
|
+
const response = await fetchRequired(url);
|
|
57
|
+
return response.text();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function expectedSha256(checksums, name) {
|
|
61
|
+
for (const line of checksums.split(/\r?\n/)) {
|
|
62
|
+
const match = line.trim().match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
|
|
63
|
+
if (match && path.basename(match[2]) === name) {
|
|
64
|
+
return match[1].toLowerCase();
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
throw new Error(`SHA256SUMS does not contain ${name}`);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function sha256(buffer) {
|
|
72
|
+
return crypto.createHash("sha256").update(buffer).digest("hex");
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
async function main() {
|
|
76
|
+
const checksums = await downloadText(`${releaseBaseUrl}/SHA256SUMS`);
|
|
77
|
+
const expected = expectedSha256(checksums, assetName);
|
|
78
|
+
const binary = await downloadBuffer(`${releaseBaseUrl}/${assetName}`);
|
|
79
|
+
const actual = sha256(binary);
|
|
80
|
+
|
|
81
|
+
if (actual !== expected) {
|
|
82
|
+
throw new Error(`Checksum mismatch for ${assetName}: expected ${expected}, got ${actual}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
fs.mkdirSync(binDir, { recursive: true });
|
|
86
|
+
fs.writeFileSync(installedBinary, binary);
|
|
87
|
+
|
|
88
|
+
if (platform !== "win32") {
|
|
89
|
+
fs.chmodSync(installedBinary, 0o755);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
main().catch((error) => {
|
|
94
|
+
console.error(`MoonDesk install failed: ${error.message}`);
|
|
95
|
+
process.exit(1);
|
|
96
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "moondesk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Use ChatGPT Chat as a local coding agent.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "https://github.com/Shattermoon/moondesk"
|
|
9
|
+
},
|
|
10
|
+
"homepage": "https://github.com/Shattermoon/moondesk#readme",
|
|
11
|
+
"bugs": {
|
|
12
|
+
"url": "https://github.com/Shattermoon/moondesk/issues"
|
|
13
|
+
},
|
|
14
|
+
"bin": {
|
|
15
|
+
"moondesk": "npm/moondesk.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"postinstall": "node npm/postinstall.js"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"npm/moondesk.js",
|
|
22
|
+
"npm/postinstall.js",
|
|
23
|
+
"LICENSE",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
},
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"registry": "https://registry.npmjs.org/"
|
|
32
|
+
}
|
|
33
|
+
}
|