@nikcli-ai/plugin 1.340.0 → 1.348.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/dist/tui.d.ts +5 -3
- package/dist/v2/manifest.d.ts +121 -0
- package/dist/v2/manifest.js +168 -0
- package/dist/v2/tui/plugin.d.ts +13 -1
- package/package.json +8 -4
package/dist/tui.d.ts
CHANGED
|
@@ -85,8 +85,10 @@ export type TuiKeybindSet = {
|
|
|
85
85
|
match: (name: string, evt: ParsedKey) => boolean;
|
|
86
86
|
print: (name: string) => string;
|
|
87
87
|
};
|
|
88
|
+
/** Mirrors the host's `DialogSize` in `@nikcli-ai/tui/ui/dialog`. */
|
|
89
|
+
export type TuiDialogSize = "small" | "medium" | "large" | "xlarge" | "full";
|
|
88
90
|
export type TuiDialogProps = {
|
|
89
|
-
size?:
|
|
91
|
+
size?: TuiDialogSize;
|
|
90
92
|
onClose: () => void;
|
|
91
93
|
children?: JSX.Element;
|
|
92
94
|
};
|
|
@@ -123,8 +125,8 @@ export type TuiTabsApi = {
|
|
|
123
125
|
export type TuiDialogStack = {
|
|
124
126
|
replace: (render: () => JSX.Element, onClose?: () => void) => void;
|
|
125
127
|
clear: () => void;
|
|
126
|
-
setSize: (size:
|
|
127
|
-
readonly size:
|
|
128
|
+
setSize: (size: TuiDialogSize) => void;
|
|
129
|
+
readonly size: TuiDialogSize;
|
|
128
130
|
readonly depth: number;
|
|
129
131
|
readonly open: boolean;
|
|
130
132
|
};
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v2 plugin manifest: what a plugin is, what it may do, and what host it
|
|
3
|
+
* needs.
|
|
4
|
+
*
|
|
5
|
+
* `specs/effect-tui/14-plugin-v2-architecture.md` requirements 2, 6 and 11. The
|
|
6
|
+
* manifest is also the **discriminator**: a module carrying one is a v2 plugin,
|
|
7
|
+
* a module without one is not. That is why it lives in the contract package
|
|
8
|
+
* rather than in a runtime — both the TUI runtime and the server-side loader
|
|
9
|
+
* have to agree on the shape without importing each other.
|
|
10
|
+
*
|
|
11
|
+
* Declaring a capability does not grant it. The host decides what it can
|
|
12
|
+
* supply; the manifest only says what the plugin will ask for. A plugin that
|
|
13
|
+
* calls a capability it did not declare fails with `CapabilityDenied` rather
|
|
14
|
+
* than silently receiving a stub — a stub would make the plugin look like it
|
|
15
|
+
* worked.
|
|
16
|
+
*/
|
|
17
|
+
import { Schema } from "effect";
|
|
18
|
+
/**
|
|
19
|
+
* What a plugin may reach for.
|
|
20
|
+
*
|
|
21
|
+
* The TUI runtime can supply `routes`, `storage` and `http` today. The rest are
|
|
22
|
+
* declared here because the manifest is the stable contract: a plugin written
|
|
23
|
+
* against a future host should not need a new manifest version to say what it
|
|
24
|
+
* wants, and a host that cannot supply a capability refuses at load rather than
|
|
25
|
+
* failing at the first call.
|
|
26
|
+
*/
|
|
27
|
+
export declare const Capability: Schema.Literals<readonly ["tools", "commands", "routes", "keymap", "scheduler", "storage", "http"]>;
|
|
28
|
+
export type Capability = typeof Capability.Type;
|
|
29
|
+
/**
|
|
30
|
+
* Where a plugin came from.
|
|
31
|
+
*
|
|
32
|
+
* `remote-disabled` is a real state, not a placeholder: remote loading is out
|
|
33
|
+
* of scope for v2, so a manifest that claims it is accepted and refused, which
|
|
34
|
+
* is more useful than an unknown-kind error.
|
|
35
|
+
*/
|
|
36
|
+
export declare const Kind: Schema.Literals<readonly ["internal", "user", "remote-disabled"]>;
|
|
37
|
+
export type Kind = typeof Kind.Type;
|
|
38
|
+
/** Semver ranges the host must satisfy. An absent field is "no requirement". */
|
|
39
|
+
export declare const HostRequirements: Schema.Struct<{
|
|
40
|
+
readonly nikcli: Schema.optional<Schema.String>;
|
|
41
|
+
readonly effect: Schema.optional<Schema.String>;
|
|
42
|
+
readonly opentui: Schema.optional<Schema.String>;
|
|
43
|
+
readonly node: Schema.optional<Schema.String>;
|
|
44
|
+
}>;
|
|
45
|
+
export type HostRequirements = typeof HostRequirements.Type;
|
|
46
|
+
/**
|
|
47
|
+
* What the plugin intends to touch outside its own process state.
|
|
48
|
+
*
|
|
49
|
+
* Recorded, not yet enforced — enforcement is EOT-17's permission evaluator,
|
|
50
|
+
* and wiring it here before that exists would mean two evaluators. Present in
|
|
51
|
+
* the manifest now so a plugin does not have to change its manifest to become
|
|
52
|
+
* enforceable later.
|
|
53
|
+
*/
|
|
54
|
+
export declare const Permissions: Schema.Struct<{
|
|
55
|
+
readonly filesystem: Schema.optional<Schema.$Array<Schema.String>>;
|
|
56
|
+
readonly network: Schema.optional<Schema.$Array<Schema.String>>;
|
|
57
|
+
readonly command: Schema.optional<Schema.$Array<Schema.String>>;
|
|
58
|
+
}>;
|
|
59
|
+
export type Permissions = typeof Permissions.Type;
|
|
60
|
+
export declare const ManifestSchema: Schema.Struct<{
|
|
61
|
+
readonly id: Schema.String;
|
|
62
|
+
readonly version: Schema.String;
|
|
63
|
+
readonly kind: Schema.Literals<readonly ["internal", "user", "remote-disabled"]>;
|
|
64
|
+
readonly capabilities: Schema.$Array<Schema.Literals<readonly ["tools", "commands", "routes", "keymap", "scheduler", "storage", "http"]>>;
|
|
65
|
+
readonly hostRequirements: Schema.optional<Schema.Struct<{
|
|
66
|
+
readonly nikcli: Schema.optional<Schema.String>;
|
|
67
|
+
readonly effect: Schema.optional<Schema.String>;
|
|
68
|
+
readonly opentui: Schema.optional<Schema.String>;
|
|
69
|
+
readonly node: Schema.optional<Schema.String>;
|
|
70
|
+
}>>;
|
|
71
|
+
readonly permissions: Schema.optional<Schema.Struct<{
|
|
72
|
+
readonly filesystem: Schema.optional<Schema.$Array<Schema.String>>;
|
|
73
|
+
readonly network: Schema.optional<Schema.$Array<Schema.String>>;
|
|
74
|
+
readonly command: Schema.optional<Schema.$Array<Schema.String>>;
|
|
75
|
+
}>>;
|
|
76
|
+
}>;
|
|
77
|
+
export type Manifest = typeof ManifestSchema.Type;
|
|
78
|
+
declare const ManifestInvalid_base: Schema.Class<ManifestInvalid, Schema.TaggedStruct<"PluginV2ManifestInvalid", {
|
|
79
|
+
readonly spec: Schema.String;
|
|
80
|
+
readonly reason: Schema.String;
|
|
81
|
+
}>, import("effect/Cause").YieldableError>;
|
|
82
|
+
/**
|
|
83
|
+
* Each error renders its own `message`.
|
|
84
|
+
*
|
|
85
|
+
* `Schema.TaggedError` carries the structured fields but leaves `message`
|
|
86
|
+
* empty, and these surface through a plugin loader whose output a human reads
|
|
87
|
+
* in a terminal. An empty message there is a loader that says a plugin failed
|
|
88
|
+
* and not why.
|
|
89
|
+
*/
|
|
90
|
+
export declare class ManifestInvalid extends ManifestInvalid_base {
|
|
91
|
+
get message(): string;
|
|
92
|
+
}
|
|
93
|
+
declare const Incompatible_base: Schema.Class<Incompatible, Schema.TaggedStruct<"PluginV2Incompatible", {
|
|
94
|
+
readonly pluginID: Schema.String;
|
|
95
|
+
readonly requirement: Schema.String;
|
|
96
|
+
readonly required: Schema.String;
|
|
97
|
+
readonly actual: Schema.String;
|
|
98
|
+
}>, import("effect/Cause").YieldableError>;
|
|
99
|
+
export declare class Incompatible extends Incompatible_base {
|
|
100
|
+
get message(): string;
|
|
101
|
+
}
|
|
102
|
+
declare const CapabilityDenied_base: Schema.Class<CapabilityDenied, Schema.TaggedStruct<"PluginV2CapabilityDenied", {
|
|
103
|
+
readonly pluginID: Schema.String;
|
|
104
|
+
readonly capability: Schema.Literals<readonly ["tools", "commands", "routes", "keymap", "scheduler", "storage", "http"]>;
|
|
105
|
+
readonly reason: Schema.String;
|
|
106
|
+
}>, import("effect/Cause").YieldableError>;
|
|
107
|
+
export declare class CapabilityDenied extends CapabilityDenied_base {
|
|
108
|
+
get message(): string;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Validate a candidate manifest.
|
|
112
|
+
*
|
|
113
|
+
* Throws `ManifestInvalid` rather than returning an option: a malformed
|
|
114
|
+
* manifest is a load failure the operator has to see, and the alternative —
|
|
115
|
+
* treating it as "not a v2 plugin" — would silently fall back to the v1 path
|
|
116
|
+
* and report a confusing shape error from there instead.
|
|
117
|
+
*/
|
|
118
|
+
export declare function parseManifest(value: unknown, spec: string): Manifest;
|
|
119
|
+
/** Whether a module looks like a v2 plugin. Requirement 11's discriminator. */
|
|
120
|
+
export declare function hasManifest(value: unknown): boolean;
|
|
121
|
+
export {};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The v2 plugin manifest: what a plugin is, what it may do, and what host it
|
|
3
|
+
* needs.
|
|
4
|
+
*
|
|
5
|
+
* `specs/effect-tui/14-plugin-v2-architecture.md` requirements 2, 6 and 11. The
|
|
6
|
+
* manifest is also the **discriminator**: a module carrying one is a v2 plugin,
|
|
7
|
+
* a module without one is not. That is why it lives in the contract package
|
|
8
|
+
* rather than in a runtime — both the TUI runtime and the server-side loader
|
|
9
|
+
* have to agree on the shape without importing each other.
|
|
10
|
+
*
|
|
11
|
+
* Declaring a capability does not grant it. The host decides what it can
|
|
12
|
+
* supply; the manifest only says what the plugin will ask for. A plugin that
|
|
13
|
+
* calls a capability it did not declare fails with `CapabilityDenied` rather
|
|
14
|
+
* than silently receiving a stub — a stub would make the plugin look like it
|
|
15
|
+
* worked.
|
|
16
|
+
*/
|
|
17
|
+
import { Schema } from "effect";
|
|
18
|
+
/**
|
|
19
|
+
* What a plugin may reach for.
|
|
20
|
+
*
|
|
21
|
+
* The TUI runtime can supply `routes`, `storage` and `http` today. The rest are
|
|
22
|
+
* declared here because the manifest is the stable contract: a plugin written
|
|
23
|
+
* against a future host should not need a new manifest version to say what it
|
|
24
|
+
* wants, and a host that cannot supply a capability refuses at load rather than
|
|
25
|
+
* failing at the first call.
|
|
26
|
+
*/
|
|
27
|
+
export const Capability = Schema.Literals(["tools", "commands", "routes", "keymap", "scheduler", "storage", "http"]);
|
|
28
|
+
/**
|
|
29
|
+
* Where a plugin came from.
|
|
30
|
+
*
|
|
31
|
+
* `remote-disabled` is a real state, not a placeholder: remote loading is out
|
|
32
|
+
* of scope for v2, so a manifest that claims it is accepted and refused, which
|
|
33
|
+
* is more useful than an unknown-kind error.
|
|
34
|
+
*/
|
|
35
|
+
export const Kind = Schema.Literals(["internal", "user", "remote-disabled"]);
|
|
36
|
+
/** Semver ranges the host must satisfy. An absent field is "no requirement". */
|
|
37
|
+
export const HostRequirements = Schema.Struct({
|
|
38
|
+
nikcli: Schema.optional(Schema.String),
|
|
39
|
+
effect: Schema.optional(Schema.String),
|
|
40
|
+
opentui: Schema.optional(Schema.String),
|
|
41
|
+
node: Schema.optional(Schema.String),
|
|
42
|
+
});
|
|
43
|
+
/**
|
|
44
|
+
* What the plugin intends to touch outside its own process state.
|
|
45
|
+
*
|
|
46
|
+
* Recorded, not yet enforced — enforcement is EOT-17's permission evaluator,
|
|
47
|
+
* and wiring it here before that exists would mean two evaluators. Present in
|
|
48
|
+
* the manifest now so a plugin does not have to change its manifest to become
|
|
49
|
+
* enforceable later.
|
|
50
|
+
*/
|
|
51
|
+
export const Permissions = Schema.Struct({
|
|
52
|
+
filesystem: Schema.optional(Schema.Array(Schema.String)),
|
|
53
|
+
network: Schema.optional(Schema.Array(Schema.String)),
|
|
54
|
+
command: Schema.optional(Schema.Array(Schema.String)),
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Plugin ids are scoped — `org:plugin` — so two authors can ship a `git`
|
|
58
|
+
* plugin. The scope is also what namespaces route names, slot ids and storage
|
|
59
|
+
* keys, which is why the separator is fixed rather than a convention.
|
|
60
|
+
*/
|
|
61
|
+
const ID_PATTERN = /^[a-z0-9][a-z0-9-]*:[a-z0-9][a-z0-9._-]*$/;
|
|
62
|
+
export const ManifestSchema = Schema.Struct({
|
|
63
|
+
id: Schema.String,
|
|
64
|
+
version: Schema.String,
|
|
65
|
+
kind: Kind,
|
|
66
|
+
capabilities: Schema.Array(Capability),
|
|
67
|
+
hostRequirements: Schema.optional(HostRequirements),
|
|
68
|
+
permissions: Schema.optional(Permissions),
|
|
69
|
+
}).annotate({ identifier: "PluginV2Manifest" });
|
|
70
|
+
/**
|
|
71
|
+
* Each error renders its own `message`.
|
|
72
|
+
*
|
|
73
|
+
* `Schema.TaggedError` carries the structured fields but leaves `message`
|
|
74
|
+
* empty, and these surface through a plugin loader whose output a human reads
|
|
75
|
+
* in a terminal. An empty message there is a loader that says a plugin failed
|
|
76
|
+
* and not why.
|
|
77
|
+
*/
|
|
78
|
+
export class ManifestInvalid extends Schema.TaggedError()("PluginV2ManifestInvalid", {
|
|
79
|
+
spec: Schema.String,
|
|
80
|
+
reason: Schema.String,
|
|
81
|
+
}) {
|
|
82
|
+
get message() {
|
|
83
|
+
return `Invalid v2 plugin manifest in ${this.spec}: ${this.reason}`;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export class Incompatible extends Schema.TaggedError()("PluginV2Incompatible", {
|
|
87
|
+
pluginID: Schema.String,
|
|
88
|
+
requirement: Schema.String,
|
|
89
|
+
required: Schema.String,
|
|
90
|
+
actual: Schema.String,
|
|
91
|
+
}) {
|
|
92
|
+
get message() {
|
|
93
|
+
return `Plugin ${this.pluginID} requires ${this.requirement} ${this.required} but the host is ${this.actual}`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
export class CapabilityDenied extends Schema.TaggedError()("PluginV2CapabilityDenied", {
|
|
97
|
+
pluginID: Schema.String,
|
|
98
|
+
capability: Capability,
|
|
99
|
+
reason: Schema.String,
|
|
100
|
+
}) {
|
|
101
|
+
get message() {
|
|
102
|
+
return `Plugin ${this.pluginID} was denied capability "${this.capability}": ${this.reason}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
const SEMVER_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
106
|
+
/**
|
|
107
|
+
* Validate a candidate manifest.
|
|
108
|
+
*
|
|
109
|
+
* Throws `ManifestInvalid` rather than returning an option: a malformed
|
|
110
|
+
* manifest is a load failure the operator has to see, and the alternative —
|
|
111
|
+
* treating it as "not a v2 plugin" — would silently fall back to the v1 path
|
|
112
|
+
* and report a confusing shape error from there instead.
|
|
113
|
+
*/
|
|
114
|
+
export function parseManifest(value, spec) {
|
|
115
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
116
|
+
throw new ManifestInvalid({ spec, reason: "manifest must be an object" });
|
|
117
|
+
}
|
|
118
|
+
const raw = value;
|
|
119
|
+
if (typeof raw.id !== "string" || !ID_PATTERN.test(raw.id)) {
|
|
120
|
+
throw new ManifestInvalid({
|
|
121
|
+
spec,
|
|
122
|
+
reason: `manifest.id must be a scoped lowercase id like "org:plugin", got ${JSON.stringify(raw.id)}`,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
if (typeof raw.version !== "string" || !SEMVER_PATTERN.test(raw.version)) {
|
|
126
|
+
throw new ManifestInvalid({
|
|
127
|
+
spec,
|
|
128
|
+
reason: `manifest.version must be semver, got ${JSON.stringify(raw.version)}`,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
if (raw.kind !== "internal" && raw.kind !== "user" && raw.kind !== "remote-disabled") {
|
|
132
|
+
throw new ManifestInvalid({
|
|
133
|
+
spec,
|
|
134
|
+
reason: `manifest.kind must be "internal", "user" or "remote-disabled", got ${JSON.stringify(raw.kind)}`,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
if (!Array.isArray(raw.capabilities)) {
|
|
138
|
+
throw new ManifestInvalid({ spec, reason: "manifest.capabilities must be an array" });
|
|
139
|
+
}
|
|
140
|
+
const allowed = new Set(Capability.literals);
|
|
141
|
+
for (const capability of raw.capabilities) {
|
|
142
|
+
if (typeof capability !== "string" || !allowed.has(capability)) {
|
|
143
|
+
throw new ManifestInvalid({
|
|
144
|
+
spec,
|
|
145
|
+
reason: `unknown capability ${JSON.stringify(capability)}; expected one of ${Capability.literals.join(", ")}`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
// An empty module is a compatibility failure per requirement 1, and a plugin
|
|
150
|
+
// that asks for nothing is the same thing one step earlier: it cannot do
|
|
151
|
+
// anything the host would notice, so loading it only produces a lifetime to
|
|
152
|
+
// manage.
|
|
153
|
+
if (raw.capabilities.length === 0) {
|
|
154
|
+
throw new ManifestInvalid({ spec, reason: "manifest.capabilities must declare at least one capability" });
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
id: raw.id,
|
|
158
|
+
version: raw.version,
|
|
159
|
+
kind: raw.kind,
|
|
160
|
+
capabilities: raw.capabilities,
|
|
161
|
+
hostRequirements: (raw.hostRequirements ?? undefined),
|
|
162
|
+
permissions: (raw.permissions ?? undefined),
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
/** Whether a module looks like a v2 plugin. Requirement 11's discriminator. */
|
|
166
|
+
export function hasManifest(value) {
|
|
167
|
+
return typeof value === "object" && value !== null && "manifest" in value;
|
|
168
|
+
}
|
package/dist/v2/tui/plugin.d.ts
CHANGED
|
@@ -1,7 +1,19 @@
|
|
|
1
|
+
import type { Capability, Kind, Manifest, Permissions } from "../manifest.js";
|
|
1
2
|
import type { Context } from "./context.js";
|
|
2
|
-
export type { Context };
|
|
3
|
+
export type { Capability, Context, Kind, Manifest, Permissions };
|
|
3
4
|
export type Cleanup = () => Promise<void> | void;
|
|
4
5
|
export interface Definition {
|
|
6
|
+
/**
|
|
7
|
+
* Present on a v2 plugin that declares itself
|
|
8
|
+
* (`specs/effect-tui/14-plugin-v2-architecture.md` requirement 2).
|
|
9
|
+
*
|
|
10
|
+
* Optional in the type, not in intent: the v2 shape shipped before the
|
|
11
|
+
* manifest did and every internal plugin is still written that way. A
|
|
12
|
+
* definition without one keeps the whole context surface and its own id —
|
|
13
|
+
* gating it by default, or renaming it for diagnostics, would change the
|
|
14
|
+
* identity the runtime keys slots, routes and enable state on.
|
|
15
|
+
*/
|
|
16
|
+
readonly manifest?: Manifest;
|
|
5
17
|
readonly id: string;
|
|
6
18
|
readonly setup: (context: Context) => Promise<Cleanup | void> | Cleanup | void;
|
|
7
19
|
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@nikcli-ai/plugin",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.348.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"scripts": {
|
|
@@ -27,6 +27,10 @@
|
|
|
27
27
|
"import": "./src/v2/tui/index.ts",
|
|
28
28
|
"types": "./src/v2/tui/index.ts"
|
|
29
29
|
},
|
|
30
|
+
"./v2/manifest": {
|
|
31
|
+
"import": "./src/v2/manifest.ts",
|
|
32
|
+
"types": "./src/v2/manifest.ts"
|
|
33
|
+
},
|
|
30
34
|
"./v2/tui/*": {
|
|
31
35
|
"import": "./src/v2/tui/*.ts",
|
|
32
36
|
"types": "./src/v2/tui/*.ts"
|
|
@@ -65,9 +69,9 @@
|
|
|
65
69
|
}
|
|
66
70
|
},
|
|
67
71
|
"dependencies": {
|
|
68
|
-
"@nikcli-ai/sdk": "1.
|
|
69
|
-
"@opentui/core": "0.5.
|
|
70
|
-
"@opentui/solid": "0.5.
|
|
72
|
+
"@nikcli-ai/sdk": "1.348.0",
|
|
73
|
+
"@opentui/core": "0.5.11",
|
|
74
|
+
"@opentui/solid": "0.5.11",
|
|
71
75
|
"effect": "4.0.0-rc.112",
|
|
72
76
|
"solid-js": "1.9.12",
|
|
73
77
|
"zod": "4.1.8"
|