opencode-muse 0.1.0 → 0.1.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/README.md CHANGED
@@ -16,7 +16,7 @@ Add the published package to your project-level `opencode.jsonc` (or
16
16
  ```jsonc
17
17
  {
18
18
  "$schema": "https://opencode.ai/config.json",
19
- "plugins": ["opencode-muse@0.1.0"],
19
+ "plugins": ["opencode-muse@0.1.1"],
20
20
  }
21
21
  ```
22
22
 
@@ -117,5 +117,6 @@ setup placeholder rather than advertising a model that will fail on use.
117
117
 
118
118
  ## Related projects
119
119
 
120
- - [opencode-muse-auth](https://github.com/TheStreamCode/opencode-muse-auth) lets OpenCode use a Muse Code subscription by extracting the credentials (API key) embedded in that subscription. Unsure this abides by the Muse Code subscription ToS (issue #12](https://github.com/TheStreamCode/opencode-muse-auth/issues/12)).
121
- - [Official Muse Code documenation](https://dev.meta.ai/docs/quickstart#agent-setup), describing how to use the Muse models in OpenCode via API key (a lot more expensive)
120
+ - [opencode-muse-auth](https://github.com/TheStreamCode/opencode-muse-auth) is another credential-extraction/direct-API approach; see its [issue #12](https://github.com/TheStreamCode/opencode-muse-auth/issues/12) for its discussion of subscription-term concerns.
121
+ - [opencode-muse-code](https://github.com/swalker326/opencode-muse-code) is an unofficial direct-API alternative. It reads the Muse CLI login, requests an API key from Meta, and sends model requests to Meta's API from OpenCode instead of routing inference through `muse serve` and MSP. Its subscription eligibility is separate from this plugin's CLI-mediated approach and has not been established by this project.
122
+ - [Official Muse Code documentation](https://dev.meta.ai/docs/quickstart#agent-setup) describes using Muse models in OpenCode through the Meta API.
@@ -1,7 +1,7 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
- import { closeSync, chmodSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
2
+ import { closeSync, chmodSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readlinkSync, readFileSync, realpathSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
3
3
  import { homedir } from "node:os";
4
- import { dirname, isAbsolute, join, relative, resolve, sep, win32 } from "node:path";
4
+ import { dirname, isAbsolute, join, posix, win32 } from "node:path";
5
5
  import { RegistryCorruptError, RegistryStorageError, RegistryValidationError, RegistryWorkspaceMismatchError, } from "../errors.js";
6
6
  const REGISTRY_VERSION = 1;
7
7
  const MAX_ENTRY_BYTES = 1_048_576;
@@ -9,29 +9,82 @@ const HASH_PATTERN = /^[a-f0-9]{64}$/;
9
9
  const UUID_V7_PATTERN = /^[a-f0-9]{8}-[a-f0-9]{4}-7[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/;
10
10
  const IDENTIFIER_PATTERN = /^[A-Za-z0-9._:@+_-]{1,512}$/;
11
11
  const METHOD_PATTERN = /^[a-z][A-Za-z0-9]*(?:\/[A-Za-z][A-Za-z0-9]*)+$/;
12
+ const MAX_PATH_SYMLINKS = 40;
13
+ /** Resolve existing path components while retaining a normalized not-yet-created suffix. */
14
+ function canonicalPath(path, platform) {
15
+ const pathApi = platform === "win32" ? win32 : posix;
16
+ if (platform !== process.platform)
17
+ return pathApi.resolve(path);
18
+ const absolute = pathApi.resolve(path);
19
+ const root = pathApi.parse(absolute).root;
20
+ let current = root;
21
+ let remaining = absolute.slice(root.length).split(pathApi.sep).filter(Boolean);
22
+ let followedSymlinks = 0;
23
+ while (remaining.length > 0) {
24
+ const [component, ...rest] = remaining;
25
+ const candidate = pathApi.join(current, component);
26
+ let stats;
27
+ try {
28
+ stats = lstatSync(candidate);
29
+ }
30
+ catch (error) {
31
+ // A missing component ends filesystem traversal; normalize the remaining suffix lexically.
32
+ if (isMissingFile(error))
33
+ return pathApi.resolve(current, ...remaining);
34
+ throw new RegistryValidationError();
35
+ }
36
+ if (stats.isSymbolicLink()) {
37
+ followedSymlinks += 1;
38
+ if (followedSymlinks > MAX_PATH_SYMLINKS)
39
+ throw new RegistryValidationError();
40
+ let target;
41
+ try {
42
+ target = readlinkSync(candidate);
43
+ }
44
+ catch {
45
+ throw new RegistryValidationError();
46
+ }
47
+ const absoluteTarget = pathApi.isAbsolute(target)
48
+ ? pathApi.resolve(target)
49
+ : pathApi.resolve(current, target);
50
+ const targetRoot = pathApi.parse(absoluteTarget).root;
51
+ current = targetRoot;
52
+ remaining = [
53
+ ...absoluteTarget.slice(targetRoot.length).split(pathApi.sep).filter(Boolean),
54
+ ...rest,
55
+ ];
56
+ continue;
57
+ }
58
+ try {
59
+ current = realpathSync(candidate);
60
+ }
61
+ catch {
62
+ throw new RegistryValidationError();
63
+ }
64
+ remaining = rest;
65
+ }
66
+ return pathApi.resolve(current);
67
+ }
12
68
  /** Refuse a caller-selected state root inside Muse's CLI-owned config directory. */
13
69
  export function assertSafeStateDirectory(path, options = {}) {
14
70
  const env = options.env ?? process.env;
15
71
  const homeDirectory = options.homeDirectory ?? homedir();
16
72
  const platform = options.platform ?? process.platform;
17
73
  const windows = platform === "win32";
18
- const pathJoin = windows ? win32.join : join;
19
- const pathResolve = windows ? win32.resolve : resolve;
20
- const pathRelative = windows ? win32.relative : relative;
21
- const pathSeparator = windows ? win32.sep : sep;
22
- const pathIsAbsolute = windows ? win32.isAbsolute : isAbsolute;
74
+ const pathApi = windows ? win32 : posix;
23
75
  const configuredHome = env.XDG_CONFIG_HOME;
24
- const configHome = requiredString(configuredHome) && pathIsAbsolute(configuredHome)
76
+ const configHome = requiredString(configuredHome) && pathApi.isAbsolute(configuredHome)
25
77
  ? configuredHome
26
- : pathJoin(homeDirectory, ".config");
27
- const museConfigDirectory = pathResolve(pathJoin(configHome, "muse"));
28
- const relativePath = pathRelative(museConfigDirectory, pathResolve(path));
78
+ : pathApi.join(homeDirectory, ".config");
79
+ const museConfigDirectory = canonicalPath(pathApi.join(configHome, "muse"), platform);
80
+ const canonicalStateDirectory = canonicalPath(path, platform);
81
+ const relativePath = pathApi.relative(museConfigDirectory, canonicalStateDirectory);
29
82
  const outsideMuseConfig = relativePath === ".."
30
- || relativePath.startsWith(`..${pathSeparator}`)
31
- || pathIsAbsolute(relativePath);
83
+ || relativePath.startsWith(`..${pathApi.sep}`)
84
+ || pathApi.isAbsolute(relativePath);
32
85
  if (!outsideMuseConfig)
33
86
  throw new RegistryValidationError();
34
- return path;
87
+ return canonicalStateDirectory;
35
88
  }
36
89
  function isRecord(value) {
37
90
  return typeof value === "object" && value !== null && !Array.isArray(value);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "opencode-muse",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "OpenCode plugin backed by the official Muse Code CLI and MSP",
5
5
  "license": "MIT",
6
6
  "type": "module",