@mindbridgeio/muse 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.
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { createClaudeOAuthRuntime, createCodexOAuthRuntime, createMuseOAuthRuntime, runOAuthHook } from "./agent-hooks.mjs";
4
+
5
+ const host = process.argv[2];
6
+ if (host === "codex") runOAuthHook(createCodexOAuthRuntime);
7
+ else if (host === "claude") runOAuthHook(createClaudeOAuthRuntime);
8
+ else if (host === "muse") runOAuthHook(createMuseOAuthRuntime);
9
+ else process.exitCode = 2;
@@ -0,0 +1,159 @@
1
+ import { execFile } from "node:child_process";
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { chmod, mkdir, readFile, realpath, writeFile } from "node:fs/promises";
4
+ import { basename, relative, resolve } from "node:path";
5
+ import { promisify } from "node:util";
6
+
7
+ const execFileAsync = promisify(execFile);
8
+ const DETECTION_TIMEOUT_MS = 1_000;
9
+
10
+ function text(value) {
11
+ return typeof value === "string" ? value.trim() : "";
12
+ }
13
+
14
+ function digest(value, length) {
15
+ return createHash("sha256").update(value).digest("hex").slice(0, length);
16
+ }
17
+
18
+ async function beforeDeadline(operation, deadline) {
19
+ const timeout = deadline - Date.now();
20
+ if (timeout <= 0) throw new Error("project detection timed out");
21
+ let timer;
22
+ try {
23
+ return await Promise.race([
24
+ operation(),
25
+ new Promise((_, reject) => {
26
+ timer = setTimeout(() => reject(new Error("project detection timed out")), timeout);
27
+ }),
28
+ ]);
29
+ } finally {
30
+ clearTimeout(timer);
31
+ }
32
+ }
33
+
34
+ /** Returns a credential-free host/path identity for common Git remote forms. */
35
+ export function normalizeGitRemote(value) {
36
+ const remote = text(value);
37
+ if (!remote || /^[A-Za-z]:[\\/]/.test(remote)) return "";
38
+ let host = "";
39
+ let port = "";
40
+ let path = "";
41
+ if (remote.includes("://")) {
42
+ try {
43
+ const parsed = new URL(remote);
44
+ if (!parsed.hostname || !["http:", "https:", "ssh:", "git:"].includes(parsed.protocol)) return "";
45
+ host = parsed.hostname.toLowerCase();
46
+ port = parsed.port;
47
+ path = parsed.pathname;
48
+ } catch {
49
+ return "";
50
+ }
51
+ } else {
52
+ const match = remote.match(/^(?:[^@/]+@)?([^:/]+):(.+)$/);
53
+ if (!match) return "";
54
+ host = match[1].toLowerCase();
55
+ path = match[2];
56
+ }
57
+ path = path.replace(/[?#].*$/, "").replaceAll("\\", "/").replace(/^\/+|\/+$/g, "").replace(/\.git$/i, "");
58
+ return host && path ? `${host}${port ? `:${port}` : ""}/${path}` : "";
59
+ }
60
+
61
+ async function git(cwd, args, deadline, run = execFileAsync) {
62
+ const timeout = deadline - Date.now();
63
+ if (timeout <= 0) throw new Error("project detection timed out");
64
+ const result = await beforeDeadline(() => run("git", ["-C", cwd, ...args], {
65
+ timeout,
66
+ maxBuffer: 64 * 1024,
67
+ windowsHide: true,
68
+ }), deadline);
69
+ return text(typeof result === "string" ? result : result.stdout);
70
+ }
71
+
72
+ async function canonical(path, deadline) {
73
+ if (!text(path)) return "";
74
+ try {
75
+ return await beforeDeadline(() => realpath(resolve(path)), deadline);
76
+ } catch {
77
+ return "";
78
+ }
79
+ }
80
+
81
+ async function installID(stateRoot, deadline) {
82
+ const path = resolve(stateRoot, "install-id");
83
+ const read = async () => {
84
+ const value = text(await beforeDeadline(() => readFile(path, "utf8"), deadline));
85
+ if (!value) {
86
+ const error = new Error("install id is empty");
87
+ error.code = "EEMPTY";
88
+ throw error;
89
+ }
90
+ await beforeDeadline(() => chmod(path, 0o600), deadline);
91
+ return value;
92
+ };
93
+ const readAfterRace = async () => {
94
+ while (Date.now() < deadline) {
95
+ try {
96
+ return await read();
97
+ } catch (error) {
98
+ if (error?.code !== "EEMPTY" && error?.code !== "ENOENT") throw error;
99
+ await beforeDeadline(() => new Promise((resolve) => setTimeout(resolve, 5)), deadline);
100
+ }
101
+ }
102
+ throw new Error("project detection timed out");
103
+ };
104
+ try {
105
+ const existing = await read();
106
+ if (existing) return existing;
107
+ } catch (error) {
108
+ if (error?.code === "EEMPTY") return readAfterRace();
109
+ if (error?.code !== "ENOENT") throw error;
110
+ }
111
+ await beforeDeadline(() => mkdir(stateRoot, { recursive: true, mode: 0o700 }), deadline);
112
+ const created = randomUUID();
113
+ try {
114
+ await beforeDeadline(() => writeFile(path, `${created}\n`, { flag: "wx", mode: 0o600 }), deadline);
115
+ return created;
116
+ } catch (error) {
117
+ if (error?.code !== "EEXIST") throw error;
118
+ return readAfterRace();
119
+ }
120
+ }
121
+
122
+ /** Best-effort project metadata for a lifecycle event. */
123
+ export async function resolveProjectMetadata({ cwd, projectRoot, stateRoot, runGit } = {}) {
124
+ const deadline = Date.now() + DETECTION_TIMEOUT_MS;
125
+ const workingDirectory = await canonical(cwd || projectRoot, deadline);
126
+ if (!workingDirectory || !text(stateRoot)) return undefined;
127
+ let root = "";
128
+ try {
129
+ root = await canonical(await git(workingDirectory, ["rev-parse", "--show-toplevel"], deadline, runGit), deadline);
130
+ } catch {
131
+ root = await canonical(projectRoot, deadline) || workingDirectory;
132
+ }
133
+ if (!root) return undefined;
134
+
135
+ let remoteIdentity = "";
136
+ try {
137
+ const remotes = (await git(root, ["remote"], deadline, runGit)).split(/\r?\n/).map(text).filter(Boolean).sort();
138
+ const remote = remotes.includes("origin") ? "origin" : remotes.includes("upstream") ? "upstream" : remotes[0];
139
+ if (remote) remoteIdentity = normalizeGitRemote(await git(root, ["remote", "get-url", remote], deadline, runGit));
140
+ } catch {
141
+ remoteIdentity = "";
142
+ }
143
+
144
+ const relativeDirectory = relative(root, workingDirectory).replaceAll("\\", "/");
145
+ const project = {
146
+ key: "",
147
+ name: basename(remoteIdentity || root),
148
+ root,
149
+ working_directory: workingDirectory,
150
+ ...(relativeDirectory && !relativeDirectory.startsWith("../") ? { relative_directory: relativeDirectory } : {}),
151
+ identity_source: remoteIdentity ? "git_remote" : "local",
152
+ };
153
+ if (remoteIdentity) project.key = `git:${remoteIdentity}`;
154
+ else {
155
+ const id = await installID(stateRoot, deadline);
156
+ project.key = `local:${digest(id, 12)}:${digest(root, 20)}`;
157
+ }
158
+ return { project };
159
+ }
@@ -0,0 +1,52 @@
1
+ {
2
+ "hooks": {
3
+ "SessionStart": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node \"<MINDBRIDGE_MUSE_HOME>/dist/oauth-hook.mjs\" muse",
9
+ "timeout": 30,
10
+ "statusMessage": "Loading MindBridge memory"
11
+ }
12
+ ]
13
+ }
14
+ ],
15
+ "UserPromptSubmit": [
16
+ {
17
+ "hooks": [
18
+ {
19
+ "type": "command",
20
+ "command": "node \"<MINDBRIDGE_MUSE_HOME>/dist/oauth-hook.mjs\" muse",
21
+ "timeout": 10,
22
+ "statusMessage": "Recording MindBridge context"
23
+ }
24
+ ]
25
+ }
26
+ ],
27
+ "Stop": [
28
+ {
29
+ "hooks": [
30
+ {
31
+ "type": "command",
32
+ "command": "node \"<MINDBRIDGE_MUSE_HOME>/dist/oauth-hook.mjs\" muse",
33
+ "timeout": 10,
34
+ "statusMessage": "Recording MindBridge response"
35
+ }
36
+ ]
37
+ }
38
+ ],
39
+ "SessionEnd": [
40
+ {
41
+ "hooks": [
42
+ {
43
+ "type": "command",
44
+ "command": "node \"<MINDBRIDGE_MUSE_HOME>/dist/oauth-hook.mjs\" muse",
45
+ "timeout": 30,
46
+ "statusMessage": "Finalizing MindBridge context"
47
+ }
48
+ ]
49
+ }
50
+ ]
51
+ }
52
+ }
package/package.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "name": "@mindbridgeio/muse",
3
+ "version": "0.1.0",
4
+ "description": "MindBridge MCP and lifecycle hooks for Muse.",
5
+ "license": "UNLICENSED",
6
+ "engines": {
7
+ "node": ">=20"
8
+ },
9
+ "bin": {
10
+ "mindbridge-muse": "bin/mindbridge-muse.mjs"
11
+ },
12
+ "dependencies": {
13
+ "@napi-rs/keyring": "^1.3.0"
14
+ },
15
+ "files": [
16
+ "bin/mindbridge-muse.mjs",
17
+ "hooks/",
18
+ "dist/",
19
+ "README.md"
20
+ ],
21
+ "scripts": {
22
+ "test": "node ../../scripts/build-agent-hook-package.mjs muse && node --test bin/mindbridge-muse.test.mjs",
23
+ "prepack": "node ../../scripts/build-agent-hook-package.mjs muse"
24
+ },
25
+ "publishConfig": {
26
+ "access": "public"
27
+ }
28
+ }