@supermux/core 0.15.1 → 0.16.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/dist/context/agents.js +7 -6
- package/dist/context/plugins.js +3 -3
- package/dist/keeper/client.js +22 -2
- package/dist/keeper/keeper.js +2 -0
- package/dist/private-files.d.ts +5 -0
- package/dist/private-files.js +11 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/context/agents.js
CHANGED
|
@@ -9,12 +9,12 @@
|
|
|
9
9
|
* into place, and the links made here (a plugin, a skills folder) are new: making one never
|
|
10
10
|
* follows one.
|
|
11
11
|
*/
|
|
12
|
-
import { lstatSync, readFileSync, readdirSync, readlinkSync, rmSync
|
|
12
|
+
import { lstatSync, readFileSync, readdirSync, readlinkSync, rmSync } from "node:fs";
|
|
13
13
|
import { basename, join } from "node:path";
|
|
14
14
|
import { mappedPluginDrops, mappedPluginServers, readPlugin, writeSkillsPlugin } from "./plugins.js";
|
|
15
15
|
import { reloadOnlyUpdates } from "./update.js";
|
|
16
16
|
import { writeAntigravityMcpConfig, writeAntigravityPlugins } from "../environment/antigravity.js";
|
|
17
|
-
import { assertRealDirectory, realDirectoryIn, writePrivateFile } from "../private-files.js";
|
|
17
|
+
import { assertRealDirectory, linkDirectory, realDirectoryIn, writePrivateFile } from "../private-files.js";
|
|
18
18
|
const dropped = (context, kind, item) => context.dropped.some(drop => drop.kind === kind && drop.item === item);
|
|
19
19
|
/** What a launch context applies: its items minus the ones the core dropped. */
|
|
20
20
|
export const appliedContext = (context) => applied(context);
|
|
@@ -42,7 +42,7 @@ function pluginFolder(context, skills, plugins, manifests) {
|
|
|
42
42
|
const entries = [];
|
|
43
43
|
plugins.forEach((path, index) => {
|
|
44
44
|
const link = join(folder, `${String(index + 1).padStart(2, "0")}-${basename(path)}`);
|
|
45
|
-
|
|
45
|
+
linkDirectory(path, link);
|
|
46
46
|
entries.push(link);
|
|
47
47
|
});
|
|
48
48
|
skills.forEach((root, index) => {
|
|
@@ -96,7 +96,7 @@ export function editPluginFolder(directory, changes, manifests) {
|
|
|
96
96
|
rmSync(path, { recursive: true, force: true });
|
|
97
97
|
undo.push(() => {
|
|
98
98
|
if (entry.kind === "plugins")
|
|
99
|
-
|
|
99
|
+
linkDirectory(entry.path, path);
|
|
100
100
|
else
|
|
101
101
|
writeSkillsPlugin(path, entry.name, entry.path, manifests);
|
|
102
102
|
});
|
|
@@ -104,8 +104,9 @@ export function editPluginFolder(directory, changes, manifests) {
|
|
|
104
104
|
}
|
|
105
105
|
else if (change.kind === "plugins") {
|
|
106
106
|
const path = join(folder, `${String(++pluginIndex).padStart(2, "0")}-${basename(change.item)}`);
|
|
107
|
-
|
|
108
|
-
|
|
107
|
+
linkDirectory(change.item, path);
|
|
108
|
+
// recursive: Bun on Windows refuses a plain rm of a junction (EFAULT); it still only removes the link.
|
|
109
|
+
undo.push(() => rmSync(path, { recursive: true, force: true }));
|
|
109
110
|
}
|
|
110
111
|
else {
|
|
111
112
|
const name = `supermux-skills-${++skillsIndex}`;
|
package/dist/context/plugins.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { existsSync, readFileSync, readdirSync, statSync
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
2
2
|
import { basename, dirname, join } from "node:path";
|
|
3
|
-
import { assertRealDirectory, realDirectoryIn, writePrivateFile } from "../private-files.js";
|
|
3
|
+
import { assertRealDirectory, linkDirectory, realDirectoryIn, writePrivateFile } from "../private-files.js";
|
|
4
4
|
import { MCP_SERVER_NAME } from "./index.js";
|
|
5
5
|
function isDirectory(path) {
|
|
6
6
|
try {
|
|
@@ -127,6 +127,6 @@ export function writeSkillsPlugin(dir, name, skillsRoot, manifests) {
|
|
|
127
127
|
...(manifest === ".cursor-plugin" ? { skills: "./skills/" } : {}),
|
|
128
128
|
}, null, 2) + "\n", () => assertRealDirectory(folder));
|
|
129
129
|
}
|
|
130
|
-
|
|
130
|
+
linkDirectory(skillsRoot, join(wrapper, "skills"));
|
|
131
131
|
return wrapper;
|
|
132
132
|
}
|
package/dist/keeper/client.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { spawn } from 'node:child_process';
|
|
2
2
|
import { randomBytes } from 'node:crypto';
|
|
3
|
-
import { lstatSync, mkdirSync, realpathSync } from 'node:fs';
|
|
3
|
+
import { lstatSync, mkdirSync, readdirSync, realpathSync } from 'node:fs';
|
|
4
4
|
import { lstat, rm, unlink } from 'node:fs/promises';
|
|
5
5
|
import { createConnection } from 'node:net';
|
|
6
|
-
import { dirname, join, resolve } from 'node:path';
|
|
6
|
+
import { basename, dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { CoreError } from '../errors.js';
|
|
8
8
|
import { assertNoLinksIn, assertRealDirectory, present, readPrivateFile, realDirectoryIn, writePrivateFile } from '../private-files.js';
|
|
9
9
|
import { CORE_SUBCOMMANDS, selfEntry } from '../self-entry.js';
|
|
@@ -114,6 +114,8 @@ function mine(info) {
|
|
|
114
114
|
* reboot emptied it, say) and listen there, to be sent a session's token.
|
|
115
115
|
*/
|
|
116
116
|
function trustedSocket(dir, path) {
|
|
117
|
+
if (process.platform === 'win32')
|
|
118
|
+
return listedSocket(path);
|
|
117
119
|
try {
|
|
118
120
|
const socket = lstatSync(path);
|
|
119
121
|
if (!socket.isSocket() || !mine(socket))
|
|
@@ -127,12 +129,29 @@ function trustedSocket(dir, path) {
|
|
|
127
129
|
return false;
|
|
128
130
|
}
|
|
129
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Windows: an AF_UNIX socket file is listed in its folder but cannot be stat'ed (EACCES), and a uid or
|
|
134
|
+
* mode would mean nothing there; its folders (the keeper's, the user's own temp dir) are the user's.
|
|
135
|
+
*/
|
|
136
|
+
function listedSocket(path) {
|
|
137
|
+
try {
|
|
138
|
+
return readdirSync(dirname(path)).includes(basename(path));
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
return false;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
130
144
|
/** The socket a new keeper of `dir` listens on: the first candidate whose directory this user can make private. */
|
|
131
145
|
function newSocketPath(dir) {
|
|
132
146
|
return privateSocketPath(keeperSocketPaths(dir), join(dir, 'keeper.sock'));
|
|
133
147
|
}
|
|
134
148
|
/** Deletes the socket a keeper left at `path` when it is this user's; anything else there stays (the path comes from a status file). */
|
|
135
149
|
async function removeSocket(path) {
|
|
150
|
+
if (process.platform === 'win32') {
|
|
151
|
+
if (listedSocket(path))
|
|
152
|
+
await unlink(path).catch(() => { });
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
136
155
|
try {
|
|
137
156
|
const info = await lstat(path);
|
|
138
157
|
if (info.isSocket() && mine(info))
|
|
@@ -421,6 +440,7 @@ async function connectLocked(options) {
|
|
|
421
440
|
const child = spawn(entry.command, entry.args, {
|
|
422
441
|
detached: true,
|
|
423
442
|
stdio: 'ignore',
|
|
443
|
+
windowsHide: true,
|
|
424
444
|
cwd: entry.cwd,
|
|
425
445
|
env: {
|
|
426
446
|
...process.env,
|
package/dist/keeper/keeper.js
CHANGED
|
@@ -254,6 +254,8 @@ async function main() {
|
|
|
254
254
|
env: agentEnv,
|
|
255
255
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
256
256
|
shell: false,
|
|
257
|
+
// Windows: the keeper has no console, so a console agent (claude.exe) would get a visible one of its own.
|
|
258
|
+
windowsHide: true,
|
|
257
259
|
});
|
|
258
260
|
if (!captureStderr)
|
|
259
261
|
agent.stderr?.resume();
|
package/dist/private-files.d.ts
CHANGED
|
@@ -6,6 +6,11 @@ export declare function realDirectoryIn(parent: string, name: string): string;
|
|
|
6
6
|
export declare function realStateDirectory(path: string): string;
|
|
7
7
|
/** Refuses a link, or anything of another user, among the entries of `dir`. */
|
|
8
8
|
export declare function assertNoLinksIn(dir: string): void;
|
|
9
|
+
/**
|
|
10
|
+
* A new link at `path` to directory `target`. On Windows a junction: a symbolic link there needs
|
|
11
|
+
* Developer Mode or an elevated process (EPERM otherwise), a junction needs neither.
|
|
12
|
+
*/
|
|
13
|
+
export declare function linkDirectory(target: string, path: string, platform?: NodeJS.Platform): void;
|
|
9
14
|
/** Whether `path` names anything (a link included), without following it. */
|
|
10
15
|
export declare function present(path: string): boolean;
|
|
11
16
|
/** A new 0600 file of this user's at `path`, open with `flags`: never an existing file, never through a link. */
|
package/dist/private-files.js
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* (O_CREAT | O_EXCL, 0600) and renamed into place, so an existing name is never opened for writing.
|
|
7
7
|
*/
|
|
8
8
|
import { randomBytes } from "node:crypto";
|
|
9
|
-
import { closeSync, constants, fchmodSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, renameSync, unlinkSync, writeSync } from "node:fs";
|
|
9
|
+
import { closeSync, constants, fchmodSync, fstatSync, lstatSync, mkdirSync, openSync, readdirSync, readFileSync, realpathSync, renameSync, symlinkSync, unlinkSync, writeSync } from "node:fs";
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import { CoreError } from "./errors.js";
|
|
12
12
|
/** Windows has no O_NOFOLLOW (nor keepers): there the lstat checks remain. */
|
|
@@ -64,6 +64,9 @@ export function assertNoLinksIn(dir) {
|
|
|
64
64
|
catch (error) {
|
|
65
65
|
if (code(error) === "ENOENT")
|
|
66
66
|
continue;
|
|
67
|
+
// A Windows AF_UNIX socket (a keeper's) cannot be stat'ed; a link or junction can.
|
|
68
|
+
if (code(error) === "EACCES" && process.platform === "win32")
|
|
69
|
+
continue;
|
|
67
70
|
throw error;
|
|
68
71
|
}
|
|
69
72
|
if (info.isSymbolicLink())
|
|
@@ -72,6 +75,13 @@ export function assertNoLinksIn(dir) {
|
|
|
72
75
|
throw refuse(path, "it belongs to another user");
|
|
73
76
|
}
|
|
74
77
|
}
|
|
78
|
+
/**
|
|
79
|
+
* A new link at `path` to directory `target`. On Windows a junction: a symbolic link there needs
|
|
80
|
+
* Developer Mode or an elevated process (EPERM otherwise), a junction needs neither.
|
|
81
|
+
*/
|
|
82
|
+
export function linkDirectory(target, path, platform = process.platform) {
|
|
83
|
+
symlinkSync(target, path, platform === "win32" ? "junction" : "dir");
|
|
84
|
+
}
|
|
75
85
|
/** Whether `path` names anything (a link included), without following it. */
|
|
76
86
|
export function present(path) {
|
|
77
87
|
try {
|
package/dist/version.d.ts
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* The version of this package, announced to agents as the `clientInfo` version of their handshake.
|
|
3
3
|
* Keep it equal to `version` in package.json: tests/alias.test.ts fails when they differ.
|
|
4
4
|
*/
|
|
5
|
-
export declare const LIBRARY_VERSION = "0.
|
|
5
|
+
export declare const LIBRARY_VERSION = "0.16.1";
|
package/dist/version.js
CHANGED
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* The version of this package, announced to agents as the `clientInfo` version of their handshake.
|
|
3
3
|
* Keep it equal to `version` in package.json: tests/alias.test.ts fails when they differ.
|
|
4
4
|
*/
|
|
5
|
-
export const LIBRARY_VERSION = "0.
|
|
5
|
+
export const LIBRARY_VERSION = "0.16.1";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@supermux/core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.16.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Build your own app on Claude Code, Codex, Cursor, OpenCode, Grok Build and Antigravity: one TypeScript API for sessions, events and permission prompts.",
|
|
6
6
|
"keywords": [
|