@wirecat/cli-core 0.449.0 → 0.450.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.
- package/CHANGELOG.md +41 -0
- package/dist/cli/command-availability.d.ts +2 -1
- package/dist/cli/command-availability.js +8 -5
- package/dist/cli/config-command.d.ts +3 -3
- package/dist/cli/config-command.js +2 -2
- package/dist/cli/early-refusal.d.ts +13 -0
- package/dist/cli/early-refusal.js +44 -2
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/mcp-server-command.js +9 -5
- package/dist/cli/messenger/mcp-command.d.ts +4 -1
- package/dist/cli/messenger/mcp-command.js +108 -12
- package/dist/cli/permission-keys.d.ts +21 -4
- package/dist/cli/permission-keys.js +31 -1
- package/dist/cli/preview.d.ts +2 -1
- package/dist/cli/preview.js +8 -6
- package/dist/cli/program.d.ts +3 -0
- package/dist/cli/program.js +10 -3
- package/dist/cli/settings.d.ts +9 -2
- package/dist/cli/settings.js +21 -6
- package/dist/cli/shared-command-exports.d.ts +7 -5
- package/dist/http/public-url.d.ts +5 -1
- package/dist/http/public-url.js +11 -3
- package/dist/mcp/agent.d.ts +5 -0
- package/dist/mcp/agent.js +7 -4
- package/dist/mcp/all-profiles.d.ts +47 -0
- package/dist/mcp/all-profiles.js +257 -0
- package/dist/mcp/http/oauth.d.ts +2 -2
- package/dist/mcp/http/oauth.js +8 -7
- package/dist/mcp/http/serve.d.ts +6 -1
- package/dist/mcp/http/serve.js +24 -7
- package/dist/mcp/index.d.ts +2 -1
- package/dist/mcp/index.js +2 -1
- package/dist/mcp/instructions.d.ts +2 -1
- package/dist/mcp/instructions.js +35 -29
- package/dist/mcp/server.js +3 -73
- package/dist/mcp/stdio-wire.d.ts +14 -0
- package/dist/mcp/stdio-wire.js +72 -0
- package/dist/sends/index.d.ts +1 -1
- package/dist/sends/index.js +1 -1
- package/dist/sends/permissions.d.ts +10 -2
- package/dist/sends/permissions.js +15 -7
- package/package.json +3 -3
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { PassThrough, Writable } from "node:stream";
|
|
2
|
+
const lineReader = (onMessage) => {
|
|
3
|
+
let rest = "";
|
|
4
|
+
return (chunk) => {
|
|
5
|
+
const lines = (rest + chunk.toString()).split("\n");
|
|
6
|
+
rest = lines.pop() ?? "";
|
|
7
|
+
for (const line of lines) {
|
|
8
|
+
try {
|
|
9
|
+
const message = JSON.parse(line);
|
|
10
|
+
if (message !== null && typeof message === "object")
|
|
11
|
+
onMessage(message);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
// Not a message; the transport answers it as a parse error.
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
/**
|
|
20
|
+
* Stdin and stdout as the transport sees them, noting each request that comes in and each answer that goes out, so
|
|
21
|
+
* the server can tell when everything asked has been answered. The transport's stdin never ends by itself: the server
|
|
22
|
+
* closes it, after the client's stdin ended and the last answer went out.
|
|
23
|
+
*/
|
|
24
|
+
export const answeredWire = (source, sink) => {
|
|
25
|
+
const asked = new Set();
|
|
26
|
+
let settle;
|
|
27
|
+
const input = new PassThrough();
|
|
28
|
+
const onData = lineReader(({ id, method }) => {
|
|
29
|
+
if (id !== undefined && typeof method === "string")
|
|
30
|
+
asked.add(JSON.stringify(id));
|
|
31
|
+
});
|
|
32
|
+
const output = new Writable({
|
|
33
|
+
write: (chunk, _encoding, done) => {
|
|
34
|
+
noteAnswer(chunk);
|
|
35
|
+
sink.write(chunk, done);
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const noteAnswer = lineReader(({ id, method }) => {
|
|
39
|
+
if (id === undefined || method !== undefined)
|
|
40
|
+
return;
|
|
41
|
+
asked.delete(JSON.stringify(id));
|
|
42
|
+
if (asked.size === 0)
|
|
43
|
+
settle?.();
|
|
44
|
+
});
|
|
45
|
+
const forward = (chunk) => {
|
|
46
|
+
onData(chunk);
|
|
47
|
+
input.write(chunk);
|
|
48
|
+
};
|
|
49
|
+
source.on("data", forward);
|
|
50
|
+
const ended = new Promise((resolve) => {
|
|
51
|
+
source.once("end", resolve);
|
|
52
|
+
source.once("close", resolve);
|
|
53
|
+
});
|
|
54
|
+
return {
|
|
55
|
+
input,
|
|
56
|
+
output,
|
|
57
|
+
ended,
|
|
58
|
+
answered: () => new Promise((resolve) => {
|
|
59
|
+
if (asked.size === 0)
|
|
60
|
+
resolve();
|
|
61
|
+
else
|
|
62
|
+
settle = resolve;
|
|
63
|
+
}),
|
|
64
|
+
/** Lets go of the real stdin, so it no longer holds the process open. */
|
|
65
|
+
release: () => {
|
|
66
|
+
source.off("data", forward);
|
|
67
|
+
if (source.listenerCount("data") === 0)
|
|
68
|
+
source.pause();
|
|
69
|
+
input.end();
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
};
|
package/dist/sends/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ export { type FloodDeadline, FloodMemory, type FloodState, FROZEN_HOLD_MS, flood
|
|
|
3
3
|
export { type Asker, type GuardRequest, guardFor, type SendGuard, type SendGuardOptions, sendGuard, sharedJournal, } from "./guard.js";
|
|
4
4
|
export { currentOperation, guardedWrite, type Operated } from "./guarded.js";
|
|
5
5
|
export { type AccountAction, type ChatAction, type SendEntry, SendJournal, type SendKind, type SendOutcome, sendsPathFor, } from "./journal.js";
|
|
6
|
-
export { CEILING_KEY, DEFAULT_PERMISSIONS, keyForCommand, keyForWrite, LEVELS, type Level, layerPermissions, levelFor, type PermissionKey, permissionOverrides, readKeysForCommand, skipFlagFor, WRITE_KEYS, } from "./permissions.js";
|
|
6
|
+
export { assertOverridesTighten, CEILING_KEY, COMMAND_PATH_KEY, DEFAULT_PERMISSIONS, keyForCommand, keyForWrite, LEVELS, type Level, layerPermissions, levelFor, type PermissionKey, permissionOverrides, readKeysForCommand, skipFlagFor, WRITE_KEYS, } from "./permissions.js";
|
|
7
7
|
export { type Recipient, RecipientList, recipientsPathFor } from "./recipients.js";
|
|
8
8
|
export { newOperationId, newSendId } from "./send-id.js";
|
|
9
9
|
export { MAX_BUFFERED_UPLOAD_BYTES, readUpload, refusedPlace, uploadBlob, uploadBytes, uploadSize, } from "./upload.js";
|
package/dist/sends/index.js
CHANGED
|
@@ -2,7 +2,7 @@ export { FloodMemory, FROZEN_HOLD_MS, floodPathFor, LIMITED_HOLD_MS, } from "./f
|
|
|
2
2
|
export { guardFor, sendGuard, sharedJournal, } from "./guard.js";
|
|
3
3
|
export { currentOperation, guardedWrite } from "./guarded.js";
|
|
4
4
|
export { SendJournal, sendsPathFor, } from "./journal.js";
|
|
5
|
-
export { CEILING_KEY, DEFAULT_PERMISSIONS, keyForCommand, keyForWrite, LEVELS, layerPermissions, levelFor, permissionOverrides, readKeysForCommand, skipFlagFor, WRITE_KEYS, } from "./permissions.js";
|
|
5
|
+
export { assertOverridesTighten, CEILING_KEY, COMMAND_PATH_KEY, DEFAULT_PERMISSIONS, keyForCommand, keyForWrite, LEVELS, layerPermissions, levelFor, permissionOverrides, readKeysForCommand, skipFlagFor, WRITE_KEYS, } from "./permissions.js";
|
|
6
6
|
export { RecipientList, recipientsPathFor } from "./recipients.js";
|
|
7
7
|
export { newOperationId, newSendId } from "./send-id.js";
|
|
8
8
|
export { MAX_BUFFERED_UPLOAD_BYTES, readUpload, refusedPlace, uploadBlob, uploadBytes, uploadSize, } from "./upload.js";
|
|
@@ -62,8 +62,16 @@ export declare const readKeysForCommand: (path: readonly string[]) => Permission
|
|
|
62
62
|
* `undefined` for a path this package does not know — a CLI's own command, which its CLI maps.
|
|
63
63
|
*/
|
|
64
64
|
export declare const keyForCommand: (path: readonly string[]) => PermissionKey | null | undefined;
|
|
65
|
+
/** A messenger's key starts with one of its resources, so a misspelled resource is refused by its shape alone. */
|
|
66
|
+
export declare const RESOURCE_KEY: RegExp;
|
|
67
|
+
/**
|
|
68
|
+
* A key of a CLI that declares its own commands (`commandPermissions`): any command path. Which paths exist is
|
|
69
|
+
* checked against the program, not here. Past the first word a key may hold capitals, as Google's method ids do
|
|
70
|
+
* (`api.calendar.calendarList.list`).
|
|
71
|
+
*/
|
|
72
|
+
export declare const COMMAND_PATH_KEY: RegExp;
|
|
65
73
|
/** Startup overrides are a nearer layer than the saved profile, and never write that profile. */
|
|
66
|
-
export declare const permissionOverrides: (entries?: readonly string[]) => Record<PermissionKey, Level>;
|
|
74
|
+
export declare const permissionOverrides: (entries?: readonly string[], shape?: RegExp) => Record<PermissionKey, Level>;
|
|
67
75
|
/** No startup flag grants access: an override may narrow the profile's level, never lift it or a key under it. */
|
|
68
|
-
export declare const assertOverridesTighten: (overrides: Readonly<Record<PermissionKey, Level>>, profile: Readonly<Record<PermissionKey, Level>>) => void;
|
|
76
|
+
export declare const assertOverridesTighten: (overrides: Readonly<Record<PermissionKey, Level>>, profile: Readonly<Record<PermissionKey, Level>>, defaults?: Readonly<Record<PermissionKey, Level>>) => void;
|
|
69
77
|
export declare const assertRetentionEvidenceRead: (permissions: Readonly<Record<string, Level>>) => void;
|
|
@@ -409,25 +409,33 @@ export const keyForCommand = (path) => {
|
|
|
409
409
|
return path.join(".");
|
|
410
410
|
return undefined;
|
|
411
411
|
};
|
|
412
|
+
/** A messenger's key starts with one of its resources, so a misspelled resource is refused by its shape alone. */
|
|
413
|
+
export const RESOURCE_KEY = new RegExp(`^(${RESOURCES.join("|")})(\\.[a-z][a-z-]*)*$`);
|
|
414
|
+
/**
|
|
415
|
+
* A key of a CLI that declares its own commands (`commandPermissions`): any command path. Which paths exist is
|
|
416
|
+
* checked against the program, not here. Past the first word a key may hold capitals, as Google's method ids do
|
|
417
|
+
* (`api.calendar.calendarList.list`).
|
|
418
|
+
*/
|
|
419
|
+
export const COMMAND_PATH_KEY = /^[a-z][a-z-]*(\.[a-zA-Z][a-zA-Z-]*)*$/;
|
|
412
420
|
/** Startup overrides are a nearer layer than the saved profile, and never write that profile. */
|
|
413
|
-
export const permissionOverrides = (entries = []) => {
|
|
421
|
+
export const permissionOverrides = (entries = [], shape = RESOURCE_KEY) => {
|
|
414
422
|
const levels = {};
|
|
415
|
-
const
|
|
423
|
+
const example = shape === RESOURCE_KEY ? "messages.send=allow" : "api=readonly";
|
|
416
424
|
for (const entry of entries) {
|
|
417
425
|
const [key = "", level = "", extra] = entry.split("=");
|
|
418
|
-
if (!
|
|
419
|
-
throw new CliError("validation_error",
|
|
426
|
+
if (!shape.test(key) || !LEVELS.includes(level) || extra !== undefined)
|
|
427
|
+
throw new CliError("validation_error", `--permission takes a command path and level, e.g. ${example} (deny, readonly, ask, allow)`);
|
|
420
428
|
levels[key] = level;
|
|
421
429
|
}
|
|
422
430
|
return levels;
|
|
423
431
|
};
|
|
424
432
|
/** No startup flag grants access: an override may narrow the profile's level, never lift it or a key under it. */
|
|
425
|
-
export const assertOverridesTighten = (overrides, profile) => {
|
|
433
|
+
export const assertOverridesTighten = (overrides, profile, defaults = DEFAULT_PERMISSIONS) => {
|
|
426
434
|
for (const [key, level] of Object.entries(overrides)) {
|
|
427
435
|
const under = Object.keys(profile).filter((named) => named.startsWith(`${key}.`));
|
|
428
|
-
const lifted = [key, ...under].find((named) => ORDER[level] > ORDER[levelFor(profile, named).level]);
|
|
436
|
+
const lifted = [key, ...under].find((named) => ORDER[level] > ORDER[levelFor(profile, named, defaults).level]);
|
|
429
437
|
if (lifted !== undefined)
|
|
430
|
-
throw new CliError("validation_error", `--permission ${key}=${level} would loosen ${lifted} from ${levelFor(profile, lifted).level}; ` +
|
|
438
|
+
throw new CliError("validation_error", `--permission ${key}=${level} would loosen ${lifted} from ${levelFor(profile, lifted, defaults).level}; ` +
|
|
431
439
|
"it only tightens — change the profile's permissions to loosen it", { key, lifted });
|
|
432
440
|
}
|
|
433
441
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.450.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|
|
@@ -217,7 +217,7 @@
|
|
|
217
217
|
"@types/node": "^22.20.5",
|
|
218
218
|
"@types/pngjs": "6.0.5",
|
|
219
219
|
"@vitest/coverage-v8": "^5.0.5",
|
|
220
|
-
"@wirecat/cli-base": "0.
|
|
220
|
+
"@wirecat/cli-base": "0.63.0",
|
|
221
221
|
"@wirecat/cli-core-legacy-tags": "npm:@wirecat/cli-core@0.414.0",
|
|
222
222
|
"commander": "15.0.0",
|
|
223
223
|
"cspell": "10.4.0",
|
|
@@ -237,7 +237,7 @@
|
|
|
237
237
|
},
|
|
238
238
|
"peerDependencies": {
|
|
239
239
|
"@napi-rs/canvas": ">=0.1.69 <2",
|
|
240
|
-
"@wirecat/cli-base": ">=0.
|
|
240
|
+
"@wirecat/cli-base": ">=0.63.0 <0.64.0",
|
|
241
241
|
"commander": ">=15"
|
|
242
242
|
},
|
|
243
243
|
"bin": {
|