muse-cli 0.2.1__tar.gz → 0.3.0__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: muse-cli
3
- Version: 0.2.1
3
+ Version: 0.3.0
4
4
  Summary: Command-line client for your personal muse.ai AI agent: chat, automate, and manage side chats, feed, goals, ideas, and sessions from the terminal. No browser needed.
5
5
  Project-URL: Homepage, https://github.com/nikships/muse-cli
6
6
  Project-URL: Documentation, https://github.com/nikships/muse-cli#readme
@@ -66,29 +66,124 @@ A command-line client for your personal muse.ai AI agent: chat from the terminal
66
66
  uv tool install muse-cli # or: pipx install muse-cli / pip install muse-cli
67
67
  ```
68
68
 
69
- Then log in and check the connection:
69
+ The command is `muse-cli`. The name `muse` belongs to Muse Code on many machines.
70
+
71
+ Want the agent skill too? The installer sets up the CLI and copies the skill
72
+ to `~/.agents/skills/muse-cli`:
70
73
 
71
74
  ```bash
72
- # 1. Install agent-browser (reads cookies from Chrome):
75
+ curl -fsSL https://raw.githubusercontent.com/nikships/muse-cli/main/install.sh | bash
76
+ ```
77
+
78
+ Upgrade with `muse-cli update`. In a terminal it also checks PyPI once a day and, when a newer release exists, prints that command on stderr. It does not upgrade itself. Set `MUSE_NO_UPDATE_CHECK=1` to silence the notice. The check stays quiet when output is piped or `CI` is set. Remove with `uv tool uninstall muse-cli`.
79
+
80
+ Logging in is a separate one-time step. Chrome shares its cookies after you
81
+ turn on remote debugging. Follow [Log in once](#log-in-once) before running
82
+ any other command.
83
+
84
+ ## Log in once
85
+
86
+ muse-cli borrows the login you already have in Chrome, saves those cookies
87
+ to `~/.config/muse-cli/cookies.txt` (mode `600`, only your user can read it),
88
+ and after that talks to muse.ai directly. Access tokens are fetched fresh
89
+ on every command. When a command later says the cookies expired, repeat this
90
+ section.
91
+
92
+ Do the steps in order. Each one checks itself before you continue.
93
+
94
+ ### 1. Install the cookie reader
95
+
96
+ `auth export` uses [agent-browser](https://github.com/vercel-labs/agent-browser)
97
+ to read Chrome's cookies. That package comes from npm, so you need Node.js first.
98
+
99
+ ```bash
100
+ node --version # if this fails, install Node.js LTS from https://nodejs.org
101
+ # and open a new terminal
73
102
  npm i -g agent-browser
74
- # 2. Log in to https://muse.ai/ in Chrome
75
- # 3. Export your session (one time; re-run when it expires):
103
+ agent-browser --version
104
+ ```
105
+
106
+ `npm i -g` needs permission to write to npm's global bin directory. If it
107
+ errors with `EACCES`, pick one and use it from then on: configure an
108
+ [npm prefix you own](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally),
109
+ or run the install with the same rights you use for other global npm tools.
110
+ Then check `command -v agent-browser` prints a path.
111
+
112
+ ### 2. Let Chrome share cookies
113
+
114
+ Open **Google Chrome**, the same profile you use for muse.ai. Paste this in
115
+ the address bar:
116
+
117
+ ```
118
+ chrome://inspect/#remote-debugging
119
+ ```
120
+
121
+ Turn on remote debugging. The checkbox reads **Allow remote debugging for
122
+ this browser instance**. Leave Chrome open.
123
+
124
+ Turn this on while Chrome is already open and muse.ai is loaded. The export
125
+ can see that window only after the switch is on. The page is in Chrome 144
126
+ and newer.
127
+
128
+ Stay in this window. Starting a second Chrome with
129
+ `--remote-debugging-port` opens a different profile, and that profile does
130
+ not have your muse.ai login.
131
+
132
+ ### 3. Be logged in, on a muse.ai tab
133
+
134
+ In that same Chrome window, open https://muse.ai/ and log in. Leave the tab
135
+ open. The exporter reads whichever tab is active, so the muse.ai tab has to
136
+ be the one in front when you run the next command.
137
+
138
+ ### 4. Save the login
139
+
140
+ ```bash
76
141
  muse-cli auth export
142
+ ```
77
143
 
78
- muse-cli status
144
+ Chrome may ask to allow the debugging connection. Click **Allow**. If the
145
+ command already failed, run it again after you click Allow.
146
+
147
+ Success looks like this (the count varies):
148
+
149
+ ```
150
+ saved 12 muse.ai cookies to /home/you/.config/muse-cli/cookies.txt
79
151
  ```
80
152
 
81
- Want the agent skill too? The installer sets up the CLI and copies the skill
82
- to `~/.agents/skills/muse-cli`:
153
+ The command prints the fix on the failure itself. These are the ones it
154
+ recognizes:
155
+
156
+ | What it says | What to do |
157
+ | --- | --- |
158
+ | No running Chrome / remote debugging | The switch in step 2 is off. Turn it on in the Chrome you already have open, then run the command again. |
159
+ | No muse.ai tab | Step 3. Open https://muse.ai/ in that same window and leave the tab open. |
160
+ | No `hatch_sess` | The tab is open and you are logged out. Log in, then export again. The cookies file already on disk is left as it was. |
161
+ | `agent-browser` is not installed | Step 1. `node --version`, then `npm i -g agent-browser`, then a new terminal. |
162
+ | Daemon already running | A previous attempt is stuck. `agent-browser close`, then `muse-cli auth export`. |
163
+
164
+ ### 5. Check the connection
83
165
 
84
166
  ```bash
85
- curl -fsSL https://raw.githubusercontent.com/nikships/muse-cli/main/install.sh | bash
167
+ muse-cli status
86
168
  ```
87
169
 
88
- Upgrade with `uv tool upgrade muse-cli`, remove with `uv tool uninstall muse-cli`.
170
+ You get JSON with your VM id, how many chats you have, the unread count, and
171
+ your identity. That means install and login both worked.
172
+
173
+ ### Copy cookies by hand
89
174
 
90
- The command is `muse-cli`, not `muse`, because `muse` clashes with Muse Code
91
- on many machines.
175
+ Use this when you don't have Node, or you don't want remote debugging on.
176
+
177
+ 1. In Chrome, open https://muse.ai/ and log in.
178
+ 2. Open DevTools (F12, or Ctrl+Shift+I) → **Application** → **Cookies** → `https://muse.ai`.
179
+ 3. Copy the cookies onto one line. `hatch_sess` has to be there. Separate cookies with `; ` (semicolon, space). A Netscape cookie jar (what curl writes) and a JSON object such as `{"hatch_sess": "..."}` also work.
180
+
181
+ ```bash
182
+ mkdir -p ~/.config/muse-cli
183
+ printf '%s\n' 'hatch_sess=the-value-from-devtools; other_cookie=other_value' > ~/.config/muse-cli/cookies.txt
184
+ chmod 600 ~/.config/muse-cli/cookies.txt
185
+ muse-cli status
186
+ ```
92
187
 
