@airongrong/plugin-api 0.0.0-stage → 1.0.0-beta.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 AiToEarn
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,58 @@
1
- # Temporary Holding Version
1
+ # @airongrong/plugin-api
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ AiToEarn 插件 SDK:插件作者唯一需要依赖的 npm 包。纯类型 + 少量校验逻辑,零 NestJS / 零宿主内部依赖。
4
+
5
+ ```bash
6
+ pnpm add @airongrong/plugin-api
7
+ ```
8
+
9
+ ## 你能拿到什么
10
+
11
+ | 模块 | 内容 |
12
+ | --- | --- |
13
+ | `plugin-manifest` | `pluginManifestSchema`(zod)、`verifyManifest()`、`PluginType`、`Capability`、`PluginHookName`、`pluginManifestJsonSchema` |
14
+ | `plugin-module` | `AdapterPluginModule` / `HookPluginModule` / `PluginActivate` 契约 |
15
+ | `channel-adapter` | `ChannelAdapter`、`ChannelMeta` —— 渠道适配器接口 |
16
+ | `host-services` | `HostServices`:宿主授予的 http / log / secrets / storage / kv 能力 |
17
+ | `post-draft` | `PostDraft`、`DraftBlock`、`PublishResult`、`ValidationIssue` —— 内容草稿与发布结果的统一模型 |
18
+ | `plugin-error-code` | `PluginErrorCode`(AIT-PL-001~008)、`AgentErrorCode`(AIT-AG-001~005)、`PluginError`、`AgentError` |
19
+ | `version` | `HOST_API_VERSION`、`isApiCompatible()`、`satisfiesVersion()` —— 宿主/插件 API 兼容性判定 |
20
+
21
+ ## 最小渠道适配器
22
+
23
+ ```ts
24
+ import { Capability, ValidationIssueSeverity, type ChannelAdapter, type HostServices } from '@airongrong/plugin-api'
25
+
26
+ export default function activate(host: HostServices): { adapter: ChannelAdapter } {
27
+ return {
28
+ adapter: {
29
+ meta: { id: 'channel.acme', label: 'Acme', capabilities: [Capability.PublishText, Capability.PublishImage] },
30
+ async authorize(ctx) {
31
+ return { externalAccountId: 'acme-1' }
32
+ },
33
+ validate(draft) {
34
+ return draft.text.length > 5000
35
+ ? [{ severity: ValidationIssueSeverity.Error, message: '正文超长', path: 'text' }]
36
+ : []
37
+ },
38
+ async publish(draft, ctx) {
39
+ host.log.info('publishing', { draftId: draft.draftId, accountId: ctx.accountId })
40
+ return { externalId: 'acme-1', url: 'https://acme.example/p/1', publishedAt: new Date().toISOString() }
41
+ },
42
+ },
43
+ }
44
+ }
45
+ ```
46
+
47
+ 完整字段见 `ChannelAdapter` / `PostDraft`(`draftId` 为幂等键,重复提交必须返回同一结果)。
48
+ 草稿示例:`visibility: DraftVisibility.Public`、`blocks`、`topics` 等。
49
+
50
+ 产物约束:插件 entry 只允许 `import '@airongrong/plugin-api'`(见 `ALLOWED_ENTRY_IMPORT`),
51
+ 动态 `import()` 的参数同样必须是该字面量——计算型写法(如 `import('node:' + 'fs')`)在校验时被拒绝,
52
+ 其他依赖一律拒绝,保证 `.aitpkg` 可离线、零依赖安装。`entry.module` 禁止包含 `..` 路径穿越。
53
+
54
+ ## 版本策略
55
+
56
+ 宿主与插件用 `apiVersion` 字段对齐,`isApiCompatible()` 要求宿主版本命中插件声明区间
57
+ (支持 `^1.0.0`、`>=1.8.0 <3` 等复合区间)且主版本不越界;
58
+ SDK 自身发版遵循 semver,breaking change 必须升主版本并同步 `HOST_API_VERSION`。
@@ -0,0 +1,20 @@
1
+ import type { Capability } from './plugin-manifest';
2
+ import type { AuthContext, AuthResult, CapabilitySet, Metrics, PostDraft, PublishContext, PublishResult, ValidationIssue } from './post-draft';
3
+ export interface ChannelMeta {
4
+ id: string;
5
+ label: string;
6
+ capabilities: Capability[];
7
+ }
8
+ /**
9
+ * 渠道适配器:唯一有网络与密钥权限、必须沙箱执行 + 人工审核的插件类型。
10
+ */
11
+ export interface ChannelAdapter {
12
+ meta: ChannelMeta;
13
+ authorize: (ctx: AuthContext) => Promise<AuthResult>;
14
+ refresh?: (ctx: AuthContext) => Promise<AuthResult>;
15
+ revoke?: (ctx: AuthContext) => Promise<void>;
16
+ /** 不允许静默降级:不支持的 capability 必须返回明确 issue */
17
+ validate: (draft: PostDraft, caps: CapabilitySet) => ValidationIssue[] | Promise<ValidationIssue[]>;
18
+ publish: (draft: PostDraft, ctx: PublishContext) => Promise<PublishResult>;
19
+ metrics?: (externalId: string, ctx: AuthContext) => Promise<Metrics>;
20
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,39 @@
1
+ export interface HostHttpRequest {
2
+ url: string;
3
+ method?: string;
4
+ headers?: Record<string, string>;
5
+ body?: string;
6
+ }
7
+ export interface HostHttpResponse {
8
+ status: number;
9
+ headers: Record<string, string>;
10
+ body: string;
11
+ }
12
+ export interface HostLogMeta {
13
+ [key: string]: unknown;
14
+ }
15
+ export interface HostSecrets {
16
+ /**
17
+ * 密钥以回调参数形式临时可用,调用结束即失效。
18
+ * key 必须在 plugin.json permissions.secrets 中声明。
19
+ */
20
+ use: <T>(key: string, fn: (value: string) => Promise<T> | T) => Promise<T>;
21
+ }
22
+ export interface HostStorage {
23
+ get: (key: string) => Promise<unknown>;
24
+ set: (key: string, value: unknown) => Promise<void>;
25
+ delete: (key: string) => Promise<void>;
26
+ }
27
+ export interface HostServices {
28
+ http: {
29
+ request: (request: HostHttpRequest) => Promise<HostHttpResponse>;
30
+ };
31
+ secrets: HostSecrets;
32
+ /** 按 pluginId + 账号 id 分区,配额见 plugin.json permissions.storage */
33
+ storage: HostStorage;
34
+ log: {
35
+ info: (message: string, meta?: HostLogMeta) => void;
36
+ warn: (message: string, meta?: HostLogMeta) => void;
37
+ error: (message: string, meta?: HostLogMeta) => void;
38
+ };
39
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,7 @@
1
+ export * from './channel-adapter';
2
+ export * from './host-services';
3
+ export * from './plugin-error-code';
4
+ export * from './plugin-manifest';
5
+ export * from './plugin-module';
6
+ export * from './post-draft';
7
+ export * from './version';
package/dist/index.js ADDED
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./channel-adapter"), exports);
18
+ __exportStar(require("./host-services"), exports);
19
+ __exportStar(require("./plugin-error-code"), exports);
20
+ __exportStar(require("./plugin-manifest"), exports);
21
+ __exportStar(require("./plugin-module"), exports);
22
+ __exportStar(require("./post-draft"), exports);
23
+ __exportStar(require("./version"), exports);
@@ -0,0 +1,27 @@
1
+ export declare enum PluginErrorCode {
2
+ InvalidManifest = "AIT-PL-001",
3
+ HashMismatch = "AIT-PL-002",
4
+ UntrustedSignature = "AIT-PL-003",
5
+ ApiVersionIncompatible = "AIT-PL-004",
6
+ PermissionViolation = "AIT-PL-005",
7
+ ActivateTimeout = "AIT-PL-006",
8
+ HookRejected = "AIT-PL-007",
9
+ IdempotencyConflict = "AIT-PL-008"
10
+ }
11
+ export declare class PluginError extends Error {
12
+ readonly code: PluginErrorCode;
13
+ readonly details?: Record<string, unknown> | undefined;
14
+ constructor(code: PluginErrorCode, message: string, details?: Record<string, unknown> | undefined);
15
+ }
16
+ export declare enum AgentErrorCode {
17
+ Unauthorized = "AIT-AG-001",
18
+ WriteNotConfirmed = "AIT-AG-002",
19
+ RateLimited = "AIT-AG-003",
20
+ ContentRiskControl = "AIT-AG-004",
21
+ RevokeTimeout = "AIT-AG-005"
22
+ }
23
+ export declare class AgentError extends Error {
24
+ readonly code: AgentErrorCode;
25
+ readonly details?: Record<string, unknown> | undefined;
26
+ constructor(code: AgentErrorCode, message: string, details?: Record<string, unknown> | undefined);
27
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentError = exports.AgentErrorCode = exports.PluginError = exports.PluginErrorCode = void 0;
4
+ var PluginErrorCode;
5
+ (function (PluginErrorCode) {
6
+ PluginErrorCode["InvalidManifest"] = "AIT-PL-001";
7
+ PluginErrorCode["HashMismatch"] = "AIT-PL-002";
8
+ PluginErrorCode["UntrustedSignature"] = "AIT-PL-003";
9
+ PluginErrorCode["ApiVersionIncompatible"] = "AIT-PL-004";
10
+ PluginErrorCode["PermissionViolation"] = "AIT-PL-005";
11
+ PluginErrorCode["ActivateTimeout"] = "AIT-PL-006";
12
+ PluginErrorCode["HookRejected"] = "AIT-PL-007";
13
+ PluginErrorCode["IdempotencyConflict"] = "AIT-PL-008";
14
+ })(PluginErrorCode || (exports.PluginErrorCode = PluginErrorCode = {}));
15
+ class PluginError extends Error {
16
+ code;
17
+ details;
18
+ constructor(code, message, details) {
19
+ super(message);
20
+ this.code = code;
21
+ this.details = details;
22
+ this.name = 'PluginError';
23
+ }
24
+ }
25
+ exports.PluginError = PluginError;
26
+ var AgentErrorCode;
27
+ (function (AgentErrorCode) {
28
+ AgentErrorCode["Unauthorized"] = "AIT-AG-001";
29
+ AgentErrorCode["WriteNotConfirmed"] = "AIT-AG-002";
30
+ AgentErrorCode["RateLimited"] = "AIT-AG-003";
31
+ AgentErrorCode["ContentRiskControl"] = "AIT-AG-004";
32
+ AgentErrorCode["RevokeTimeout"] = "AIT-AG-005";
33
+ })(AgentErrorCode || (exports.AgentErrorCode = AgentErrorCode = {}));
34
+ class AgentError extends Error {
35
+ code;
36
+ details;
37
+ constructor(code, message, details) {
38
+ super(message);
39
+ this.code = code;
40
+ this.details = details;
41
+ this.name = 'AgentError';
42
+ }
43
+ }
44
+ exports.AgentError = AgentError;
@@ -0,0 +1,149 @@
1
+ import { z } from 'zod';
2
+ export declare enum PluginType {
3
+ Skill = "skill",
4
+ Hook = "hook",
5
+ Adapter = "adapter"
6
+ }
7
+ export declare enum PluginHookName {
8
+ ValidateDraft = "validateDraft",
9
+ BeforePublish = "beforePublish",
10
+ AfterPublish = "afterPublish",
11
+ OnAuthExpired = "onAuthExpired"
12
+ }
13
+ export declare enum Capability {
14
+ PublishText = "publish.text",
15
+ PublishNote = "publish.note",
16
+ PublishImage = "publish.image",
17
+ PublishVideo = "publish.video",
18
+ PublishThread = "publish.thread",
19
+ PublishScheduled = "publish.scheduled",
20
+ MetricsBasic = "metrics.basic",
21
+ MetricsEngagement = "metrics.engagement"
22
+ }
23
+ export declare const PLUGIN_ID_PATTERN: RegExp;
24
+ export declare const pluginDomainSchema: z.ZodString;
25
+ export declare const pluginManifestSchema: z.ZodObject<{
26
+ $schema: z.ZodOptional<z.ZodString>;
27
+ id: z.ZodString;
28
+ version: z.ZodString;
29
+ apiVersion: z.ZodString;
30
+ type: z.ZodEnum<{
31
+ skill: PluginType.Skill;
32
+ hook: PluginType.Hook;
33
+ adapter: PluginType.Adapter;
34
+ }>;
35
+ name: z.ZodOptional<z.ZodString>;
36
+ description: z.ZodString;
37
+ entry: z.ZodOptional<z.ZodObject<{
38
+ module: z.ZodString;
39
+ sha256: z.ZodString;
40
+ }, z.core.$strip>>;
41
+ contributes: z.ZodObject<{
42
+ channels: z.ZodOptional<z.ZodArray<z.ZodObject<{
43
+ id: z.ZodString;
44
+ label: z.ZodString;
45
+ capabilities: z.ZodArray<z.ZodEnum<{
46
+ "publish.text": Capability.PublishText;
47
+ "publish.note": Capability.PublishNote;
48
+ "publish.image": Capability.PublishImage;
49
+ "publish.video": Capability.PublishVideo;
50
+ "publish.thread": Capability.PublishThread;
51
+ "publish.scheduled": Capability.PublishScheduled;
52
+ "metrics.basic": Capability.MetricsBasic;
53
+ "metrics.engagement": Capability.MetricsEngagement;
54
+ }>>;
55
+ }, z.core.$strip>>>;
56
+ hooks: z.ZodOptional<z.ZodArray<z.ZodEnum<{
57
+ validateDraft: PluginHookName.ValidateDraft;
58
+ beforePublish: PluginHookName.BeforePublish;
59
+ afterPublish: PluginHookName.AfterPublish;
60
+ onAuthExpired: PluginHookName.OnAuthExpired;
61
+ }>>>;
62
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
63
+ }, z.core.$strip>;
64
+ permissions: z.ZodObject<{
65
+ network: z.ZodArray<z.ZodString>;
66
+ secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
67
+ storage: z.ZodOptional<z.ZodObject<{
68
+ kv: z.ZodOptional<z.ZodBoolean>;
69
+ bytes: z.ZodOptional<z.ZodNumber>;
70
+ }, z.core.$strip>>;
71
+ }, z.core.$strip>;
72
+ engines: z.ZodObject<{
73
+ aitoearn: z.ZodString;
74
+ }, z.core.$strip>;
75
+ license: z.ZodString;
76
+ publisher: z.ZodOptional<z.ZodObject<{
77
+ id: z.ZodOptional<z.ZodString>;
78
+ name: z.ZodOptional<z.ZodString>;
79
+ keyId: z.ZodOptional<z.ZodString>;
80
+ }, z.core.$strip>>;
81
+ signature: z.ZodOptional<z.ZodString>;
82
+ }, z.core.$strip>;
83
+ export type PluginManifest = z.infer<typeof pluginManifestSchema>;
84
+ /** 插件产物只允许 import 这一个包,其余一律拒绝(§3.3-1 自包含单文件 ESM) */
85
+ export declare const ALLOWED_ENTRY_IMPORT = "@airongrong/plugin-api";
86
+ /**
87
+ * 运行时复用同一套入口源码约束(CLI 的 verify/pack 与宿主加载共用)。
88
+ * 阻止 entry 通过 import 'node:fs' / 'node:child_process' 绕过权限代理。
89
+ */
90
+ export declare function assertPluginEntrySource(source: string, modulePath?: string): void;
91
+ export declare const pluginManifestJsonSchema: z.core.ZodStandardJSONSchemaPayload<z.ZodObject<{
92
+ $schema: z.ZodOptional<z.ZodString>;
93
+ id: z.ZodString;
94
+ version: z.ZodString;
95
+ apiVersion: z.ZodString;
96
+ type: z.ZodEnum<{
97
+ skill: PluginType.Skill;
98
+ hook: PluginType.Hook;
99
+ adapter: PluginType.Adapter;
100
+ }>;
101
+ name: z.ZodOptional<z.ZodString>;
102
+ description: z.ZodString;
103
+ entry: z.ZodOptional<z.ZodObject<{
104
+ module: z.ZodString;
105
+ sha256: z.ZodString;
106
+ }, z.core.$strip>>;
107
+ contributes: z.ZodObject<{
108
+ channels: z.ZodOptional<z.ZodArray<z.ZodObject<{
109
+ id: z.ZodString;
110
+ label: z.ZodString;
111
+ capabilities: z.ZodArray<z.ZodEnum<{
112
+ "publish.text": Capability.PublishText;
113
+ "publish.note": Capability.PublishNote;
114
+ "publish.image": Capability.PublishImage;
115
+ "publish.video": Capability.PublishVideo;
116
+ "publish.thread": Capability.PublishThread;
117
+ "publish.scheduled": Capability.PublishScheduled;
118
+ "metrics.basic": Capability.MetricsBasic;
119
+ "metrics.engagement": Capability.MetricsEngagement;
120
+ }>>;
121
+ }, z.core.$strip>>>;
122
+ hooks: z.ZodOptional<z.ZodArray<z.ZodEnum<{
123
+ validateDraft: PluginHookName.ValidateDraft;
124
+ beforePublish: PluginHookName.BeforePublish;
125
+ afterPublish: PluginHookName.AfterPublish;
126
+ onAuthExpired: PluginHookName.OnAuthExpired;
127
+ }>>>;
128
+ skills: z.ZodOptional<z.ZodArray<z.ZodString>>;
129
+ }, z.core.$strip>;
130
+ permissions: z.ZodObject<{
131
+ network: z.ZodArray<z.ZodString>;
132
+ secrets: z.ZodOptional<z.ZodArray<z.ZodString>>;
133
+ storage: z.ZodOptional<z.ZodObject<{
134
+ kv: z.ZodOptional<z.ZodBoolean>;
135
+ bytes: z.ZodOptional<z.ZodNumber>;
136
+ }, z.core.$strip>>;
137
+ }, z.core.$strip>;
138
+ engines: z.ZodObject<{
139
+ aitoearn: z.ZodString;
140
+ }, z.core.$strip>;
141
+ license: z.ZodString;
142
+ publisher: z.ZodOptional<z.ZodObject<{
143
+ id: z.ZodOptional<z.ZodString>;
144
+ name: z.ZodOptional<z.ZodString>;
145
+ keyId: z.ZodOptional<z.ZodString>;
146
+ }, z.core.$strip>>;
147
+ signature: z.ZodOptional<z.ZodString>;
148
+ }, z.core.$strip>>;
149
+ export declare function verifyManifest(input: unknown): PluginManifest;
@@ -0,0 +1,128 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.pluginManifestJsonSchema = exports.ALLOWED_ENTRY_IMPORT = exports.pluginManifestSchema = exports.pluginDomainSchema = exports.PLUGIN_ID_PATTERN = exports.Capability = exports.PluginHookName = exports.PluginType = void 0;
4
+ exports.assertPluginEntrySource = assertPluginEntrySource;
5
+ exports.verifyManifest = verifyManifest;
6
+ const zod_1 = require("zod");
7
+ const plugin_error_code_1 = require("./plugin-error-code");
8
+ var PluginType;
9
+ (function (PluginType) {
10
+ PluginType["Skill"] = "skill";
11
+ PluginType["Hook"] = "hook";
12
+ PluginType["Adapter"] = "adapter";
13
+ })(PluginType || (exports.PluginType = PluginType = {}));
14
+ var PluginHookName;
15
+ (function (PluginHookName) {
16
+ PluginHookName["ValidateDraft"] = "validateDraft";
17
+ PluginHookName["BeforePublish"] = "beforePublish";
18
+ PluginHookName["AfterPublish"] = "afterPublish";
19
+ PluginHookName["OnAuthExpired"] = "onAuthExpired";
20
+ })(PluginHookName || (exports.PluginHookName = PluginHookName = {}));
21
+ var Capability;
22
+ (function (Capability) {
23
+ Capability["PublishText"] = "publish.text";
24
+ Capability["PublishNote"] = "publish.note";
25
+ Capability["PublishImage"] = "publish.image";
26
+ Capability["PublishVideo"] = "publish.video";
27
+ Capability["PublishThread"] = "publish.thread";
28
+ Capability["PublishScheduled"] = "publish.scheduled";
29
+ Capability["MetricsBasic"] = "metrics.basic";
30
+ Capability["MetricsEngagement"] = "metrics.engagement";
31
+ })(Capability || (exports.Capability = Capability = {}));
32
+ exports.PLUGIN_ID_PATTERN = /^(skill|hook|channel)\.[a-z0-9-]{2,32}$/;
33
+ const SEMVER_PATTERN = /^\d+\.\d+\.\d+$/;
34
+ const SEMVER_RANGE_PATTERN = /^([\^~]|>=|<=|[><=])?\d+(\.\d+){0,2}(\s+([\^~]|>=|<=|[><=])?\d+(\.\d+){0,2})*$/;
35
+ const SHA256_PATTERN = /^[a-f0-9]{64}$/;
36
+ const DOMAIN_PATTERN = /^(?!\d+\.\d+\.\d+\.\d+$)(?!-)[a-z0-9-]{1,63}(?<!-)(\.(?!-)[a-z0-9-]{1,63}(?<!-))+$/i;
37
+ const MODULE_PATTERN = /^(?:\.\/)?dist\/[\w./-]+\.mjs$/;
38
+ exports.pluginDomainSchema = zod_1.z.string().regex(DOMAIN_PATTERN, 'network 域名必须是合法主机名,禁止通配符与 IP 字面量');
39
+ exports.pluginManifestSchema = zod_1.z.object({
40
+ $schema: zod_1.z.string().min(1).optional(),
41
+ id: zod_1.z.string().regex(exports.PLUGIN_ID_PATTERN, 'id 必须匹配 ^(skill|hook|channel)\\.[a-z0-9-]{2,32}$'),
42
+ version: zod_1.z.string().regex(SEMVER_PATTERN, 'version 必须是 semver'),
43
+ apiVersion: zod_1.z.string().regex(SEMVER_RANGE_PATTERN, 'apiVersion 必须是 semver 区间,如 ^1.0.0'),
44
+ type: zod_1.z.enum(Object.values(PluginType)),
45
+ name: zod_1.z.string().min(1).optional(),
46
+ description: zod_1.z.string().min(1),
47
+ entry: zod_1.z.object({
48
+ module: zod_1.z.string().regex(MODULE_PATTERN, 'entry.module 必须是打包后的单文件 ESM(dist/*.mjs)'),
49
+ sha256: zod_1.z.string().regex(SHA256_PATTERN, 'entry.sha256 必须是 64 位小写十六进制'),
50
+ }).optional(),
51
+ contributes: zod_1.z.object({
52
+ channels: zod_1.z.array(zod_1.z.object({
53
+ id: zod_1.z.string().min(1),
54
+ label: zod_1.z.string().min(1),
55
+ capabilities: zod_1.z.array(zod_1.z.enum(Object.values(Capability))).min(1),
56
+ })).optional(),
57
+ hooks: zod_1.z.array(zod_1.z.enum(Object.values(PluginHookName))).optional(),
58
+ skills: zod_1.z.array(zod_1.z.string().min(1)).optional(),
59
+ }),
60
+ permissions: zod_1.z.object({
61
+ network: zod_1.z.array(exports.pluginDomainSchema).max(5, 'permissions.network 单插件上限 5 个域名'),
62
+ secrets: zod_1.z.array(zod_1.z.string().min(1)).optional(),
63
+ storage: zod_1.z.object({
64
+ kv: zod_1.z.boolean().optional(),
65
+ bytes: zod_1.z.number().int().positive().optional(),
66
+ }).optional(),
67
+ }),
68
+ engines: zod_1.z.object({
69
+ aitoearn: zod_1.z.string().regex(SEMVER_RANGE_PATTERN, 'engines.aitoearn 必须是 semver 区间'),
70
+ }),
71
+ license: zod_1.z.string().min(1),
72
+ publisher: zod_1.z.object({
73
+ id: zod_1.z.string().min(1).optional(),
74
+ name: zod_1.z.string().min(1).optional(),
75
+ keyId: zod_1.z.string().min(1).optional(),
76
+ }).optional(),
77
+ signature: zod_1.z.string().min(1).optional(),
78
+ }).superRefine((manifest, context) => {
79
+ if (manifest.type === PluginType.Skill && manifest.entry) {
80
+ context.addIssue({
81
+ code: 'custom',
82
+ path: ['entry'],
83
+ message: '技能插件禁止 entry 字段(技能不执行代码)',
84
+ });
85
+ }
86
+ if (manifest.type !== PluginType.Skill && !manifest.entry) {
87
+ context.addIssue({
88
+ code: 'custom',
89
+ path: ['entry'],
90
+ message: `${manifest.type} 插件必须声明 entry(单文件 ESM 产物与 sha256)`,
91
+ });
92
+ }
93
+ if (manifest.publisher && !manifest.publisher.id && !manifest.publisher.name) {
94
+ context.addIssue({
95
+ code: 'custom',
96
+ path: ['publisher'],
97
+ message: 'publisher 至少需要 id 或 name',
98
+ });
99
+ }
100
+ });
101
+ /** 插件产物只允许 import 这一个包,其余一律拒绝(§3.3-1 自包含单文件 ESM) */
102
+ exports.ALLOWED_ENTRY_IMPORT = '@airongrong/plugin-api';
103
+ const ENTRY_IMPORT_PATTERN = /(?:import|export)[^'"]*?from\s*['"]([^'"]+)['"]|import\s*\(\s*['"]([^'"]+)['"]/g;
104
+ /**
105
+ * 运行时复用同一套入口源码约束(CLI 的 verify/pack 与宿主加载共用)。
106
+ * 阻止 entry 通过 import 'node:fs' / 'node:child_process' 绕过权限代理。
107
+ */
108
+ function assertPluginEntrySource(source, modulePath = 'entry') {
109
+ const violations = [];
110
+ for (const match of source.matchAll(ENTRY_IMPORT_PATTERN)) {
111
+ const specifier = match[1] ?? match[2];
112
+ if (specifier && specifier !== exports.ALLOWED_ENTRY_IMPORT) {
113
+ violations.push(specifier);
114
+ }
115
+ }
116
+ if (violations.length > 0) {
117
+ throw new plugin_error_code_1.PluginError(plugin_error_code_1.PluginErrorCode.InvalidManifest, `${modulePath} 必须是自包含单文件 ESM,只允许 import '${exports.ALLOWED_ENTRY_IMPORT}',违规: ${violations.join(', ')}`);
118
+ }
119
+ }
120
+ exports.pluginManifestJsonSchema = zod_1.z.toJSONSchema(exports.pluginManifestSchema);
121
+ function verifyManifest(input) {
122
+ const result = exports.pluginManifestSchema.safeParse(input);
123
+ if (!result.success) {
124
+ const issue = result.error.issues[0];
125
+ throw new plugin_error_code_1.PluginError(plugin_error_code_1.PluginErrorCode.InvalidManifest, `plugin.json 非法: ${issue.path.join('.')} ${issue.message}`, { issues: result.error.issues });
126
+ }
127
+ return result.data;
128
+ }
@@ -0,0 +1,32 @@
1
+ import type { ChannelAdapter } from './channel-adapter';
2
+ import type { HostServices } from './host-services';
3
+ import type { PostDraft, PublishResult } from './post-draft';
4
+ export interface HookHandlers {
5
+ /** 编辑期每次保存;返回 issue 阻断保存(可选,默认警告) */
6
+ validateDraft?: (draft: PostDraft) => ValidationIssueResult[] | Promise<ValidationIssueResult[]>;
7
+ /** fail-closed:抛错即中止发布,并透传原因 */
8
+ beforePublish?: (draft: PostDraft) => Promise<void>;
9
+ /** fail-open:只记日志 */
10
+ afterPublish?: (result: PublishResult) => Promise<void>;
11
+ onAuthExpired?: (ctx: {
12
+ accountId: string;
13
+ channelId: string;
14
+ }) => Promise<void>;
15
+ }
16
+ export interface ValidationIssueResult {
17
+ severity: 'error' | 'warning';
18
+ message: string;
19
+ path?: string;
20
+ }
21
+ export interface AdapterPluginModule {
22
+ adapter: ChannelAdapter;
23
+ }
24
+ export interface HookPluginModule {
25
+ hooks: HookHandlers;
26
+ }
27
+ export type PluginModule = AdapterPluginModule | HookPluginModule;
28
+ /**
29
+ * 插件入口契约:默认导出或 activate 函数,宿主在 worker 内调用。
30
+ * 模块内只允许 import '@airongrong/plugin-api'(类型与工具,不打入产物)。
31
+ */
32
+ export type PluginActivate = (host: HostServices) => PluginModule | Promise<PluginModule>;
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,74 @@
1
+ export declare enum DraftBlockKind {
2
+ Image = "image",
3
+ Video = "video",
4
+ Link = "link"
5
+ }
6
+ export type DraftBlock = {
7
+ kind: DraftBlockKind.Image;
8
+ url: string;
9
+ width: number;
10
+ height: number;
11
+ } | {
12
+ kind: DraftBlockKind.Video;
13
+ url: string;
14
+ durationSec: number;
15
+ coverUrl?: string;
16
+ } | {
17
+ kind: DraftBlockKind.Link;
18
+ url: string;
19
+ };
20
+ export declare enum DraftVisibility {
21
+ Public = "public",
22
+ Private = "private",
23
+ Unlisted = "unlisted"
24
+ }
25
+ export interface PostDraft {
26
+ /** 幂等键:同一 draftId 重复提交必须返回同一结果 */
27
+ draftId: string;
28
+ text: string;
29
+ blocks: DraftBlock[];
30
+ topics: string[];
31
+ mentions: string[];
32
+ visibility: DraftVisibility;
33
+ /** ISO8601,由宿主统一调度 */
34
+ scheduledAt?: string;
35
+ locale: string;
36
+ }
37
+ export declare enum ValidationIssueSeverity {
38
+ Error = "error",
39
+ Warning = "warning"
40
+ }
41
+ export interface ValidationIssue {
42
+ severity: ValidationIssueSeverity;
43
+ message: string;
44
+ /** 关联的草稿字段,如 blocks.0.url */
45
+ path?: string;
46
+ }
47
+ export interface PublishResult {
48
+ externalId: string;
49
+ url: string;
50
+ publishedAt: string;
51
+ warnings?: string[];
52
+ }
53
+ export interface PublishContext {
54
+ accountId: string;
55
+ locale: string;
56
+ }
57
+ export interface Metrics {
58
+ views?: number;
59
+ likes?: number;
60
+ comments?: number;
61
+ shares?: number;
62
+ collectedAt: string;
63
+ }
64
+ export interface AuthContext {
65
+ accountId: string;
66
+ }
67
+ export interface AuthResult {
68
+ externalAccountId: string;
69
+ displayName?: string;
70
+ expiresAt?: string;
71
+ }
72
+ export interface CapabilitySet {
73
+ capabilities: string[];
74
+ }
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ValidationIssueSeverity = exports.DraftVisibility = exports.DraftBlockKind = void 0;
4
+ var DraftBlockKind;
5
+ (function (DraftBlockKind) {
6
+ DraftBlockKind["Image"] = "image";
7
+ DraftBlockKind["Video"] = "video";
8
+ DraftBlockKind["Link"] = "link";
9
+ })(DraftBlockKind || (exports.DraftBlockKind = DraftBlockKind = {}));
10
+ var DraftVisibility;
11
+ (function (DraftVisibility) {
12
+ DraftVisibility["Public"] = "public";
13
+ DraftVisibility["Private"] = "private";
14
+ DraftVisibility["Unlisted"] = "unlisted";
15
+ })(DraftVisibility || (exports.DraftVisibility = DraftVisibility = {}));
16
+ var ValidationIssueSeverity;
17
+ (function (ValidationIssueSeverity) {
18
+ ValidationIssueSeverity["Error"] = "error";
19
+ ValidationIssueSeverity["Warning"] = "warning";
20
+ })(ValidationIssueSeverity || (exports.ValidationIssueSeverity = ValidationIssueSeverity = {}));
@@ -0,0 +1,12 @@
1
+ export declare const HOST_API_VERSION = "1.0.0";
2
+ /**
3
+ * Minimal semver range matcher supporting the doc grammar:
4
+ * `>=1.8.0 <3`, `^1.2.0`, `~1.2.0`, `1.2.3` and combinations.
5
+ */
6
+ export declare function satisfiesVersion(version: string, range: string): boolean;
7
+ /**
8
+ * Host/plugin apiVersion compatibility: the host version must satisfy the
9
+ * declared range and its major must lie within the majors the range allows
10
+ * (compound ranges like `>=1.8.0 <3` are supported, see plan §9).
11
+ */
12
+ export declare function isApiCompatible(pluginApiVersion: string, hostApiVersion?: string): boolean;
@@ -0,0 +1,124 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HOST_API_VERSION = void 0;
4
+ exports.satisfiesVersion = satisfiesVersion;
5
+ exports.isApiCompatible = isApiCompatible;
6
+ exports.HOST_API_VERSION = '1.0.0';
7
+ const VERSION_PATTERN = /^\d+\.\d+\.\d+$/;
8
+ function parseVersion(version) {
9
+ if (!VERSION_PATTERN.test(version)) {
10
+ return undefined;
11
+ }
12
+ return version.split('.').map(part => Number(part));
13
+ }
14
+ function compareVersions(a, b) {
15
+ for (let index = 0; index < 3; index += 1) {
16
+ if (a[index] !== b[index]) {
17
+ return a[index] - b[index];
18
+ }
19
+ }
20
+ return 0;
21
+ }
22
+ function comparePartial(version, expected, precision) {
23
+ for (let index = 0; index < precision; index += 1) {
24
+ if (version[index] !== expected[index]) {
25
+ return version[index] - expected[index];
26
+ }
27
+ }
28
+ return 0;
29
+ }
30
+ /**
31
+ * Minimal semver range matcher supporting the doc grammar:
32
+ * `>=1.8.0 <3`, `^1.2.0`, `~1.2.0`, `1.2.3` and combinations.
33
+ */
34
+ function satisfiesVersion(version, range) {
35
+ const parsedVersion = parseVersion(version);
36
+ if (!parsedVersion) {
37
+ return false;
38
+ }
39
+ return range.trim().split(/\s+/).every((clause) => {
40
+ const match = /^(>=|<=|[><=^~])?(\d+(?:\.\d+){0,2})$/.exec(clause);
41
+ if (!match) {
42
+ return false;
43
+ }
44
+ const operator = match[1] ?? '=';
45
+ const parts = match[2].split('.').map(part => Number(part));
46
+ const expected = [parts[0] ?? 0, parts[1] ?? 0, parts[2] ?? 0];
47
+ const precision = parts.length;
48
+ switch (operator) {
49
+ case '>=':
50
+ return compareVersions(parsedVersion, expected) >= 0;
51
+ case '<=':
52
+ return compareVersions(parsedVersion, expected) <= 0;
53
+ case '>':
54
+ return compareVersions(parsedVersion, expected) > 0;
55
+ case '<':
56
+ return compareVersions(parsedVersion, expected) < 0;
57
+ case '~':
58
+ return comparePartial(parsedVersion, expected, Math.min(precision, 2)) === 0;
59
+ case '^':
60
+ return comparePartial(parsedVersion, expected, 1) === 0 && compareVersions(parsedVersion, expected) >= 0;
61
+ default:
62
+ if (precision === 3) {
63
+ return compareVersions(parsedVersion, expected) === 0;
64
+ }
65
+ return comparePartial(parsedVersion, expected, precision) === 0;
66
+ }
67
+ });
68
+ }
69
+ const MAJOR_CEILING = Number.POSITIVE_INFINITY;
70
+ /**
71
+ * 计算区间允许的主版本闭区间 [lo, hi](hi 为 Infinity 表示无上界)。
72
+ * 区间非法或主版本范围为空时返回 undefined。边界刻意宽松(如 `>2` 记 lo=2),
73
+ * 精确命中仍由 satisfiesVersion 把关,这里只做跨主版本的粗粒度闸门。
74
+ */
75
+ function rangeMajors(range) {
76
+ let lo = 0;
77
+ let hi = MAJOR_CEILING;
78
+ for (const clause of range.trim().split(/\s+/)) {
79
+ const match = /^(>=|<=|[><=^~])?(\d+(?:\.\d+){0,2})$/.exec(clause);
80
+ if (!match) {
81
+ return undefined;
82
+ }
83
+ const operator = match[1] ?? '=';
84
+ const parts = match[2].split('.').map(part => Number(part));
85
+ const major = parts[0] ?? 0;
86
+ const hasUpperTail = parts.slice(1).some(part => part > 0);
87
+ switch (operator) {
88
+ case '>=':
89
+ case '>':
90
+ // >X.Y.Z 仍允许主版本 X(如 >1.8.0 含 1.9.0)
91
+ lo = Math.max(lo, major);
92
+ break;
93
+ case '<=':
94
+ hi = Math.min(hi, major);
95
+ break;
96
+ case '<':
97
+ // <3.0.0 排除主版本 3,<3.0.1 仍允许 3.0.0
98
+ hi = Math.min(hi, hasUpperTail ? major : major - 1);
99
+ break;
100
+ case '^':
101
+ case '~':
102
+ default:
103
+ lo = Math.max(lo, major);
104
+ hi = Math.min(hi, major);
105
+ }
106
+ }
107
+ return lo > hi ? undefined : [lo, hi];
108
+ }
109
+ /**
110
+ * Host/plugin apiVersion compatibility: the host version must satisfy the
111
+ * declared range and its major must lie within the majors the range allows
112
+ * (compound ranges like `>=1.8.0 <3` are supported, see plan §9).
113
+ */
114
+ function isApiCompatible(pluginApiVersion, hostApiVersion = exports.HOST_API_VERSION) {
115
+ const host = parseVersion(hostApiVersion);
116
+ if (!host) {
117
+ return false;
118
+ }
119
+ const majors = rangeMajors(pluginApiVersion);
120
+ if (!majors || host[0] < majors[0] || host[0] > majors[1]) {
121
+ return false;
122
+ }
123
+ return satisfiesVersion(hostApiVersion, pluginApiVersion);
124
+ }
package/package.json CHANGED
@@ -1,6 +1,53 @@
1
1
  {
2
2
  "name": "@airongrong/plugin-api",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.0.0-beta.0",
4
+ "description": "AiToEarn plugin SDK: plugin.json manifest schema, adapter contract, host services and error codes",
5
+ "license": "MIT",
6
+ "type": "commonjs",
7
+ "sideEffects": false,
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "default": "./dist/index.js"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "README.md",
19
+ "LICENSE"
20
+ ],
21
+ "engines": {
22
+ "node": ">=20"
23
+ },
24
+ "publishConfig": {
25
+ "access": "public"
26
+ },
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "git+https://github.com/yikart/aitoearn-sdk.git",
30
+ "directory": "packages/plugin-api"
31
+ },
32
+ "homepage": "https://github.com/yikart/aitoearn-sdk/tree/main/packages/plugin-api#readme",
33
+ "bugs": {
34
+ "url": "https://github.com/yikart/aitoearn-sdk/issues"
35
+ },
36
+ "keywords": [
37
+ "aitoearn",
38
+ "plugin",
39
+ "manifest",
40
+ "channel-adapter",
41
+ "sdk"
42
+ ],
43
+ "scripts": {
44
+ "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
45
+ "clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\"",
46
+ "test": "vitest run",
47
+ "typecheck": "tsc -p tsconfig.json --noEmit",
48
+ "prepublishOnly": "node ../../scripts/prepublish-guard.mjs"
49
+ },
50
+ "dependencies": {
51
+ "zod": "^4.3.6"
52
+ }
53
+ }