@wirecat/cli-core 0.448.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 +50 -1
- 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
package/dist/cli/preview.js
CHANGED
|
@@ -6,19 +6,21 @@ import { commandPathOf } from "./command-contract.js";
|
|
|
6
6
|
import { refuseUnknownPermissionKeys } from "./permission-keys.js";
|
|
7
7
|
export class PreviewComplete extends Error {
|
|
8
8
|
}
|
|
9
|
-
export const preview = (command, settings, tool,
|
|
9
|
+
export const preview = (command, settings, tool, keys, messenger = true) => {
|
|
10
10
|
const path = commandPathOf(command);
|
|
11
11
|
const { permissions } = settings;
|
|
12
|
-
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
|
|
12
|
+
const declared = typeof keys === "object" ? keys : undefined;
|
|
13
|
+
const keyOf = typeof keys === "object" ? keys.keyOf : keys;
|
|
14
|
+
refuseUnknownPermissionKeys(command, settings, tool, declared ?? (keyOf && ((one) => keyOf(commandPathOf(one)))));
|
|
15
|
+
const key = declared ? declared.keyOf(path) : (keyOf?.(path) ?? keyForCommand(path));
|
|
16
|
+
const level = key ? levelFor(permissions, key, declared?.defaults).level : "allow";
|
|
17
|
+
for (const source of declared ? [] : readKeysForCommand(path)) {
|
|
16
18
|
if (levelFor(permissions, source).level === "deny")
|
|
17
19
|
throw new CliError("permission_error", `this profile does not permit ${source}`, { permission: source });
|
|
18
20
|
}
|
|
19
21
|
const meta = metaOf(command);
|
|
20
22
|
const writes = (meta.effects !== undefined && writesAnything(meta.effects)) ||
|
|
21
|
-
(key !== null && key !== undefined && WRITE_KEYS.includes(key));
|
|
23
|
+
(!declared && key !== null && key !== undefined && WRITE_KEYS.includes(key));
|
|
22
24
|
if (level === "deny" || (writes && level === "readonly"))
|
|
23
25
|
throw new CliError("permission_error", `this profile does not permit ${path.join(" ")}`, { permission: key });
|
|
24
26
|
return previewBody(command, { key: key ?? null, level }, messenger);
|
package/dist/cli/program.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { Command } from "commander";
|
|
|
2
2
|
import { type AppIdentity } from "../ports/app.js";
|
|
3
3
|
import { type BaseEnvironment } from "./context.js";
|
|
4
4
|
import { type FirstScreen } from "./help/first-screen.js";
|
|
5
|
+
import type { CommandPermissions } from "./permission-keys.js";
|
|
5
6
|
import { type GlobalFlags, type ResolveOptions, type Settings } from "./settings.js";
|
|
6
7
|
export interface ProgramDefinition {
|
|
7
8
|
app: AppIdentity;
|
|
@@ -10,6 +11,8 @@ export interface ProgramDefinition {
|
|
|
10
11
|
/** The CLI's own settings, for keeping a failure that happened before its command could; plain ones without. */
|
|
11
12
|
configuration?: {
|
|
12
13
|
permissionKey?: (path: readonly string[]) => string | null | undefined;
|
|
14
|
+
/** A CLI's own keys (`commandPermissions`), from `settingsFor`: its commands are refused by them before they run. */
|
|
15
|
+
permissions?: CommandPermissions | undefined;
|
|
13
16
|
resolveSettings: (flags?: GlobalFlags, options?: ResolveOptions) => Pick<Settings, "profile" | "keepFailedRuns" | "keepRunsForDays" | "skillHint"> & Partial<Pick<Settings, "recordInput" | "permissions" | "permissionSources" | "configPath" | "savedPermissions" | "sources">>;
|
|
14
17
|
};
|
|
15
18
|
configure?: (program: Command) => void;
|
package/dist/cli/program.js
CHANGED
|
@@ -17,7 +17,7 @@ import { logParsedArgv, logVersion } from "./argv-log.js";
|
|
|
17
17
|
import { commandAvailability } from "./command-availability.js";
|
|
18
18
|
import { commandPathOf } from "./command-contract.js";
|
|
19
19
|
import { outputFor, provide } from "./context.js";
|
|
20
|
-
import { refuseWriteBeforeConnecting } from "./early-refusal.js";
|
|
20
|
+
import { refuseByCommandPermissions, refuseWriteBeforeConnecting } from "./early-refusal.js";
|
|
21
21
|
import { changesAuthorityAt, changesAuthorityNow, isLogin, isStoreChange, judgedEffects, judgedTargets, storeChangeRefusal, } from "./effects.js";
|
|
22
22
|
import { itemOf, renderAnswer, snakeKeys } from "./envelope.js";
|
|
23
23
|
import { byteCount, DEFAULT_COMMAND_MS, DEFAULT_OUTPUT_BYTES, execution } from "./execution.js";
|
|
@@ -221,7 +221,7 @@ const runOnce = async (argv, definition, options) => {
|
|
|
221
221
|
});
|
|
222
222
|
if (!settings.permissions)
|
|
223
223
|
return undefined;
|
|
224
|
-
return commandAvailability(info, settings.permissions, definition.configuration?.permissionKey, changesAuthorityAt(program), readOnlyLockOf(definition.app, env) !== undefined, options.agent !== undefined);
|
|
224
|
+
return commandAvailability(info, settings.permissions, definition.configuration?.permissionKey ?? definition.configuration?.permissions?.keyOf, changesAuthorityAt(program), readOnlyLockOf(definition.app, env) !== undefined, options.agent !== undefined, definition.configuration?.permissions);
|
|
225
225
|
}
|
|
226
226
|
catch {
|
|
227
227
|
// Help must still work when the configuration needs repair.
|
|
@@ -296,6 +296,13 @@ const runOnce = async (argv, definition, options) => {
|
|
|
296
296
|
dryRun: flags.dryRun === true,
|
|
297
297
|
...(definition.configuration ? { resolve: definition.configuration.resolveSettings } : {}),
|
|
298
298
|
});
|
|
299
|
+
if (definition.configuration?.permissions)
|
|
300
|
+
refuseByCommandPermissions(action, definition.configuration.permissions, {
|
|
301
|
+
app: definition.app,
|
|
302
|
+
env: commandEnv,
|
|
303
|
+
dryRun: flags.dryRun === true,
|
|
304
|
+
resolve: definition.configuration.resolveSettings,
|
|
305
|
+
});
|
|
299
306
|
// A client older than the relay's refusal to forward a login still sends one; on serve it would log out serve.
|
|
300
307
|
if (options.relayed && isLogin(action))
|
|
301
308
|
throw new CliError("permission_error", `\`${command} ${path.join(" ")}\` logs the account in or out, which serve never runs for a client — stop ` +
|
|
@@ -394,7 +401,7 @@ const runOnce = async (argv, definition, options) => {
|
|
|
394
401
|
configPath: resolved.configPath ?? "",
|
|
395
402
|
profile: resolved.profile,
|
|
396
403
|
...(resolved.savedPermissions ? { savedPermissions: resolved.savedPermissions } : {}),
|
|
397
|
-
}, command, definition.configuration?.permissionKey, definition.app.messenger !== false);
|
|
404
|
+
}, command, definition.configuration?.permissionKey ?? definition.configuration?.permissions, definition.app.messenger !== false);
|
|
398
405
|
renderAnswer(action, outputFor(action), itemOf(snakeKeys(previewed)));
|
|
399
406
|
throw new PreviewComplete();
|
|
400
407
|
}
|
package/dist/cli/settings.d.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { type AISettings } from "../analysis/settings.js";
|
|
|
4
4
|
import { type AppIdentity } from "../ports/app.js";
|
|
5
5
|
import { type Level, type PermissionKey } from "../sends/permissions.js";
|
|
6
6
|
import { type ConfigIssue } from "./config-issues.js";
|
|
7
|
+
import type { CommandPermissions } from "./permission-keys.js";
|
|
7
8
|
export declare const plain: (rule: string) => (issue: v.BaseIssue<unknown>) => string;
|
|
8
9
|
export declare const count: v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>;
|
|
9
10
|
export declare const flag: v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>;
|
|
@@ -24,6 +25,11 @@ export interface SettingsExtension<T extends object = Record<never, never>> {
|
|
|
24
25
|
defaults?: v.ObjectEntries;
|
|
25
26
|
/** Profiles this messenger keeps in files of its own, beyond the config file and the remembered accounts. */
|
|
26
27
|
profiles?: (env: NodeJS.ProcessEnv) => string[];
|
|
28
|
+
/**
|
|
29
|
+
* A CLI that is not a messenger declares its own permission keys (`commandPermissions`): `permissions` and
|
|
30
|
+
* `--permission` then take any command path, which the program checks against its commands.
|
|
31
|
+
*/
|
|
32
|
+
permissions?: CommandPermissions;
|
|
27
33
|
}
|
|
28
34
|
export interface SettingsExtensionContext {
|
|
29
35
|
flags: GlobalFlags;
|
|
@@ -272,7 +278,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
272
278
|
record: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
273
279
|
record_input: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
274
280
|
keep_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
275
|
-
permissions: v.OptionalSchema<v.RecordSchema<v.
|
|
281
|
+
permissions: v.OptionalSchema<v.RecordSchema<v.GenericSchema<string>, v.PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: v.PicklistIssue) => string>, (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
276
282
|
sends_per_hour: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
277
283
|
requests_per_minute: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 0, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
278
284
|
transcribe_with: v.OptionalSchema<v.PicklistSchema<["auto", "messenger", "local"], (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
@@ -346,7 +352,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
346
352
|
record: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
347
353
|
record_input: v.OptionalSchema<v.BooleanSchema<(issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
348
354
|
keep_runs_for_days: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
349
|
-
permissions: v.OptionalSchema<v.RecordSchema<v.
|
|
355
|
+
permissions: v.OptionalSchema<v.RecordSchema<v.GenericSchema<string>, v.PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: v.PicklistIssue) => string>, (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
350
356
|
sends_per_hour: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 1, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
351
357
|
requests_per_minute: v.OptionalSchema<v.SchemaWithPipe<readonly [v.NumberSchema<(issue: v.BaseIssue<unknown>) => string>, v.IntegerAction<number, (issue: v.BaseIssue<unknown>) => string>, v.MinValueAction<number, 0, (issue: v.BaseIssue<unknown>) => string>]>, undefined>;
|
|
352
358
|
transcribe_with: v.OptionalSchema<v.PicklistSchema<["auto", "messenger", "local"], (issue: v.BaseIssue<unknown>) => string>, undefined>;
|
|
@@ -364,6 +370,7 @@ export declare const settingsFor: <T extends object = Record<never, never>>(app:
|
|
|
364
370
|
readonly profiles: v.OptionalSchema<v.RecordSchema<v.StringSchema<undefined>, v.StrictObjectSchema<v.ObjectEntries, (issue: v.StrictObjectIssue) => string>, "has to be an object of profiles, by name">, undefined>;
|
|
365
371
|
}, (issue: v.StrictObjectIssue) => string>, undefined>;
|
|
366
372
|
}, (issue: v.StrictObjectIssue) => string>;
|
|
373
|
+
permissions?: CommandPermissions | undefined;
|
|
367
374
|
};
|
|
368
375
|
/** What `settingsFor` hands a CLI — named, so a CLI can export it without naming the schema library's types. */
|
|
369
376
|
export type Configuration = ReturnType<typeof settingsFor>;
|
package/dist/cli/settings.js
CHANGED
|
@@ -5,7 +5,7 @@ import { AI_ENTRIES, resolveAISettings } from "../analysis/settings.js";
|
|
|
5
5
|
import { parseDuration } from "../domain/duration.js";
|
|
6
6
|
import { DEFAULT_RERANK_MODEL, RERANK_MODELS } from "../embeddings/models.js";
|
|
7
7
|
import { envName, readOnlyLockOf } from "../ports/app.js";
|
|
8
|
-
import { assertOverridesTighten, CEILING_KEY, LEVELS, layerPermissions, permissionOverrides, RESOURCES, } from "../sends/permissions.js";
|
|
8
|
+
import { assertOverridesTighten, CEILING_KEY, COMMAND_PATH_KEY, LEVELS, layerPermissions, permissionOverrides, RESOURCE_KEY, RESOURCES, } from "../sends/permissions.js";
|
|
9
9
|
import { storePath } from "../store/path.js";
|
|
10
10
|
import { sharedConfigPath } from "../store/shared-config.js";
|
|
11
11
|
import { checkConfigText, issueMessage, readConfigFile, readConfigText, savedPermissionKeys, schemaIssues, settingPathOf, withoutPath, } from "./config-issues.js";
|
|
@@ -39,7 +39,9 @@ const wholeOrZero = (text, name) => {
|
|
|
39
39
|
throw new CliError("validation_error", `${name} has to be a whole number, 0 or more`);
|
|
40
40
|
return value;
|
|
41
41
|
};
|
|
42
|
-
const
|
|
42
|
+
const levelsOf = (key, example) => v.record(key, v.picklist(LEVELS, (issue) => `has to be one of ${LEVELS.join(", ")}, not ${issue.received}`), plain(`has to be an object of command paths and levels, like {"${example}": "ask"}`));
|
|
43
|
+
const permissionLevels = levelsOf(v.pipe(v.string(), v.regex(RESOURCE_KEY, (issue) => `${issue.input} is not a command path — it starts with one of ${RESOURCES.join(", ")}`)), "messages.delete");
|
|
44
|
+
const commandPathLevels = levelsOf(v.pipe(v.string(), v.regex(COMMAND_PATH_KEY, (issue) => `${issue.input} is not a command path, like api or events.delete`)), "events.delete");
|
|
43
45
|
/** valibot's own words ("Expected never but received …") mean nothing to someone editing a file. */
|
|
44
46
|
const objectMessage = (issue) => issue.expected === "never" ? "unknown setting" : "has to be an object, in braces";
|
|
45
47
|
/**
|
|
@@ -122,7 +124,12 @@ export const fromFile = (settings, key, fallback) => first([
|
|
|
122
124
|
* here; a command that re-derived the order would disagree with the others.
|
|
123
125
|
*/
|
|
124
126
|
export const settingsFor = (app, extension = {}) => {
|
|
125
|
-
const
|
|
127
|
+
const declared = extension.permissions;
|
|
128
|
+
const profileEntries = {
|
|
129
|
+
...SHARED_PROFILE_ENTRIES,
|
|
130
|
+
...(declared ? { permissions: v.optional(commandPathLevels) } : {}),
|
|
131
|
+
...extension.profile,
|
|
132
|
+
};
|
|
126
133
|
const defaultsOnly = { ...SHARED_DEFAULTS_ONLY, ...extension.defaults };
|
|
127
134
|
const defaultsEntries = { ...profileEntries, ...defaultsOnly };
|
|
128
135
|
const strictOf = (entries) => v.strictObject(entries, objectMessage);
|
|
@@ -252,8 +259,8 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
252
259
|
from,
|
|
253
260
|
scope?.permissions,
|
|
254
261
|
]);
|
|
255
|
-
const overrides = permissionOverrides(flags.permission);
|
|
256
|
-
assertOverridesTighten(overrides, layerPermissions(fileLayers).levels);
|
|
262
|
+
const overrides = permissionOverrides(flags.permission, declared ? COMMAND_PATH_KEY : RESOURCE_KEY);
|
|
263
|
+
assertOverridesTighten(overrides, layerPermissions(fileLayers).levels, declared?.defaults);
|
|
257
264
|
const permissions = withCeiling(layerPermissions([["flag", overrides], ...fileLayers]), env);
|
|
258
265
|
const updateCheck = first([
|
|
259
266
|
[
|
|
@@ -408,7 +415,15 @@ export const settingsFor = (app, extension = {}) => {
|
|
|
408
415
|
saveConfigFile(path, checked.success ? checked.output : changed);
|
|
409
416
|
return valueAt(checked.success ? checked.output : changed, at) ?? null;
|
|
410
417
|
};
|
|
411
|
-
return {
|
|
418
|
+
return {
|
|
419
|
+
resolveSettings,
|
|
420
|
+
configuredProfiles,
|
|
421
|
+
changeSetting,
|
|
422
|
+
allSettings,
|
|
423
|
+
defaultsOnlyKeys,
|
|
424
|
+
schema,
|
|
425
|
+
...(declared ? { permissions: declared } : {}),
|
|
426
|
+
};
|
|
412
427
|
};
|
|
413
428
|
const isScope = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
414
429
|
const valueAt = (node, path) => path.reduce((at, key) => (isScope(at) || Array.isArray(at) ? at[key] : undefined), node);
|
|
@@ -81,7 +81,7 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
81
81
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
82
82
|
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
83
83
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
84
|
-
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").
|
|
84
|
+
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").GenericSchema<string>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
85
85
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
86
86
|
requests_per_minute: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 0, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
87
87
|
transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
@@ -155,7 +155,7 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
155
155
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
156
156
|
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
157
157
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
158
|
-
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").
|
|
158
|
+
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").GenericSchema<string>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
159
159
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
160
160
|
requests_per_minute: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 0, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
161
161
|
transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
@@ -173,8 +173,9 @@ export declare const configCommand: (app: import("./entry.js").AppIdentity, conf
|
|
|
173
173
|
readonly profiles: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").StringSchema<undefined>, import("valibot").StrictObjectSchema<import("valibot").ObjectEntries, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, undefined>;
|
|
174
174
|
}, (issue: import("valibot").StrictObjectIssue) => string>, undefined>;
|
|
175
175
|
}, (issue: import("valibot").StrictObjectIssue) => string>;
|
|
176
|
+
permissions?: import("./permission-keys.js").CommandPermissions | undefined;
|
|
176
177
|
}, args_2?: {
|
|
177
|
-
permissionKey?: import("./permission-keys.js").
|
|
178
|
+
permissionKey?: import("./permission-keys.js").PermissionKeySource | undefined;
|
|
178
179
|
} | undefined) => import("commander").Command;
|
|
179
180
|
export declare const guideCommand: (app: Pick<import("./entry.js").AppIdentity, "command">, options: import("./guide.js").GuideOptions) => import("commander").Command;
|
|
180
181
|
export declare const addMeetingBatchCommand: (program: import("commander").Command, deps: {
|
|
@@ -284,7 +285,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
284
285
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
285
286
|
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
286
287
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
287
|
-
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").
|
|
288
|
+
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").GenericSchema<string>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
288
289
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
289
290
|
requests_per_minute: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 0, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
290
291
|
transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
@@ -358,7 +359,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
358
359
|
record: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
359
360
|
record_input: import("valibot").OptionalSchema<import("valibot").BooleanSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
360
361
|
keep_runs_for_days: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
361
|
-
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").
|
|
362
|
+
permissions: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").GenericSchema<string>, import("valibot").PicklistSchema<readonly ["deny", "readonly", "ask", "allow"], (issue: import("valibot").PicklistIssue) => string>, (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
362
363
|
sends_per_hour: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 1, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
363
364
|
requests_per_minute: import("valibot").OptionalSchema<import("valibot").SchemaWithPipe<readonly [import("valibot").NumberSchema<(issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").IntegerAction<number, (issue: import("valibot").BaseIssue<unknown>) => string>, import("valibot").MinValueAction<number, 0, (issue: import("valibot").BaseIssue<unknown>) => string>]>, undefined>;
|
|
364
365
|
transcribe_with: import("valibot").OptionalSchema<import("valibot").PicklistSchema<["auto", "messenger", "local"], (issue: import("valibot").BaseIssue<unknown>) => string>, undefined>;
|
|
@@ -376,6 +377,7 @@ export declare const completeCommand: (messenger: import("./index.js").Messenger
|
|
|
376
377
|
readonly profiles: import("valibot").OptionalSchema<import("valibot").RecordSchema<import("valibot").StringSchema<undefined>, import("valibot").StrictObjectSchema<import("valibot").ObjectEntries, (issue: import("valibot").StrictObjectIssue) => string>, "has to be an object of profiles, by name">, undefined>;
|
|
377
378
|
}, (issue: import("valibot").StrictObjectIssue) => string>, undefined>;
|
|
378
379
|
}, (issue: import("valibot").StrictObjectIssue) => string>;
|
|
380
|
+
permissions?: import("./permission-keys.js").CommandPermissions | undefined;
|
|
379
381
|
}, "configuredProfiles">, options?: import("./messenger/complete-command.js").CompletionOptions | undefined) => import("commander").Command;
|
|
380
382
|
export declare const contactsCommand: (messenger: import("./index.js").Messenger) => import("commander").Command;
|
|
381
383
|
export declare const conversationsCommand: (messenger: import("./index.js").Messenger) => import("commander").Command;
|
|
@@ -2,9 +2,13 @@
|
|
|
2
2
|
* The tunnel's address a public listener is reached at, checked one way for every command that opens
|
|
3
3
|
* one. `missing` says what needs it; `reaches` who calls through it, for the https refusal.
|
|
4
4
|
*/
|
|
5
|
-
export declare const publicUrlOf: (given: string | undefined, { missing, reaches }: {
|
|
5
|
+
export declare const publicUrlOf: (given: string | undefined, { missing, reaches, path, }: {
|
|
6
6
|
missing: string;
|
|
7
7
|
reaches: string;
|
|
8
|
+
/** An MCP server may sit under a path of a shared address, `https://<name>.ts.net/tg`, one per tool. */
|
|
9
|
+
path?: boolean;
|
|
8
10
|
}) => URL;
|
|
9
11
|
/** A local port from `--port`, or the command's own default. */
|
|
10
12
|
export declare const portOf: (given: string | undefined, fallback: number) => number;
|
|
13
|
+
/** The connector address under a public address: `https://<name>.ts.net/mcp`, or `…/tg/mcp` under a path. */
|
|
14
|
+
export declare const mcpUrlOf: (publicUrl: URL) => URL;
|
package/dist/http/public-url.js
CHANGED
|
@@ -4,7 +4,7 @@ const EXAMPLE = "--public-url https://<name>.ts.net";
|
|
|
4
4
|
* The tunnel's address a public listener is reached at, checked one way for every command that opens
|
|
5
5
|
* one. `missing` says what needs it; `reaches` who calls through it, for the https refusal.
|
|
6
6
|
*/
|
|
7
|
-
export const publicUrlOf = (given, { missing, reaches }) => {
|
|
7
|
+
export const publicUrlOf = (given, { missing, reaches, path = false, }) => {
|
|
8
8
|
if (!given)
|
|
9
9
|
throw new CliError("configuration_error", `${missing} needs the tunnel's address: ${EXAMPLE}`);
|
|
10
10
|
let url;
|
|
@@ -19,8 +19,14 @@ export const publicUrlOf = (given, { missing, reaches }) => {
|
|
|
19
19
|
const local = url.protocol === "http:" && ["localhost", "127.0.0.1"].includes(url.hostname);
|
|
20
20
|
if (url.protocol !== "https:" && !local)
|
|
21
21
|
throw new CliError("validation_error", `--public-url must be https — ${reaches} through it`);
|
|
22
|
-
if (url.
|
|
23
|
-
throw new CliError("validation_error",
|
|
22
|
+
if (url.search || url.hash || (!path && url.pathname !== "/"))
|
|
23
|
+
throw new CliError("validation_error", path
|
|
24
|
+
? `--public-url is the tunnel's address, or a path of it, without a query: ${EXAMPLE}/tg`
|
|
25
|
+
: `--public-url is the tunnel's address only, without a path: ${EXAMPLE}`);
|
|
26
|
+
if (path && !/^(\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*\/?$/.test(url.pathname))
|
|
27
|
+
throw new CliError("validation_error", "--public-url's path takes letters, digits, dot, dash and underscore: /tg");
|
|
28
|
+
if (path)
|
|
29
|
+
url.pathname = url.pathname.replace(/\/+$/, "") || "/";
|
|
24
30
|
return url;
|
|
25
31
|
};
|
|
26
32
|
/** A local port from `--port`, or the command's own default. */
|
|
@@ -32,3 +38,5 @@ export const portOf = (given, fallback) => {
|
|
|
32
38
|
throw new CliError("validation_error", "--port takes 1–65535");
|
|
33
39
|
return port;
|
|
34
40
|
};
|
|
41
|
+
/** The connector address under a public address: `https://<name>.ts.net/mcp`, or `…/tg/mcp` under a path. */
|
|
42
|
+
export const mcpUrlOf = (publicUrl) => new URL(`${publicUrl.pathname.replace(/\/+$/, "")}/mcp`, publicUrl.origin);
|
package/dist/mcp/agent.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type CommandInfo, type CommandOutput } from "@wirecat/cli-base/commands
|
|
|
2
2
|
import { type ErrorObject } from "@wirecat/cli-base/standards";
|
|
3
3
|
import * as v from "valibot";
|
|
4
4
|
import { type CommandAvailability } from "../cli/command-availability.js";
|
|
5
|
+
import type { CommandPermissions } from "../cli/permission-keys.js";
|
|
5
6
|
export type { CommandAvailability } from "../cli/command-availability.js";
|
|
6
7
|
import type { RunOptions } from "../cli/program.js";
|
|
7
8
|
import type { StdinField } from "../cli/stdin-field.js";
|
|
@@ -22,6 +23,8 @@ export interface AgentContext {
|
|
|
22
23
|
overrides?: readonly string[];
|
|
23
24
|
/** The key a CLI's own command is checked against, when `keyForCommand` does not know it. */
|
|
24
25
|
keyOf?: (path: readonly string[]) => PermissionKey | null | undefined;
|
|
26
|
+
/** A declared CLI's keys and defaults (`configuration.permissions`); in place of `keyOf`. */
|
|
27
|
+
permissions?: CommandPermissions | undefined;
|
|
25
28
|
authority?: CatalogueOptions["authority"];
|
|
26
29
|
/** The server's one open connection. The command borrows it: it cannot close it, and never opens its own. */
|
|
27
30
|
connection?: () => Promise<MessengerAdapter>;
|
|
@@ -60,6 +63,8 @@ export interface CatalogueOptions {
|
|
|
60
63
|
/** Whether a read-only lock currently blocks messenger changes. */
|
|
61
64
|
readOnly?: boolean;
|
|
62
65
|
keyOf?: (path: readonly string[]) => PermissionKey | null | undefined;
|
|
66
|
+
/** A declared CLI's keys and defaults (`configuration.permissions`); in place of `keyOf`. */
|
|
67
|
+
permissions?: CommandPermissions | undefined;
|
|
63
68
|
/** Whether a command changes what a later run may do (`grantsAuthority`): execution requires `mcp.authority`. */
|
|
64
69
|
authority?: (path: readonly string[]) => boolean;
|
|
65
70
|
/** Arguments left out of every tool, by name. */
|
package/dist/mcp/agent.js
CHANGED
|
@@ -124,7 +124,7 @@ const schemaOf = (fields) => v.object(Object.fromEntries(fields.map((field) => [
|
|
|
124
124
|
const startsWith = (path, prefix) => prefix.every((word, at) => path[at] === word);
|
|
125
125
|
/** Build the discoverable command catalogue; profile permissions annotate availability. */
|
|
126
126
|
export const toolsFromCommands = (commands, options = {}) => {
|
|
127
|
-
const { root = [], exclude = [], levels, keyOf, authority, readOnly = false } = options;
|
|
127
|
+
const { root = [], exclude = [], levels, keyOf, authority, readOnly = false, permissions } = options;
|
|
128
128
|
return flatten(commands)
|
|
129
129
|
.filter((info) =>
|
|
130
130
|
// A group is offered only when it runs on its own; without an action it only prints its help.
|
|
@@ -136,9 +136,11 @@ export const toolsFromCommands = (commands, options = {}) => {
|
|
|
136
136
|
info.lifetime === undefined &&
|
|
137
137
|
(options.includeUnavailable === true ||
|
|
138
138
|
(!(readOnly && (changesMessenger(info) || authority?.(info.path))) &&
|
|
139
|
-
(levels === undefined
|
|
139
|
+
(levels === undefined && !(readOnly && permissions)
|
|
140
|
+
? true
|
|
141
|
+
: commandAvailability(info, levels ?? {}, keyOf, authority, readOnly, true, permissions).available))))
|
|
140
142
|
.map((info) => ({
|
|
141
|
-
availability: () => commandAvailability(info, options.currentLevels?.() ?? levels ?? {}, keyOf, authority, readOnly),
|
|
143
|
+
availability: () => commandAvailability(info, options.currentLevels?.() ?? levels ?? {}, keyOf, authority, readOnly, true, permissions),
|
|
142
144
|
command: info.path.slice(root.length).join(" "),
|
|
143
145
|
path: info.path,
|
|
144
146
|
title: info.summary ?? info.description,
|
|
@@ -189,7 +191,8 @@ export const runCommandForAgent = async (program, path, input, ctx) => {
|
|
|
189
191
|
if (info.lifetime)
|
|
190
192
|
throw new CliError("validation_error", `"${path.join(" ")}" runs until it is stopped; an agent call must return`);
|
|
191
193
|
const levels = ctx.levels?.();
|
|
192
|
-
const availability = (levels || ctx.readOnly
|
|
194
|
+
const availability = (levels || ctx.readOnly || ctx.permissions) &&
|
|
195
|
+
commandAvailability(info, levels ?? {}, ctx.keyOf, ctx.authority, ctx.readOnly, true, ctx.permissions);
|
|
193
196
|
if (availability && !availability.available)
|
|
194
197
|
throw new CliError("permission_error", `${path.join(" ")}: ${availability.reason}`, {
|
|
195
198
|
sent: false,
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { Readable, Writable } from "node:stream";
|
|
2
|
+
import { McpServer, type Tool } from "@modelcontextprotocol/server";
|
|
3
|
+
import type { HttpOptions } from "./http/serve.js";
|
|
4
|
+
export declare const CHILD_IDLE_MS: number;
|
|
5
|
+
/** How to start the one-profile server for a profile: `<node> <script> <profile> mcp`. */
|
|
6
|
+
export interface ChildSpec {
|
|
7
|
+
command: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
env: NodeJS.ProcessEnv;
|
|
10
|
+
}
|
|
11
|
+
export interface AllProfilesOptions {
|
|
12
|
+
/** `tg`: the tools' prefix and the server's name. */
|
|
13
|
+
command: string;
|
|
14
|
+
version: string;
|
|
15
|
+
/** `Telegram`: what the `profile` field says it picks. */
|
|
16
|
+
name: string;
|
|
17
|
+
/** Read again for each new session, so a profile set up while serving over HTTP shows up. */
|
|
18
|
+
profiles: () => readonly string[];
|
|
19
|
+
child: (profile: string) => ChildSpec;
|
|
20
|
+
instructions: string;
|
|
21
|
+
idleMs?: number;
|
|
22
|
+
/** A child's stderr, line by line; never this process's stdout, which carries the protocol. */
|
|
23
|
+
note?: (line: string) => void;
|
|
24
|
+
}
|
|
25
|
+
/** The child's tool with the one field that picks the profile, required and without a default. */
|
|
26
|
+
export declare const withProfile: (tool: Tool, profiles: readonly string[], name: string) => Tool;
|
|
27
|
+
/**
|
|
28
|
+
* **One server for every profile of a tool** (TASK-26): `<tool> mcp` shows the tools `<tool> <profile> mcp` shows,
|
|
29
|
+
* each with a required `profile`, and runs a call in that profile's own server, started as a child the first time
|
|
30
|
+
* it is needed. The child applies the profile's permissions, keyring, store and audit log, so nothing here decides
|
|
31
|
+
* what a profile may do; this only routes.
|
|
32
|
+
*/
|
|
33
|
+
export declare const allProfilesServer: (options: AllProfilesOptions) => {
|
|
34
|
+
build: () => McpServer;
|
|
35
|
+
close: () => Promise<void>;
|
|
36
|
+
};
|
|
37
|
+
/** Over stdin and stdout until the client closes stdin or the process is told to stop; then the children close. */
|
|
38
|
+
export declare const serveAllProfilesOverStdio: (options: AllProfilesOptions, io?: {
|
|
39
|
+
stdin?: Readable;
|
|
40
|
+
stdout?: Writable;
|
|
41
|
+
signal?: AbortSignal;
|
|
42
|
+
}) => Promise<void>;
|
|
43
|
+
/** Over HTTP behind the tunnel, one login for every profile; the caller keeps it running and closes it. */
|
|
44
|
+
export declare const serveAllProfilesOverHttp: (options: AllProfilesOptions, http: Omit<HttpOptions, "appName" | "onError">) => Promise<{
|
|
45
|
+
url: URL;
|
|
46
|
+
close: () => Promise<void>;
|
|
47
|
+
}>;
|