opencode-muse-auth 0.4.0 → 0.4.1

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2026 TheStreamCode
3
+ Copyright (c) 2026 Michael Gasperini (Mikesoft)
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -1,16 +1,24 @@
1
1
  # opencode-muse-auth
2
2
 
3
- Use Meta **Muse Spark** in [opencode](https://opencode.ai) billed to your
4
- **Muse Code monthly subscription** — no pay-as-you-go API key, no third-party CLI.
3
+ Muse Code subscription sign-in for opencode
5
4
 
6
- > **Unofficial and experimental.** This plugin uses a Muse Code subscription
7
- > credential from opencode. Meta's terms limit that credential to Muse Code, so
8
- > this use may not be supported and Meta may suspend or revoke accounts or
9
- > credentials. Use it at your own risk; the authors cannot recover an account.
10
- > Read [DISCLAIMER.md](DISCLAIMER.md) before installing.
5
+ [![CI](https://github.com/TheStreamCode/opencode-muse-auth/actions/workflows/ci.yml/badge.svg)](https://github.com/TheStreamCode/opencode-muse-auth/actions/workflows/ci.yml)
6
+ [![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
7
+ [![npm](https://img.shields.io/npm/v/opencode-muse-auth)](https://www.npmjs.com/package/opencode-muse-auth)
8
+
9
+ opencode plugin that registers the **Muse Code subscription** login method for the built-in `meta`
10
+ provider. No API key to copy and no third-party CLI to install. A successful login does not guarantee
11
+ model access: Meta may reject subscription requests from clients other than Muse Code.
12
+
13
+ > **Unofficial and experimental.** This plugin uses a Muse Code subscription credential from
14
+ > opencode. Meta's terms limit that credential to Muse Code, so this use may not be supported and
15
+ > Meta may suspend or revoke accounts or credentials. Use it at your own risk; the authors cannot
16
+ > recover an account. Read [DISCLAIMER.md](DISCLAIMER.md) before installing.
11
17
 
12
18
  ## Install
13
19
 
20
+ Add the plugin to `opencode.json`; opencode installs it from npm:
21
+
14
22
  ```jsonc
15
23
  // opencode.json
16
24
  {
@@ -19,64 +27,129 @@ Use Meta **Muse Spark** in [opencode](https://opencode.ai) billed to your
19
27
  }
20
28
  ```
21
29
 
22
- Then run `/connect` in opencode, pick the `meta` provider and the
23
- *Muse Code subscription* method: it shows a Meta device page URL and a code.
24
- Enter the code in your browser (your own Meta account) and the login
25
- completes by itself.
26
-
27
- Verified end-to-end against the published package: isolated project with
28
- only `"plugins": ["opencode-muse-auth"]`, live chat on `meta/muse-spark-1.3`
29
- billed to subscription.
30
+ ## Sign in
31
+
32
+ 1. Run `/connect` in opencode.
33
+ 2. Pick the `meta` provider and the **Muse Code subscription** method.
34
+ 3. Open the printed Meta device page, enter the printed code, and approve the login in your own
35
+ browser. The login completes by itself.
36
+ 4. Select a `muse-*` model exposed by the `meta` provider, for example `meta/muse-spark-1.3`.
37
+ Meta's subscription restrictions still apply to requests from opencode.
38
+
39
+ The plugin mints a stable, account-bound inference key and hands it to opencode. The OAuth access
40
+ token from the login flow is kept in memory only and is never written to disk.
41
+
42
+ ## Features
43
+
44
+ - Registers an `auth` hook for the built-in `meta` provider; there is no separate provider id to
45
+ configure.
46
+ - Runs the Meta device-code exchange (RFC 8628) inside `/connect`, then mints the subscription
47
+ inference key through the Model API.
48
+ - Routes through the Responses API; the `meta.txt` system prompt applies automatically to `muse-*`
49
+ model ids.
50
+ - V1 storage: the OpenCode auth store is the primary source, and the loader falls back to the
51
+ legacy cache only when the store has no key.
52
+ - V2 storage: the credential lives in the OpenCode integration store, and no local cache is
53
+ written on this path.
54
+ - Never re-mints on its own; re-run `/connect` only after a 401, because the mint endpoint is
55
+ aggressively rate-limited.
56
+ - Flow parameters are compatible with the published behavior of oh-my-pi (MIT); see [NOTICE](NOTICE)
57
+ and [AUTH_CONTRACT.md](AUTH_CONTRACT.md).
30
58
 
31
59
  ## Requirements
32
60
 
33
- - opencode with plugin support
34
- - A Meta account with an active Muse Code subscription
35
-
36
- ## How it works
37
-
38
- The plugin registers an `auth` hook for the built-in `meta` provider
39
- (Routes through the Responses API, `meta.txt` system prompt applies
40
- automatically to `muse-*` model ids):
41
-
42
- - **Login** — Meta device-code exchange (RFC 8628) inside `/connect`, then
43
- mints the stable account-bound inference key via the Model API. The key is
44
- never printed and the OAuth access token from the login flow is kept in
45
- memory only.
46
- - **V1 storage** — OpenCode's auth store is the primary source; the auth
47
- `loader` falls back to the legacy cache
48
- (`~/.config/opencode/muse-code-sub.json`) only when the store has no key.
49
- The cache is still written so upgrades keep working.
50
- - **V2 storage** — the credential lives in OpenCode's integration store; the
51
- plugin does not write the local cache on this path.
52
- - **Runtime** — the mint endpoint is aggressively rate-limited, so the plugin
53
- never re-mints on its own; re-run `/connect` only if access is revoked (401).
54
-
55
- Flow parameters are compatible with the published behavior of oh-my-pi (MIT);
56
- see [NOTICE](NOTICE) and [AUTH_CONTRACT.md](AUTH_CONTRACT.md).
57
-
58
- ## Credentials and removal
59
-
60
- The inference key is held by OpenCode (V1 auth store or V2 integration store);
61
- the legacy cache, when present, stores only `apiKey`, `accountId`, and `email`.
62
- The OAuth access token is never written to disk. To remove access:
63
-
64
- - disconnect the provider or log out in OpenCode (this removes the key OpenCode
65
- uses; the store is `~/.local/share/opencode/auth.json`);
66
- - delete `~/.config/opencode/muse-code-sub.json` if it exists from an older
67
- version;
61
+ - opencode with plugin support, including the v2 plugin module shape accepted by opencode 1.18.29+
62
+ and 2.x.
63
+ - Node.js 22.6 or newer for development; CI runs Node.js 22 and 24.
64
+ - An active Muse Code subscription on the Meta account used at login.
65
+
66
+ ## Configuration
67
+
68
+ | Setting | Purpose | Default |
69
+ | --- | --- | --- |
70
+ | `plugins` entry in `opencode.json` | Loads the plugin. | not set |
71
+ | Auth store | Primary credential storage used by the V1 loader. | `~/.local/share/opencode/auth.json` |
72
+ | Legacy cache | Read-only fallback for credentials written by earlier versions. | `~/.config/opencode/muse-code-sub.json` |
73
+ | `/connect` method | Selects Muse Code subscription login for the `meta` provider. | `meta` provider |
74
+
75
+ ## Privacy and security
76
+
77
+ The inference key is held by opencode: in the V1 auth store or the V2 integration store. The
78
+ legacy cache, when present, stores only `apiKey`, `accountId`, and `email`; the OAuth access token
79
+ is never written to disk. The plugin talks only to `auth.meta.com` for the device login and
80
+ `api.meta.ai` for key minting and inference. See [SECURITY.md](SECURITY.md) for vulnerability
81
+ reporting.
82
+
83
+ To remove access:
84
+
85
+ - disconnect the provider or log out in opencode (the store is
86
+ `~/.local/share/opencode/auth.json`);
87
+ - delete `~/.config/opencode/muse-code-sub.json` if an older version created it;
68
88
  - run `muse logout` if you also want to clear the Muse Code CLI session;
69
89
  - remove `opencode-muse-auth` from `opencode.json`.
70
90
 
91
+ ## Troubleshooting
92
+
93
+ ### `/connect` shows no Muse Code subscription method
94
+
95
+ The plugin is not loaded. Check the JSON syntax of `opencode.json`, the plugin name spelling, and
96
+ the package installation, then restart opencode.
97
+
98
+ ### Requests fail with 401 or access was revoked
99
+
100
+ A 401 may indicate a rotated or revoked key. Re-run `/connect` once; the plugin never re-mints on
101
+ its own.
102
+
103
+ ### Meta rejects subscription requests from this client
104
+
105
+ Meta may return a 403 mentioning `client_attestation_denied` or that Muse Code subscriptions cover
106
+ the Muse Code client only. This is a client restriction, not evidence of an expired key. Signing in
107
+ again does not remove the restriction. Use Muse Code for the subscription, or a separate
108
+ pay-as-you-go Meta Model API key for other clients, subject to Meta's terms.
109
+
110
+ ### The login times out or is denied
111
+
112
+ Approve the printed code in the browser before the flow expires, then retry. A login rejected by
113
+ Meta usually means the account has no active Muse Code subscription.
114
+
115
+ ### A legacy cache file is still present
116
+
117
+ It is harmless: the V1 loader prefers the OpenCode auth store and reads the cache only when the
118
+ store has no key. Delete the file if you are migrating to V2 storage.
119
+
71
120
  ## Development
72
121
 
73
- ```sh
122
+ Requirements: Node.js 22.6 or newer and npm.
123
+
124
+ ```bash
125
+ git clone https://github.com/TheStreamCode/opencode-muse-auth.git
126
+ cd opencode-muse-auth
74
127
  npm install
75
128
  npm run build
76
129
  npm test
77
130
  ```
78
131
 
132
+ `npm test` builds with `tsc` and runs the TypeScript test suite with the Node test runner. CI runs
133
+ the same gate on Node.js 22 and 24; Node 22 needs 22.6 or newer for test-runner type stripping.
134
+ No Meta account is needed for the test suite.
135
+
136
+ ## Support
137
+
138
+ Usage questions and workflows belong in GitHub Discussions; reproducible defects belong in GitHub
139
+ Issues using the bug report form. See [SUPPORT.md](SUPPORT.md). Problems in opencode or Meta
140
+ themselves belong to their respective projects.
141
+
142
+ ## Contributing
143
+
144
+ See [CONTRIBUTING.md](CONTRIBUTING.md). Pull requests must pass the required CI checks.
145
+
79
146
  ## License
80
147
 
81
- MIT — see [LICENSE](LICENSE). The software is provided "as is"; see
148
+ MIT — see [`LICENSE`](LICENSE). The software is provided "as is"; see
82
149
  [DISCLAIMER.md](DISCLAIMER.md) for the risk notice and liability terms.
150
+
151
+ ---
152
+
153
+ Maintained by [Michael Gasperini (Mikesoft)](https://mikesoft.it).
154
+ If this project is useful to you, consider
155
+ [sponsoring its maintenance](https://github.com/sponsors/TheStreamCode).
package/dist/auth.js CHANGED
@@ -101,9 +101,12 @@ export async function pollToken(deviceCode, intervalS, expiresInS) {
101
101
  let interval = Math.max(MIN_INTERVAL_S, Number(intervalS) || 5);
102
102
  const deadline = Date.now() + Number(expiresInS || 600) * 1000;
103
103
  let slowDowns = 0;
104
+ let transportFailures = 0;
104
105
  while (Date.now() < deadline) {
105
- // The token endpoint answers HTTP errors (e.g. 400 authorization_pending)
106
- // as part of the normal flow: never throw here, classify instead.
106
+ // The token endpoint answers OAuth errors (e.g. 400 authorization_pending) as part of the
107
+ // normal flow. A rejected fetch, an unparseable body, or a body carrying neither a token nor an
108
+ // OAuth error is a transport/proxy blip, not a verdict: count it and keep polling until the
109
+ // deadline. Only an explicit, terminal OAuth error aborts the device login.
107
110
  let body = null;
108
111
  try {
109
112
  const res = await fetch(TOKEN_URL, {
@@ -126,18 +129,22 @@ export async function pollToken(deviceCode, intervalS, expiresInS) {
126
129
  catch {
127
130
  body = null;
128
131
  }
129
- if (body && !body.error) {
130
- if (typeof body.access_token !== "string" || !body.access_token) {
131
- throw new Error("token response has no access_token");
132
- }
132
+ if (body && typeof body.access_token === "string" && body.access_token) {
133
133
  return body.access_token;
134
134
  }
135
135
  if (body?.error === "slow_down") {
136
136
  slowDowns += 1;
137
137
  interval = Math.max(MIN_INTERVAL_S, interval + SLOW_DOWN_STEP_S);
138
138
  }
139
- else if (body?.error !== "authorization_pending") {
140
- throw new Error(`login failed: ${body?.error || "unreadable token response"}`);
139
+ else if (body?.error === "authorization_pending") {
140
+ // Expected while the user completes approval: keep polling at the current interval.
141
+ }
142
+ else if (typeof body?.error === "string" && body.error) {
143
+ throw new Error(`login failed: ${body.error}`);
144
+ }
145
+ else {
146
+ // Unreadable response: no body, no token, and no OAuth error to classify.
147
+ transportFailures += 1;
141
148
  }
142
149
  const remaining = deadline - Date.now();
143
150
  if (remaining <= 0)
@@ -146,7 +153,11 @@ export async function pollToken(deviceCode, intervalS, expiresInS) {
146
153
  setTimeout(resolve, Math.min(interval, remaining) * 1000);
147
154
  await promise;
148
155
  }
149
- throw new Error(slowDowns ? "login timed out after slow_down responses" : "login timed out waiting for approval");
156
+ throw new Error(slowDowns
157
+ ? "login timed out after slow_down responses"
158
+ : transportFailures > 0
159
+ ? `login timed out waiting for approval after ${transportFailures} network error${transportFailures === 1 ? "" : "s"}`
160
+ : "login timed out waiting for approval");
150
161
  }
151
162
  export async function mintKey(accessToken) {
152
163
  const res = await fetch(KEY_URL, {
package/package.json CHANGED
@@ -1,17 +1,17 @@
1
1
  {
2
2
  "name": "opencode-muse-auth",
3
- "version": "0.4.0",
3
+ "version": "0.4.1",
4
4
  "description": "Unofficial and experimental: Muse Spark in opencode using a Muse Code subscription credential. May conflict with Meta's terms; use at your own risk. See DISCLAIMER.md.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
7
7
  "type": "module",
8
8
  "license": "MIT",
9
- "author": "TheStreamCode",
10
- "repository": {
11
- "type": "git",
12
- "url": "git+https://github.com/TheStreamCode/opencode-muse-auth.git"
9
+ "author": {
10
+ "name": "Michael Gasperini",
11
+ "url": "https://mikesoft.it"
13
12
  },
14
- "homepage": "https://github.com/TheStreamCode/opencode-muse-auth#readme",
13
+ "repository": "github:TheStreamCode/opencode-muse-auth",
14
+ "homepage": "https://www.npmjs.com/package/opencode-muse-auth",
15
15
  "bugs": {
16
16
  "url": "https://github.com/TheStreamCode/opencode-muse-auth/issues"
17
17
  },
@@ -42,11 +42,11 @@
42
42
  "prepublishOnly": "npm run build"
43
43
  },
44
44
  "devDependencies": {
45
- "@opencode-ai/plugin": "^1.19.0",
46
- "@types/node": "^22.20.3",
45
+ "@opencode-ai/plugin": "^1.19.3",
46
+ "@types/node": "^26.6.5",
47
47
  "typescript": "^7.0.2"
48
48
  },
49
49
  "dependencies": {
50
- "@opencode/plugin": "^2.0.27"
50
+ "@opencode/plugin": "^2.0.32"
51
51
  }
52
52
  }