mtech-ai-local-runner 0.1.37 → 0.1.38
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 +8 -0
- package/dist/src/daemon/session-opener.js +31 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
## 0.1.38: find the Codex app shipped as ChatGPT.app
|
|
2
|
+
|
|
3
|
+
0.1.37 looked only for `Codex.app`, so a Mac whose Codex app is the ChatGPT app (`/Applications/ChatGPT.app`,
|
|
4
|
+
bundle id `com.openai.codex`) reported `chatgpt: null` and got no [Open in ChatGPT] button. The runner now also
|
|
5
|
+
takes `ChatGPT.app`, in `/Applications` or `~/Applications`, when its Info.plist registers the `codex` URL
|
|
6
|
+
scheme. It reads the plist with `/usr/bin/plutil` (no shell, five-second timeout). The plain ChatGPT chat app
|
|
7
|
+
registers no `codex` scheme and is still not taken. Nothing else changes.
|
|
8
|
+
|
|
1
9
|
## 0.1.37: open my AI session from Slack
|
|
2
10
|
|
|
3
11
|
The daemon runs a second loop beside the job poller. When its employee presses [Open in Claude] or
|
|
@@ -32,19 +32,46 @@ const LINKS = {
|
|
|
32
32
|
const defaultExists = async (path) => access(path).then(() => true, () => false);
|
|
33
33
|
// A timeout keeps a hung `open` from blocking the opens loop; there is still no shell.
|
|
34
34
|
const defaultRun = (file, args) => execFileAsync(file, args, { timeout: 10_000 });
|
|
35
|
-
|
|
35
|
+
// plutil reads both XML and binary property lists.
|
|
36
|
+
const defaultUrlSchemes = async (infoPlist) => {
|
|
37
|
+
const stdout = await new Promise((resolve, reject) => {
|
|
38
|
+
execFile("/usr/bin/plutil", ["-convert", "json", "-o", "-", infoPlist], { timeout: 5_000 }, (error, out) => error ? reject(error) : resolve(String(out)));
|
|
39
|
+
});
|
|
40
|
+
const types = JSON.parse(stdout).CFBundleURLTypes;
|
|
41
|
+
return Array.isArray(types) ? types.flatMap(type => {
|
|
42
|
+
const schemes = type?.CFBundleURLSchemes;
|
|
43
|
+
return Array.isArray(schemes) ? schemes.filter((scheme) => typeof scheme === "string") : [];
|
|
44
|
+
}) : [];
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* The app each client opens on this Mac: the Claude desktop app before Claude Code's URL handler. The Codex app
|
|
48
|
+
* is Codex.app, or ChatGPT.app when that app registers the codex scheme (the ChatGPT app that bundles Codex
|
|
49
|
+
* ships as com.openai.codex); the plain ChatGPT chat app registers no codex scheme and is not taken.
|
|
50
|
+
*/
|
|
36
51
|
export async function detectSessionSurfaces(deps) {
|
|
37
52
|
const exists = deps.exists ?? defaultExists;
|
|
53
|
+
const urlSchemes = deps.urlSchemes ?? defaultUrlSchemes;
|
|
38
54
|
const anyOf = async (paths) => {
|
|
39
55
|
for (const path of paths)
|
|
40
56
|
if (await exists(path))
|
|
41
57
|
return true;
|
|
42
58
|
return false;
|
|
43
59
|
};
|
|
60
|
+
const registersCodex = async (paths) => {
|
|
61
|
+
for (const path of paths) {
|
|
62
|
+
if (!await exists(path))
|
|
63
|
+
continue;
|
|
64
|
+
const schemes = await urlSchemes(join(path, "Contents", "Info.plist")).catch(() => []);
|
|
65
|
+
if (schemes.includes("codex"))
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
};
|
|
44
70
|
const userApplications = deps.home === "" ? [] : [join(deps.home, "Applications")];
|
|
45
|
-
const
|
|
71
|
+
const inApplications = (app) => [join("/Applications", app), ...userApplications.map(dir => join(dir, app))];
|
|
72
|
+
const desktop = await anyOf(inApplications("Claude.app"));
|
|
46
73
|
const cli = deps.home !== "" && await exists(join(deps.home, "Applications", "Claude Code URL Handler.app"));
|
|
47
|
-
const codex = await anyOf(
|
|
74
|
+
const codex = await anyOf(inApplications("Codex.app")) || await registersCodex(inApplications("ChatGPT.app"));
|
|
48
75
|
return { claude: desktop ? "claude_desktop" : cli ? "claude_cli" : null, chatgpt: codex ? "codex_app" : null };
|
|
49
76
|
}
|
|
50
77
|
export async function sessionOpenAdvertisement(deps) {
|
|
@@ -67,7 +94,7 @@ export class SessionOpener {
|
|
|
67
94
|
return { status: "failed", failureCode: "invalid_request" };
|
|
68
95
|
let clients;
|
|
69
96
|
try {
|
|
70
|
-
clients = await detectSessionSurfaces({ home: this.deps.home, exists: this.deps.exists });
|
|
97
|
+
clients = await detectSessionSurfaces({ home: this.deps.home, exists: this.deps.exists, urlSchemes: this.deps.urlSchemes });
|
|
71
98
|
}
|
|
72
99
|
catch {
|
|
73
100
|
return { status: "failed", failureCode: "handler_missing" };
|