93
188
  ## Usage
94
189
 
@@ -166,12 +261,13 @@ pyproject.toml
166
261
 
167
262
  ## Setup notes
168
263
 
169
- - `auth export` reads cookies from a running Chrome via
170
- [agent-browser](https://github.com/nikships/foundry) (`npm i -g agent-browser`).
171
- No Chrome? Copy your `muse.ai` cookies into `~/.config/muse-cli/cookies.txt`
172
- by hand (Netscape jar or `name=value; ...` format, needs `hatch_sess`).
173
- - Cookies live at `~/.config/muse-cli/cookies.txt` (mode 600). Access and
174
- gateway tokens are fetched fresh on every run, nothing long-lived is stored.
264
+ - Login is the [Log in once](#log-in-once) section above. Cookies live at
265
+ `~/.config/muse-cli/cookies.txt` (mode 600). Access and gateway tokens are
266
+ fetched fresh on every run.
267
+ - `auth export` reads those cookies from Google Chrome through
268
+ [agent-browser](https://github.com/vercel-labs/agent-browser). Chrome has
269
+ to be open, with remote debugging on
270
+ (`chrome://inspect/#remote-debugging`) and a logged-in muse.ai tab in front.
175
271
  - Respect muse.ai's terms and rate limits. Internal APIs are unversioned and
176
272
  can change; if calls fail, re-derive from a fresh app bundle.
177
273
 
@@ -29,29 +29,124 @@ A command-line client for your personal muse.ai AI agent: chat from the terminal
29
29
  uv tool install muse-cli # or: pipx install muse-cli / pip install muse-cli
30
30
  ```
31
31
 
32
- Then log in and check the connection:
32
+ The command is `muse-cli`. The name `muse` belongs to Muse Code on many machines.
33
+
34
+ Want the agent skill too? The installer sets up the CLI and copies the skill
35
+ to `~/.agents/skills/muse-cli`:
33
36
 
34
37
  ```bash
35
- # 1. Install agent-browser (reads cookies from Chrome):
38
+ curl -fsSL https://raw.githubusercontent.com/nikships/muse-cli/main/install.sh | bash
39
+ ```
40
+
41
+ Upgrade with `muse-cli update`. In a terminal it also checks PyPI once a day and, when a newer release exists, prints that command on stderr. It does not upgrade itself. Set `MUSE_NO_UPDATE_CHECK=1` to silence the notice. The check stays quiet when output is piped or `CI` is set. Remove with `uv tool uninstall muse-cli`.
42
+
43
+ Logging in is a separate one-time step. Chrome shares its cookies after you
44
+ turn on remote debugging. Follow [Log in once](#log-in-once) before running
45
+ any other command.
46
+
47
+ ## Log in once
48
+
49
+ muse-cli borrows the login you already have in Chrome, saves those cookies
50
+ to `~/.config/muse-cli/cookies.txt` (mode `600`, only your user can read it),
51
+ and after that talks to muse.ai directly. Access tokens are fetched fresh
52
+ on every command. When a command later says the cookies expired, repeat this
53
+ section.
54
+
55
+ Do the steps in order. Each one checks itself before you continue.
56
+
57
+ ### 1. Install the cookie reader
58
+
59
+ `auth export` uses [agent-browser](https://github.com/vercel-labs/agent-browser)
60
+ to read Chrome's cookies. That package comes from npm, so you need Node.js first.
61
+
62
+ ```bash
63
+ node --version # if this fails, install Node.js LTS from https://nodejs.org
64
+ # and open a new terminal
36
65
  npm i -g agent-browser
37
- # 2. Log in to https://muse.ai/ in Chrome
38
- # 3. Export your session (one time; re-run when it expires):
66
+ agent-browser --version
67
+ ```
68
+
69
+ `npm i -g` needs permission to write to npm's global bin directory. If it
70
+ errors with `EACCES`, pick one and use it from then on: configure an
71
+ [npm prefix you own](https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally),
72
+ or run the install with the same rights you use for other global npm tools.
73
+ Then check `command -v agent-browser` prints a path.
74
+
75
+ ### 2. Let Chrome share cookies
76
+
77
+ Open **Google Chrome**, the same profile you use for muse.ai. Paste this in
78
+ the address bar:
79
+
80
+ ```
81
+ chrome://inspect/#remote-debugging
82
+ ```
83
+
84
+ Turn on remote debugging. The checkbox reads **Allow remote debugging for
85
+ this browser instance**. Leave Chrome open.
86
+
87
+ Turn this on while Chrome is already open and muse.ai is loaded. The export
88
+ can see that window only after the switch is on. The page is in Chrome 144
89
+ and newer.
90
+
91
+ Stay in this window. Starting a second Chrome with
92
+ `--remote-debugging-port` opens a different profile, and that profile does
93
+ not have your muse.ai login.
94
+
95
+ ### 3. Be logged in, on a muse.ai tab
96
+
97
+ In that same Chrome window, open https://muse.ai/ and log in. Leave the tab
98
+ open. The exporter reads whichever tab is active, so the muse.ai tab has to
99
+ be the one in front when you run the next command.
100
+
101
+ ### 4. Save the login
102
+
103
+ ```bash
39
104
  muse-cli auth export
105
+ ```
40
106
 
41
- muse-cli status
107
+ Chrome may ask to allow the debugging connection. Click **Allow**. If the
108
+ command already failed, run it again after you click Allow.
109
+
110
+ Success looks like this (the count varies):
111
+
112
+ ```
113
+ saved 12 muse.ai cookies to /home/you/.config/muse-cli/cookies.txt
42
114
  ```
43
115
 
44
- Want the agent skill too? The installer sets up the CLI and copies the skill
45
- to `~/.agents/skills/muse-cli`:
116
+ The command prints the fix on the failure itself. These are the ones it
117
+ recognizes:
118
+
119
+ | What it says | What to do |
120
+ | --- | --- |
121
+ | No running Chrome / remote debugging | The switch in step 2 is off. Turn it on in the Chrome you already have open, then run the command again. |
122
+ | No muse.ai tab | Step 3. Open https://muse.ai/ in that same window and leave the tab open. |
123
+ | No `hatch_sess` | The tab is open and you are logged out. Log in, then export again. The cookies file already on disk is left as it was. |
124
+ | `agent-browser` is not installed | Step 1. `node --version`, then `npm i -g agent-browser`, then a new terminal. |
125
+ | Daemon already running | A previous attempt is stuck. `agent-browser close`, then `muse-cli auth export`. |
126
+
127
+ ### 5. Check the connection
46
128
 
47
129
  ```bash
48
- curl -fsSL https://raw.githubusercontent.com/nikships/muse-cli/main/install.sh | bash
130
+ muse-cli status
49
131
  ```
50
132
 
51
- Upgrade with `uv tool upgrade muse-cli`, remove with `uv tool uninstall muse-cli`.
133
+ You get JSON with your VM id, how many chats you have, the unread count, and
134
+ your identity. That means install and login both worked.
135
+
136
+ ### Copy cookies by hand
52
137
 
53
- The command is `muse-cli`, not `muse`, because `muse` clashes with Muse Code
54
- on many machines.
138
+ Use this when you don't have Node, or you don't want remote debugging on.
139
+
140
+ 1. In Chrome, open https://muse.ai/ and log in.
141
+ 2. Open DevTools (F12, or Ctrl+Shift+I) → **Application** → **Cookies** → `https://muse.ai`.
142
+ 3. Copy the cookies onto one line. `hatch_sess` has to be there. Separate cookies with `; ` (semicolon, space). A Netscape cookie jar (what curl writes) and a JSON object such as `{"hatch_sess": "..."}` also work.
143
+
144
+ ```bash
145
+ mkdir -p ~/.config/muse-cli
146
+ printf '%s\n' 'hatch_sess=the-value-from-devtools; other_cookie=other_value' > ~/.config/muse-cli/cookies.txt
147
+ chmod 600 ~/.config/muse-cli/cookies.txt
148
+ muse-cli status
149
+ ```
55
150
 
56
151
  ## Usage
57
152
 
@@ -129,12 +224,13 @@ pyproject.toml
129
224
 
130
225
  ## Setup notes
131
226
 
132
- - `auth export` reads cookies from a running Chrome via
133
- [agent-browser](https://github.com/nikships/foundry) (`npm i -g agent-browser`).
134
- No Chrome? Copy your `muse.ai` cookies into `~/.config/muse-cli/cookies.txt`
135
- by hand (Netscape jar or `name=value; ...` format, needs `hatch_sess`).
136
- - Cookies live at `~/.config/muse-cli/cookies.txt` (mode 600). Access and
137
- gateway tokens are fetched fresh on every run, nothing long-lived is stored.
227
+ - Login is the [Log in once](#log-in-once) section above. Cookies live at
228
+ `~/.config/muse-cli/cookies.txt` (mode 600). Access and gateway tokens are
229
+ fetched fresh on every run.
230
+ - `auth export` reads those cookies from Google Chrome through
231
+ [agent-browser](https://github.com/vercel-labs/agent-browser). Chrome has
232
+ to be open, with remote debugging on
233
+ (`chrome://inspect/#remote-debugging`) and a logged-in muse.ai tab in front.
138
234
  - Respect muse.ai's terms and rate limits. Internal APIs are unversioned and
139
235
  can change; if calls fail, re-derive from a fresh app bundle.
140
236
 
@@ -74,6 +74,9 @@ chat / feed / goals / ideas / sessions / ...
74
74
  delete: `{method: "/api/session/<op>", session_id}`.
75
75
  - `api.idea-cards.execute`: `{ideaCardId, mode: "full"}` plus path param.
76
76
  - POSTs to muse.ai need browser `Sec-Fetch-*` headers or they return 403.
77
- - `auth export` must focus a muse.ai tab before reading cookies (the export
78
- follows the active tab) and must never overwrite a working jar without a
79
- `hatch_sess` in the new one.
77
+ - `auth export` reads cookies from the user's own Google Chrome through
78
+ agent-browser `--auto-connect`. Chrome 144+ shares that profile only after
79
+ remote debugging is enabled at `chrome://inspect/#remote-debugging`. The
80
+ export follows the active tab, so a muse.ai tab has to be focused first,
81
+ and it must never overwrite a working jar without a `hatch_sess` in the
82
+ new one. A freshly launched browser has no login; do not fall back to one.
@@ -0,0 +1,180 @@
1
+ ---
2
+ name: muse-cli
3
+ description: Manage a personal muse.ai agent from the terminal (send messages, read chats, watch live events, feed/goals/ideas/sessions). Use when asked to message, check, or automate someone's Muse personal AI, work with muse.ai chats/threads/feed/goals outside the browser, or when the muse-cli tool itself needs installing or auth setup.
4
+ ---
5
+
6
+ # muse-cli: drive a muse.ai personal agent from the terminal
7
+
8
+ Assume a bare machine: only this skill is present. No repo clone, no
9
+ dependencies, no auth. Work top to bottom; stop at the first step that
10
+ fails and report it.
11
+
12
+ The `muse-cli` package (on PyPI) talks to the muse.ai personal gateway
13
+ directly. No browser needed after the one-time cookie export. Every command
14
+ prints JSON.
15
+
16
+ ## 1. Install
17
+
18
+ ```bash
19
+ curl -fsSL https://raw.githubusercontent.com/nikships/muse-cli/main/install.sh | bash
20
+ ```
21
+
22
+ This installs the CLI from PyPI with `uv tool install muse-cli` (its own
23
+ isolated environment) and this skill (to `~/.agents/skills/muse-cli`). CLI
24
+ only? `uv tool install muse-cli`, `pipx install muse-cli`, or
25
+ `pip install muse-cli` all work. The command is `muse-cli` (`muse` clashes
26
+ with Muse Code, don't use it). Verify before continuing:
27
+
28
+ ```bash
29
+ command -v muse-cli
30
+ muse-cli --help >/dev/null && echo cli-ok # proves the install + deps resolve
31
+ ```
32
+
33
+ ## 2. Auth
34
+
35
+ The CLI stores the user's muse.ai browser login at
36
+ `~/.config/muse-cli/cookies.txt` (mode 0600). Chrome hides those cookies
37
+ until remote debugging is on. Walk the user through the steps below and wait
38
+ for them. Do not run `muse-cli auth export` before they say the Chrome
39
+ switch is on and a logged-in muse.ai tab is open.
40
+
41
+ Access and gateway tokens are fetched fresh on every run; only cookies
42
+ persist. When a later command fails with `auth error`, the cookies expired:
43
+ repeat 2b (or 2c).
44
+
45
+ ### 2a. Cookie reader
46
+
47
+ ```bash
48
+ command -v node >/dev/null && node --version
49
+ command -v npm >/dev/null && npm --version
50
+ command -v agent-browser >/dev/null && agent-browser --version
51
+ ```
52
+
53
+ If `agent-browser` is missing and `npm` works:
54
+
55
+ ```bash
56
+ npm i -g agent-browser
57
+ agent-browser --version # must print a version
58
+ command -v agent-browser # must print a path
59
+ ```
60
+
61
+ `EACCES` from `npm i -g` means npm cannot write its global bin directory.
62
+ Stop and tell the user. They fix it with an npm prefix they own
63
+ (https://docs.npmjs.com/resolving-eacces-permissions-errors-when-installing-packages-globally)
64
+ or by installing with the same rights they use for other global npm tools.
65
+ Do not sudo unless they tell you to.
66
+
67
+ If `node` or `npm` is missing, stop. Tell them to install Node.js LTS from
68
+ https://nodejs.org, open a new terminal, and come back. Offer 2c if they
69
+ would rather not install Node.
70
+
71
+ ### 2b. Chrome, then export
72
+
73
+ Tell the user to do all of this in the Google Chrome window they already
74
+ use for muse.ai. Needs Chrome 144 or newer. Wait until they confirm:
75
+
76
+ 1. Address bar: `chrome://inspect/#remote-debugging`
77
+ 2. Turn on remote debugging ("Allow remote debugging for this browser instance").
78
+ 3. Open https://muse.ai/ and log in. Leave that tab in front. The export
79
+ reads the active tab.
80
+
81
+ Then:
82
+
83
+ ```bash
84
+ muse-cli auth export
85
+ test -s ~/.config/muse-cli/cookies.txt && echo auth-ok
86
+ ```
87
+
88
+ If Chrome shows an Allow prompt, they click Allow. If the command failed
89
+ before that click, run it once more.
90
+
91
+ Success is a line like `saved N muse.ai cookies to ~/.config/muse-cli/cookies.txt`.
92
+
93
+ If it fails, the CLI prints the next step. Map it like this, and do not retry
94
+ in a loop:
95
+
96
+ - "No running Chrome" or "remote-debugging-port": the switch in step 2 is
97
+ off, or they are in a different Chrome. Send them back to the steps above.
98
+ Do not tell them to launch Chrome with `--remote-debugging-port`. That
99
+ opens another profile, and it does not have their muse.ai login.
100
+ - "no muse.ai tab": Chrome connected. They still need https://muse.ai/ open
101
+ and in front in that same window.
102
+ - "no hatch_sess" / "do not include hatch_sess": the tab is open and they
103
+ are logged out. The cookies file on disk was kept.
104
+ - "daemon already running": `agent-browser close`, then `muse-cli auth export` once.
105
+ - `agent-browser` not found: back to 2a.
106
+
107
+ ### 2c. Copy cookies by hand
108
+
109
+ Use this when Node is unavailable, or the user does not want remote debugging on.
110
+
111
+ 1. They are logged in at https://muse.ai/ in Chrome.
112
+ 2. DevTools (F12) → Application → Cookies → `https://muse.ai`.
113
+ 3. Write one line to `~/.config/muse-cli/cookies.txt`. `hatch_sess` is required.
114
+ Separate cookies with `; `:
115
+
116
+ ```
117
+ hatch_sess=VALUE; other_name=other_value
118
+ ```
119
+
120
+ A Netscape jar or `{"hatch_sess": "..."}` JSON also works.
121
+
122
+ 4. `chmod 600 ~/.config/muse-cli/cookies.txt`
123
+ 5. `test -s ~/.config/muse-cli/cookies.txt && echo auth-ok`, then `muse-cli status`.
124
+ Stop if status fails and report the error text.
125
+
126
+ ## 3. Verify end to end
127
+
128
+ ```bash
129
+ muse-cli status # VM id, chat count, unread, identity: install + auth proven
130
+ ```
131
+
132
+ ## Everyday commands
133
+
134
+ ```bash
135
+ muse-cli threads # main chat + side chats (session_ids)
136
+ muse-cli history --limit 5 # recent main-chat messages
137
+ muse-cli history --thread <session-id> --limit 5 # one side chat
138
+ muse-cli send "message" --wait 120 # send + wait for the reply
139
+ muse-cli send "message" --thread <session-id> --wait 0 # fire and forget to a side chat
140
+ muse-cli watch --timeout 60 # tail live agent events
141
+ muse-cli feed --limit 5
142
+ muse-cli goals
143
+ muse-cli ideas
144
+ muse-cli unread
145
+ muse-cli seen <thread-id>
146
+ ```
147
+
148
+ Management (visible side effects, confirm with the user first when destructive):
149
+
150
+ ```bash
151
+ muse-cli feed-react <unit-id> love
152
+ muse-cli idea-exec <idea-id> # the agent acts on the idea (real work)
153
+ muse-cli session-start --title "x" # new side chat
154
+ muse-cli session-rename <id> "title" # also: pin, unpin, archive, unarchive, delete
155
+ muse-cli wake
156
+ ```
157
+
158
+ ## Escape hatch
159
+
160
+ `muse-cli raw <method> --body '{...}' [--param k=v]` calls any of the 258
161
+ gateway methods in `routes.json`. Prefer the named commands above; use raw
162
+ only for methods with no wrapper.
163
+
164
+ ## Gotchas
165
+
166
+ - `send` returns `{"sent": true, "reply": {...}}`, polling history until the
167
+ reply lands (up to `--wait`). If `reply` is missing, the agent was slower
168
+ than the wait: the message still landed, confirm with `history`. The reply
169
+ is matched as a genuine answer, not background chatter.
170
+ - `send --wait 0` is fire-and-forget (no polling).
171
+ - `history` without `--thread` reads the main chat only.
172
+ - The gateway API is unversioned. Whole classes of calls failing at once means
173
+ the protocol drifted: see `docs/PROTOCOL.md` for the re-derivation notes,
174
+ don't guess at crypto or framing.
175
+ - Respect rate limits. Writes (send, react, execute, session ops) act as the
176
+ user in their agent: announce them before running, never loop them.
177
+ - A stderr line `A new release of muse-cli is available` is an upgrade
178
+ notice, not a failed command. Upgrade with `muse-cli update`. The check
179
+ runs at most once a day, only in a terminal, and never changes the install
180
+ by itself. `MUSE_NO_UPDATE_CHECK=1` silences it.
@@ -1,3 +1,3 @@
1
1
  """muse-cli: talk to your personal muse.ai AI agent from the terminal."""
2
2
 
3
- __version__ = "0.2.1"
3
+ __version__ = "0.3.0"
@@ -1,8 +1,10 @@
1
- """muse-cli: CLI for your personal muse.ai agent. No browser needed (after cookie export).
1
+ """muse-cli: CLI for your personal muse.ai agent. No browser needed after cookie export.
2
2
 
3
- Setup:
4
- 1. Log in to https://muse.ai/ in Chrome (Auth profile).
5
- 2. muse-cli auth export # saves session cookies locally (chmod 600)
3
+ One-time setup (also printed by `muse-cli auth export` when a step is missing):
4
+ 1. npm i -g agent-browser
5
+ 2. In Chrome, open chrome://inspect/#remote-debugging and turn remote debugging on.
6
+ 3. Open https://muse.ai/ in that same window and log in. Leave the tab open.
7
+ 4. muse-cli auth export # saves session cookies locally (chmod 600)
6
8
 
7
9
  Then: muse-cli status | muse-cli threads | muse-cli history | muse-cli send "hello" | ...
8
10
  """
@@ -15,6 +17,7 @@ import time
15
17
 
16
18
  from . import __version__
17
19
  from .gateway import Gateway, AuthError, GatewayError, load_cookies
20
+ from .update import cmd_update, finish_update_check, start_update_check
18
21
 
19
22
  CONFIG_DIR = os.path.expanduser("~/.config/muse-cli")
20
23
  CONFIG_FILE = os.path.join(CONFIG_DIR, "config.json")
@@ -44,8 +47,11 @@ def load_config():
44
47
 
45
48
  def connect(cfg):
46
49
  if not os.path.exists(cfg["cookies_file"]):
47
- raise AuthError(f"no cookies at {cfg['cookies_file']}; log in to https://muse.ai/ "
48
- "in Chrome, then run `muse-cli auth export`")
50
+ raise AuthError(
51
+ f"no cookies at {cfg['cookies_file']}. Run `muse-cli auth export` "
52
+ "and follow the steps it prints: turn on Chrome remote debugging at "
53
+ "chrome://inspect/#remote-debugging, open https://muse.ai/ in that "
54
+ "window, then export again.")
49
55
  cookies = load_cookies(cfg["cookies_file"])
50
56
  if not cookies.strip():
51
57
  raise AuthError(f"cookies file {cfg['cookies_file']} is empty; run `muse-cli auth export`")
@@ -56,70 +62,175 @@ def out(obj):
56
62
  print(json.dumps(obj, indent=2, ensure_ascii=False))
57
63
 
58
64
 
65
+ def _parse_browser_doc(stdout):
66
+ """agent-browser prints a JSON envelope, sometimes after a warning line."""
67
+ text = (stdout or "").strip()
68
+ if not text:
69
+ return None
70
+ blobs = [text]
71
+ for line in reversed(text.splitlines()):
72
+ line = line.strip()
73
+ if line.startswith("{") and line not in blobs:
74
+ blobs.append(line)
75
+ for blob in blobs:
76
+ try:
77
+ doc = json.loads(blob)
78
+ except json.JSONDecodeError:
79
+ continue
80
+ if isinstance(doc, dict):
81
+ return doc
82
+ return None
83
+
84
+
59
85
  def _browser_run(argv):
60
- """Run agent-browser, parsing its JSON envelope. NOTE: it exits 0 even
61
- on failure, reporting {"success": false, "error": ...} on stdout."""
86
+ """Run agent-browser and return the JSON envelope's data.
87
+
88
+ On failure the tool prints {"success": false, "error": ...}. Some versions
89
+ exit 0 anyway, some exit non-zero; trust the envelope when it is there.
90
+ """
62
91
  import subprocess
63
92
  r = subprocess.run(argv, capture_output=True, text=True)
64
- if r.returncode != 0:
65
- raise RuntimeError((r.stderr or r.stdout)[:200] or f"exit {r.returncode}")
66
- try:
67
- doc = json.loads(r.stdout)
68
- except json.JSONDecodeError:
69
- raise RuntimeError((r.stdout or r.stderr)[:200] or "empty output")
93
+ doc = _parse_browser_doc(r.stdout) or _parse_browser_doc(r.stderr)
70
94
  if isinstance(doc, dict) and doc.get("success") is False:
71
- raise RuntimeError(str(doc.get("error") or "unknown error")[:200])
95
+ raise RuntimeError(str(doc.get("error") or "unknown error")[:400])
96
+ if r.returncode != 0 or doc is None:
97
+ msg = (r.stderr or r.stdout or "").strip()
98
+ raise RuntimeError(msg[:400] or f"exit {r.returncode}")
72
99
  return doc.get("data", {}) if isinstance(doc, dict) else doc
73
100
 
74
101
 
75
- def _browser_cookies(headed):
76
- """Read the cookie jar via agent-browser. Headed auto-connect attaches
77
- to the user's real Chrome; plain mode uses a fresh browser (no login)."""
78
- base = ["agent-browser"] + (["--headed", "--auto-connect"] if headed else [])
79
- if shutil.which("agent-browser") is None:
80
- print("agent-browser not found; install it with: npm i -g agent-browser", file=sys.stderr)
81
- print("alternative: export cookies by hand, see README Setup notes.", file=sys.stderr)
82
- sys.exit(1)
102
+ def _export_error_kind(err):
103
+ e = (err or "").lower()
104
+ if "daemon already running" in e:
105
+ return "daemon"
106
+ if ("no running chrome" in e or "remote-debugging" in e
107
+ or "auto-launch failed" in e or "auto-connect" in e):
108
+ return "debug"
109
+ if err == "no muse.ai tab open in Chrome":
110
+ return "tab"
111
+ return "other"
112
+
113
+
114
+ def _print_hand_copy():
115
+ print("Copy the login by hand instead:", file=sys.stderr)
116
+ print(" 1. In Chrome, open https://muse.ai/ and log in.", file=sys.stderr)
117
+ print(" 2. DevTools (F12) → Application → Cookies → https://muse.ai", file=sys.stderr)
118
+ print(" 3. Write one line to ~/.config/muse-cli/cookies.txt", file=sys.stderr)
119
+ print(" hatch_sess=VALUE; other_name=other_value", file=sys.stderr)
120
+ print(" hatch_sess is required. Separate cookies with '; '.", file=sys.stderr)
121
+ print(" 4. chmod 600 ~/.config/muse-cli/cookies.txt", file=sys.stderr)
122
+ print(" 5. muse-cli status", file=sys.stderr)
123
+
124
+
125
+ def _print_chrome_debug_steps():
126
+ print("Turn on remote debugging so muse-cli can read the muse.ai cookies.", file=sys.stderr)
127
+ print("Do this in the Chrome window where you already use muse.ai:", file=sys.stderr)
128
+ print(file=sys.stderr)
129
+ print(" 1. Open chrome://inspect/#remote-debugging", file=sys.stderr)
130
+ print(" 2. Turn on remote debugging", file=sys.stderr)
131
+ print(' ("Allow remote debugging for this browser instance").', file=sys.stderr)
132
+ print(" 3. Open https://muse.ai/ and log in. Leave that tab open.", file=sys.stderr)
133
+ print(" 4. Run `muse-cli auth export` again.", file=sys.stderr)
134
+ print(" If Chrome asks to allow the connection, click Allow.", file=sys.stderr)
135
+ print(" If this command already failed, run it again after Allow.", file=sys.stderr)
136
+ print(file=sys.stderr)
137
+ print("Stay in that same window. A second Chrome started with", file=sys.stderr)
138
+ print("--remote-debugging-port is another profile and has no muse.ai login.", file=sys.stderr)
139
+ print(file=sys.stderr)
140
+ print("Success looks like:", file=sys.stderr)
141
+ print(f" saved N muse.ai cookies to {COOKIES_FILE}", file=sys.stderr)
142
+ print(file=sys.stderr)
143
+ _print_hand_copy()
144
+
145
+
146
+ def _print_need_agent_browser():
147
+ print("agent-browser is not installed, so muse-cli cannot read Chrome's cookies.", file=sys.stderr)
148
+ print(file=sys.stderr)
149
+ print(" 1. Install Node.js LTS if `node --version` fails: https://nodejs.org", file=sys.stderr)
150
+ print(" 2. Open a new terminal, then:", file=sys.stderr)
151
+ print(" npm i -g agent-browser", file=sys.stderr)
152
+ print(" agent-browser --version", file=sys.stderr)
153
+ print(" 3. Run `muse-cli auth export` again and follow the Chrome steps it prints.", file=sys.stderr)
154
+ print(file=sys.stderr)
155
+ _print_hand_copy()
156
+
157
+
158
+ def _print_export_failure(err):
159
+ kind = _export_error_kind(err)
160
+ print("cookie export failed.", file=sys.stderr)
161
+ if kind == "daemon":
162
+ print("agent-browser is still running from an earlier attempt. Close it, then retry:", file=sys.stderr)
163
+ print(" agent-browser close", file=sys.stderr)
164
+ print(" muse-cli auth export", file=sys.stderr)
165
+ print(file=sys.stderr)
166
+ _print_chrome_debug_steps()
167
+ return
168
+ if kind == "debug":
169
+ _print_chrome_debug_steps()
170
+ return
171
+ if kind == "tab":
172
+ print("Chrome is connected, but no muse.ai tab is open.", file=sys.stderr)
173
+ print("Open https://muse.ai/, log in, leave that tab open, and run", file=sys.stderr)
174
+ print("`muse-cli auth export` again.", file=sys.stderr)
175
+ return
176
+ print(err, file=sys.stderr)
177
+ print(file=sys.stderr)
178
+ print("Check both of these, then run `muse-cli auth export` again:", file=sys.stderr)
179
+ print(" - chrome://inspect/#remote-debugging has remote debugging on", file=sys.stderr)
180
+ print(" - https://muse.ai/ is open in that same Chrome and you are logged in", file=sys.stderr)
181
+ print(file=sys.stderr)
182
+ _print_hand_copy()
183
+
184
+
185
+ def _browser_cookies():
186
+ """Read muse.ai cookies from the user's Chrome via agent-browser.
187
+
188
+ Auto-connect attaches to the real profile. A fresh browser has no login,
189
+ so this never falls back to launching one.
190
+ """
191
+ base = ["agent-browser", "--headed", "--auto-connect"]
83
192
  last_err = "unknown error"
84
- for _ in range(3):
193
+ for attempt in range(3):
85
194
  try:
86
- if headed:
87
- # Cookies follow the active tab: focus a muse.ai tab first,
88
- # else the export comes back empty even when logged in.
89
- data = _browser_run(base + ["tab", "list", "--json"])
90
- tabs = data.get("tabs", []) if isinstance(data, dict) else []
91
- muse_tabs = [t for t in tabs
92
- if isinstance(t, dict) and "muse.ai" in (t.get("url") or "")
93
- and (t.get("id") or t.get("tabId"))]
94
- if not muse_tabs:
95
- return None, "no muse.ai tab open in Chrome"
96
- _browser_run(base + ["tab", muse_tabs[0].get("id") or muse_tabs[0]["tabId"]])
195
+ # Cookies follow the active tab: focus a muse.ai tab first,
196
+ # else the export comes back empty even when logged in.
197
+ data = _browser_run(base + ["tab", "list", "--json"])
198
+ tabs = data.get("tabs", []) if isinstance(data, dict) else []
199
+ muse_tabs = [t for t in tabs
200
+ if isinstance(t, dict) and "muse.ai" in (t.get("url") or "")
201
+ and (t.get("id") or t.get("tabId"))]
202
+ if not muse_tabs:
203
+ return None, "no muse.ai tab open in Chrome"
204
+ _browser_run(base + ["tab", muse_tabs[0].get("id") or muse_tabs[0]["tabId"]])
97
205
  data = _browser_run(base + ["cookies", "get", "--json"])
98
206
  jar = data.get("cookies", []) if isinstance(data, dict) else []
99
207
  return [c for c in jar if "muse.ai" in c.get("domain", "")], None
100
208
  except RuntimeError as e:
101
209
  last_err = str(e)
210
+ # Missing debug port and a stuck daemon will not change on retry.
211
+ if _export_error_kind(last_err) in ("debug", "daemon") or attempt == 2:
212
+ return None, last_err
102
213
  time.sleep(2)
103
214
  return None, last_err
104
215
 
105
216
 
106
217
  def cmd_auth_export(_args):
107
218
  os.makedirs(CONFIG_DIR, exist_ok=True)
108
- jar, err = _browser_cookies(headed=True)
109
- if jar is None and err != "no muse.ai tab open in Chrome":
110
- # Headed attach failed (no Chrome, old agent-browser, ...): a plain
111
- # browser shares no login, so this is a last resort at best.
112
- jar, err = _browser_cookies(headed=False)
219
+ if shutil.which("agent-browser") is None:
220
+ _print_need_agent_browser()
221
+ sys.exit(1)
222
+ print("Reading muse.ai cookies from Chrome...", file=sys.stderr)
223
+ jar, err = _browser_cookies()
113
224
  if jar is None:
114
- print(f"cookie export failed: {err}", file=sys.stderr)
115
- print("is Chrome running with muse.ai open?", file=sys.stderr)
116
- print("alternative: export cookies by hand, see README Setup.", file=sys.stderr)
225
+ _print_export_failure(err)
117
226
  sys.exit(1)
118
227
  # Never clobber a working login with an empty or logged-out jar.
119
228
  if not any(c["name"] == "hatch_sess" for c in jar):
120
- print("refusing to overwrite cookies: no hatch_sess in export "
121
- "(are you logged in to muse.ai?). Existing file left intact.",
229
+ print("Chrome answered, but the cookies do not include hatch_sess.", file=sys.stderr)
230
+ print("That cookie is set only after you log in at https://muse.ai/.", file=sys.stderr)
231
+ print("Log in, leave the muse.ai tab open, and run `muse-cli auth export` again.",
122
232
  file=sys.stderr)
233
+ print(f"Left {COOKIES_FILE} unchanged.", file=sys.stderr)
123
234
  sys.exit(1)
124
235
  lines = ["# Netscape HTTP Cookie File"]
125
236
  for c in jar:
@@ -476,7 +587,32 @@ def main():
476
587
  sub = ap.add_subparsers(dest="cmd", required=True)
477
588
 
478
589
  p = sub.add_parser("auth", help="auth helpers"); a = p.add_subparsers(dest="op", required=True)
479
- a.add_parser("export", help="export Chrome session cookies for the CLI").set_defaults(fn=cmd_auth_export)
590
+ export_help = (
591
+ "Copy your muse.ai login out of Google Chrome into\n"
592
+ "~/.config/muse-cli/cookies.txt (mode 600).\n"
593
+ "\n"
594
+ "Chrome shares those cookies only after remote debugging is on.\n"
595
+ "In the Chrome window you already use for muse.ai:\n"
596
+ "\n"
597
+ " 1. Open chrome://inspect/#remote-debugging\n"
598
+ " 2. Turn on remote debugging\n"
599
+ ' ("Allow remote debugging for this browser instance")\n'
600
+ " 3. Open https://muse.ai/ and log in. Leave that tab open.\n"
601
+ " 4. Run this command. If Chrome asks to allow the connection,\n"
602
+ " click Allow, then run it again if it failed before the click.\n"
603
+ "\n"
604
+ "Requires agent-browser: npm i -g agent-browser\n"
605
+ "(install Node.js LTS from https://nodejs.org first if npm is missing).\n"
606
+ "\n"
607
+ "Stay in that same Chrome. Starting another Chrome with\n"
608
+ "--remote-debugging-port uses a different profile and has no muse.ai login."
609
+ )
610
+ a.add_parser(
611
+ "export",
612
+ help="copy the muse.ai login out of Chrome (remote debugging must be on)",
613
+ description=export_help,
614
+ formatter_class=argparse.RawDescriptionHelpFormatter,
615
+ ).set_defaults(fn=cmd_auth_export)
480
616
 
481
617
  sub.add_parser("status", help="VM, session count, unread, identity").set_defaults(fn=cmd_status)
482
618
  p = sub.add_parser("threads", help="list chats and side chats")
@@ -520,23 +656,30 @@ def main():
520
656
  p.add_argument("title")
521
657
  p.set_defaults(fn=cmd_session_op(kind))
522
658
  sub.add_parser("wake", help="request a VM wake").set_defaults(fn=cmd_wake)
659
+ sub.add_parser("update", help="upgrade this install to the latest release").set_defaults(fn=cmd_update)
523
660
  p = sub.add_parser("raw", help="call any gateway method (escape hatch)")
524
661
  p.add_argument("method"); p.add_argument("--body", default=None)
525
662
  p.add_argument("--param", action="append", default=[], help="path param k=v (repeatable)")
526
663
  p.add_argument("--timeout", type=int, default=30); p.set_defaults(fn=cmd_raw)
527
664
 
528
665
  args = ap.parse_args()
666
+ # A piped command prints JSON. The notice stays off unless both streams
667
+ # are a terminal, and `update` is already doing the upgrade.
668
+ notice = None if args.cmd == "update" else start_update_check()
529
669
  try:
530
- args.fn(args)
531
- except AuthError as e:
532
- print(f"auth error: {e}", file=sys.stderr)
533
- sys.exit(2)
534
- except GatewayError as e:
535
- print(f"gateway error: {e}", file=sys.stderr)
536
- sys.exit(3)
537
- except TimeoutError as e:
538
- print(f"timeout: {e}", file=sys.stderr)
539
- sys.exit(4)
670
+ try:
671
+ args.fn(args)
672
+ except AuthError as e:
673
+ print(f"auth error: {e}", file=sys.stderr)
674
+ sys.exit(2)
675
+ except GatewayError as e:
676
+ print(f"gateway error: {e}", file=sys.stderr)
677
+ sys.exit(3)
678
+ except TimeoutError as e:
679
+ print(f"timeout: {e}", file=sys.stderr)
680
+ sys.exit(4)
681
+ finally:
682
+ finish_update_check(notice)
540
683
 
541
684
 
542
685
  if __name__ == "__main__":
@@ -0,0 +1,150 @@
1
+ """Tell an interactive user when a newer muse-cli is on PyPI.
2
+
3
+ Same shape as `gh` and `uv`: look at most once a day, never on a pipe or in
4
+ CI, never fail the command, and print the upgrade command instead of
5
+ replacing the install. uv tools do not upgrade themselves.
6
+ """
7
+ import json
8
+ import os
9
+ import shutil
10
+ import sys
11
+ import threading
12
+ import time
13
+ import urllib.request
14
+
15
+ from . import __version__
16
+
17
+ CHECK_INTERVAL = 24 * 60 * 60
18
+ FAIL_BACKOFF = 60 * 60
19
+ STATE_FILE = os.path.expanduser("~/.config/muse-cli/update.json")
20
+ PYPI_URL = "https://pypi.org/pypi/muse-cli/json"
21
+
22
+
23
+ def version_key(value):
24
+ """Stable x.y.z as a tuple. Pre-releases and junk compare as absent."""
25
+ text = str(value).strip().lstrip("v").split("+", 1)[0]
26
+ if not text or any(c.isalpha() for c in text):
27
+ return None
28
+ try:
29
+ return tuple(int(part) for part in text.split("."))
30
+ except ValueError:
31
+ return None
32
+
33
+
34
+ def is_newer(latest, current):
35
+ new, old = version_key(latest), version_key(current)
36
+ return bool(new and old and new > old)
37
+
38
+
39
+ def upgrade_argv():
40
+ """The upgrade command for this install. uv tool, pipx, or pip."""
41
+ prefix = os.path.realpath(sys.prefix)
42
+ sep = os.sep
43
+ if f"{sep}uv{sep}tools{sep}" in prefix:
44
+ return ["uv", "tool", "upgrade", "muse-cli"]
45
+ if f"{sep}pipx{sep}" in prefix:
46
+ return ["pipx", "upgrade", "muse-cli"]
47
+ return [sys.executable, "-m", "pip", "install", "-U", "muse-cli"]
48
+
49
+
50
+ def upgrade_command():
51
+ return " ".join(upgrade_argv())
52
+
53
+
54
+ def _should_check():
55
+ if os.environ.get("MUSE_NO_UPDATE_CHECK"):
56
+ return False
57
+ if os.environ.get("CI"):
58
+ return False
59
+ try:
60
+ return sys.stdout.isatty() and sys.stderr.isatty()
61
+ except (AttributeError, ValueError):
62
+ return False
63
+
64
+
65
+ def _load_state():
66
+ try:
67
+ with open(STATE_FILE) as fh:
68
+ data = json.load(fh)
69
+ except (OSError, json.JSONDecodeError):
70
+ return {}
71
+ return data if isinstance(data, dict) else {}
72
+
73
+
74
+ def _save_state(data):
75
+ try:
76
+ os.makedirs(os.path.dirname(STATE_FILE), exist_ok=True)
77
+ tmp = STATE_FILE + ".tmp"
78
+ with open(tmp, "w") as fh:
79
+ json.dump(data, fh)
80
+ os.replace(tmp, STATE_FILE)
81
+ except OSError:
82
+ pass
83
+
84
+
85
+ def _fetch_latest():
86
+ req = urllib.request.Request(
87
+ PYPI_URL,
88
+ headers={"Accept": "application/json", "User-Agent": f"muse-cli/{__version__}"},
89
+ )
90
+ with urllib.request.urlopen(req, timeout=2) as resp:
91
+ payload = json.loads(resp.read().decode())
92
+ return str(payload["info"]["version"])
93
+
94
+
95
+ def start_update_check():
96
+ """Begin a check, or return a cached result. None when the notice is off."""
97
+ if not _should_check():
98
+ return None
99
+ state = _load_state()
100
+ latest = state.get("latest") or ""
101
+ checked = float(state.get("checked_at") or 0)
102
+ if latest and time.time() - checked < CHECK_INTERVAL:
103
+ return {"latest": latest}
104
+ holder = {"latest": None, "thread": None}
105
+
106
+ def work():
107
+ try:
108
+ found = _fetch_latest()
109
+ holder["latest"] = found
110
+ _save_state({"checked_at": time.time(), "latest": found})
111
+ except Exception:
112
+ # Offline or PyPI blip: try again in an hour, keep any known version.
113
+ holder["latest"] = latest or None
114
+ _save_state({
115
+ "checked_at": time.time() - CHECK_INTERVAL + FAIL_BACKOFF,
116
+ "latest": latest,
117
+ })
118
+
119
+ thread = threading.Thread(target=work, daemon=True)
120
+ holder["thread"] = thread
121
+ thread.start()
122
+ return holder
123
+
124
+
125
+ def finish_update_check(holder):
126
+ """Print the notice on stderr once the check has had a moment to finish."""
127
+ if not holder:
128
+ return
129
+ thread = holder.get("thread")
130
+ if thread is not None:
131
+ thread.join(timeout=1.5)
132
+ latest = holder.get("latest")
133
+ if not is_newer(latest, __version__):
134
+ return
135
+ print(
136
+ f"\nA new release of muse-cli is available: {__version__} → {latest}\n"
137
+ f"To upgrade, run: {upgrade_command()}\n",
138
+ file=sys.stderr,
139
+ )
140
+
141
+
142
+ def cmd_update(_args):
143
+ argv = upgrade_argv()
144
+ if argv[0] != sys.executable and shutil.which(argv[0]) is None:
145
+ print(f"{argv[0]} is not on PATH.", file=sys.stderr)
146
+ print(f"To upgrade, run: {upgrade_command()}", file=sys.stderr)
147
+ sys.exit(1)
148
+ print(f"running: {upgrade_command()}", file=sys.stderr)
149
+ import subprocess
150
+ raise SystemExit(subprocess.call(argv))
@@ -1,104 +0,0 @@
1
- ---
2
- name: muse-cli
3
- description: Manage a personal muse.ai agent from the terminal (send messages, read chats, watch live events, feed/goals/ideas/sessions). Use when asked to message, check, or automate someone's Muse personal AI, work with muse.ai chats/threads/feed/goals outside the browser, or when the muse-cli tool itself needs installing or auth setup.
4
- ---
5
-
6
- # muse-cli: drive a muse.ai personal agent from the terminal
7
-
8
- Assume a bare machine: only this skill is present. No repo clone, no
9
- dependencies, no auth. Work top to bottom; stop at the first step that
10
- fails and report it.
11
-
12
- The `muse-cli` package (on PyPI) talks to the muse.ai personal gateway
13
- directly. No browser needed after the one-time cookie export. Every command
14
- prints JSON.
15
-
16
- ## 1. Install
17
-
18
- ```bash
19
- curl -fsSL https://raw.githubusercontent.com/nikships/muse-cli/main/install.sh | bash
20
- ```
21
-
22
- This installs the CLI from PyPI with `uv tool install muse-cli` (its own
23
- isolated environment) and this skill (to `~/.agents/skills/muse-cli`). CLI
24
- only? `uv tool install muse-cli`, `pipx install muse-cli`, or
25
- `pip install muse-cli` all work. The command is `muse-cli` (`muse` clashes
26
- with Muse Code, don't use it). Verify before continuing:
27
-
28
- ```bash
29
- command -v muse-cli
30
- muse-cli --help >/dev/null && echo cli-ok # proves the install + deps resolve
31
- ```
32
-
33
- ## 2. Auth
34
-
35
- The user must be logged in to https://muse.ai/ in Chrome first. `auth export`
36
- reads Chrome's cookies through
37
- [agent-browser](https://github.com/nikships/foundry); install it if missing,
38
- then:
39
-
40
- ```bash
41
- npm i -g agent-browser # one-time; skip if already installed
42
- muse-cli auth export # saves muse.ai cookies to ~/.config/muse-cli/cookies.txt (0600)
43
- test -s ~/.config/muse-cli/cookies.txt && echo auth-ok
44
- ```
45
-
46
- No Chrome? Copy the `muse.ai` cookies by hand (DevTools → Application →
47
- Cookies; needs `hatch_sess`) into `~/.config/muse-cli/cookies.txt` as
48
- Netscape-jar or `name=value; ...` text.
49
-
50
- Access and gateway tokens are fetched fresh on every run; only cookies
51
- persist. When commands later fail with `auth error`, cookies expired:
52
- re-run `auth export`.
53
-
54
- ## 3. Verify end to end
55
-
56
- ```bash
57
- muse-cli status # VM id, chat count, unread, identity: install + auth proven
58
- ```
59
-
60
- ## Everyday commands
61
-
62
- ```bash
63
- muse-cli threads # main chat + side chats (session_ids)
64
- muse-cli history --limit 5 # recent main-chat messages
65
- muse-cli history --thread <session-id> --limit 5 # one side chat
66
- muse-cli send "message" --wait 120 # send + wait for the reply
67
- muse-cli send "message" --thread <session-id> --wait 0 # fire and forget to a side chat
68
- muse-cli watch --timeout 60 # tail live agent events
69
- muse-cli feed --limit 5
70
- muse-cli goals
71
- muse-cli ideas
72
- muse-cli unread
73
- muse-cli seen <thread-id>
74
- ```
75
-
76
- Management (visible side effects, confirm with the user first when destructive):
77
-
78
- ```bash
79
- muse-cli feed-react <unit-id> love
80
- muse-cli idea-exec <idea-id> # the agent acts on the idea (real work)
81
- muse-cli session-start --title "x" # new side chat
82
- muse-cli session-rename <id> "title" # also: pin, unpin, archive, unarchive, delete
83
- muse-cli wake
84
- ```
85
-
86
- ## Escape hatch
87
-
88
- `muse-cli raw <method> --body '{...}' [--param k=v]` calls any of the 258
89
- gateway methods in `routes.json`. Prefer the named commands above; use raw
90
- only for methods with no wrapper.
91
-
92
- ## Gotchas
93
-
94
- - `send` returns `{"sent": true, "reply": {...}}`, polling history until the
95
- reply lands (up to `--wait`). If `reply` is missing, the agent was slower
96
- than the wait: the message still landed, confirm with `history`. The reply
97
- is matched as a genuine answer, not background chatter.
98
- - `send --wait 0` is fire-and-forget (no polling).
99
- - `history` without `--thread` reads the main chat only.
100
- - The gateway API is unversioned. Whole classes of calls failing at once means
101
- the protocol drifted: see `docs/PROTOCOL.md` for the re-derivation notes,
102
- don't guess at crypto or framing.
103
- - Respect rate limits. Writes (send, react, execute, session ops) act as the
104
- user in their agent: announce them before running, never loop them.
File without changes
File without changes
File without changes