weaver-work-cli 0.1.4 → 0.1.5

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.
Files changed (87) hide show
  1. package/dist/internal/e10/request-runtime.js +3 -2
  2. package/dist/shortcuts/index.js +4 -0
  3. package/dist/shortcuts/meeting/continuation.js +70 -0
  4. package/dist/shortcuts/meeting/errors.js +63 -0
  5. package/dist/shortcuts/meeting/host.js +167 -0
  6. package/dist/shortcuts/meeting/index.js +123 -0
  7. package/dist/shortcuts/meeting/manifest.js +64 -0
  8. package/dist/shortcuts/meeting/operations/catalogs.js +58 -0
  9. package/dist/shortcuts/meeting/operations/change.js +242 -0
  10. package/dist/shortcuts/meeting/operations/conflicts.js +142 -0
  11. package/dist/shortcuts/meeting/operations/create.js +349 -0
  12. package/dist/shortcuts/meeting/operations/env.js +191 -0
  13. package/dist/shortcuts/meeting/operations/lifecycle.js +139 -0
  14. package/dist/shortcuts/meeting/operations/meetings.js +73 -0
  15. package/dist/shortcuts/meeting/operations/receipts.js +195 -0
  16. package/dist/shortcuts/meeting/operations/registry.js +47 -0
  17. package/dist/shortcuts/meeting/operations/rooms.js +132 -0
  18. package/dist/shortcuts/meeting/operations/shared.js +112 -0
  19. package/dist/shortcuts/meeting/operations/signs.js +41 -0
  20. package/dist/shortcuts/meeting/operations/types.js +25 -0
  21. package/dist/shortcuts/meeting/operations.js +19 -0
  22. package/dist/shortcuts/yimiaoban/continuation.js +70 -0
  23. package/dist/shortcuts/yimiaoban/errors.js +61 -0
  24. package/dist/shortcuts/yimiaoban/host.js +469 -0
  25. package/dist/shortcuts/yimiaoban/index.js +271 -0
  26. package/dist/shortcuts/yimiaoban/manifest.js +28 -0
  27. package/dist/shortcuts/yimiaoban/operations/file-user.js +288 -0
  28. package/dist/shortcuts/yimiaoban/operations/group-read.js +477 -0
  29. package/dist/shortcuts/yimiaoban/operations/group-write.js +591 -0
  30. package/dist/shortcuts/yimiaoban/operations/msg-parse.js +429 -0
  31. package/dist/shortcuts/yimiaoban/operations/msg-sync.js +519 -0
  32. package/dist/shortcuts/yimiaoban/operations/msg-write.js +593 -0
  33. package/dist/shortcuts/yimiaoban/operations/person.js +102 -0
  34. package/dist/shortcuts/yimiaoban/operations/registry.js +38 -0
  35. package/dist/shortcuts/yimiaoban/operations/session.js +437 -0
  36. package/dist/shortcuts/yimiaoban/operations/shared.js +236 -0
  37. package/dist/shortcuts/yimiaoban/operations/types.js +27 -0
  38. package/dist/shortcuts/yimiaoban/operations/write-util.js +78 -0
  39. package/dist/shortcuts/yimiaoban/operations.js +8 -0
  40. package/docs/_catalog.md +6 -0
  41. package/docs/meeting.md +48 -0
  42. package/docs/yimiaoban.md +86 -0
  43. package/package.json +1 -1
  44. package/skill-template/business-info.json +4 -1
  45. package/skill-template/domains/meeting.md +3 -0
  46. package/skill-template/domains/yimiaoban.md +21 -0
  47. package/skills/weaver-e10-calendar/SKILL.md +1 -0
  48. package/skills/weaver-e10-esb/SKILL.md +1 -0
  49. package/skills/weaver-e10-esb/references/source-manifest.json +3 -3
  50. package/skills/weaver-e10-hrm/SKILL.md +1 -0
  51. package/skills/weaver-e10-jiuchuanhui/SKILL.md +1 -0
  52. package/skills/weaver-e10-jucailin/SKILL.md +1 -0
  53. package/skills/weaver-e10-mail/SKILL.md +1 -0
  54. package/skills/weaver-e10-meeting/SKILL.md +106 -0
  55. package/skills/weaver-e10-meeting/product.json +8 -0
  56. package/skills/weaver-e10-meeting/references/meeting-change.md +67 -0
  57. package/skills/weaver-e10-meeting/references/meeting-create.md +52 -0
  58. package/skills/weaver-e10-meeting/references/meeting-lifecycle.md +57 -0
  59. package/skills/weaver-e10-meeting/references/meeting-queries.md +61 -0
  60. package/skills/weaver-e10-meeting/references/meeting-receipts-signs.md +48 -0
  61. package/skills/weaver-e10-meeting/references/meeting-update.md +7 -0
  62. package/skills/weaver-e10-meeting/references/source-manifest.json +50 -0
  63. package/skills/weaver-e10-plan/SKILL.md +1 -0
  64. package/skills/weaver-e10-plan/references/source-manifest.json +3 -3
  65. package/skills/weaver-e10-qiyecheng/SKILL.md +1 -0
  66. package/skills/weaver-e10-qiyecheng/references/source-manifest.json +3 -3
  67. package/skills/weaver-e10-shared/SKILL.md +4 -0
  68. package/skills/weaver-e10-skill-maker/SKILL.md +1 -0
  69. package/skills/weaver-e10-skill-maker/references/weaver-skill-style.md +1 -1
  70. package/skills/weaver-e10-wenshuding/SKILL.md +1 -0
  71. package/skills/weaver-e10-wenshuding/references/source-manifest.json +3 -3
  72. package/skills/weaver-e10-yepiaotong/references/source-manifest.json +5 -5
  73. package/skills/weaver-e10-yimiaoban/SKILL.md +104 -0
  74. package/skills/weaver-e10-yimiaoban/product.json +8 -0
  75. package/skills/weaver-e10-yimiaoban/references/ding-write.md +68 -0
  76. package/skills/weaver-e10-yimiaoban/references/error-codes.md +85 -0
  77. package/skills/weaver-e10-yimiaoban/references/field-resolution.md +46 -0
  78. package/skills/weaver-e10-yimiaoban/references/file-user-i18n.md +54 -0
  79. package/skills/weaver-e10-yimiaoban/references/group-read.md +55 -0
  80. package/skills/weaver-e10-yimiaoban/references/group-write.md +72 -0
  81. package/skills/weaver-e10-yimiaoban/references/msg-read.md +59 -0
  82. package/skills/weaver-e10-yimiaoban/references/msg-write.md +64 -0
  83. package/skills/weaver-e10-yimiaoban/references/person.md +44 -0
  84. package/skills/weaver-e10-yimiaoban/references/safety-boundaries.md +61 -0
  85. package/skills/weaver-e10-yimiaoban/references/session-sysmsg.md +47 -0
  86. package/skills/weaver-e10-yimiaoban/references/source-manifest.json +485 -0
  87. package/skills/weaver-e10-ziguanjia/SKILL.md +1 -0
@@ -1,5 +1,6 @@
1
1
  import { CliError, ExitCode } from '../../core/errors.js';
2
2
  const DEFAULT_TIMEOUT_MS = 30_000;
3
+ const DEFAULT_BIG_INT_STRINGS = true;
3
4
  const RESERVED_HEADERS = [
4
5
  'authorization',
5
6
  'cookie',
@@ -81,7 +82,7 @@ export class E10RequestRuntime {
81
82
  formData: request.formData,
82
83
  timeoutMs: request.timeoutMs || DEFAULT_TIMEOUT_MS,
83
84
  responseType: request.responseType || 'json',
84
- bigIntStrings: request.bigIntStrings === true,
85
+ bigIntStrings: request.bigIntStrings ?? DEFAULT_BIG_INT_STRINGS,
85
86
  };
86
87
  }
87
88
  sessionContext() {
@@ -184,7 +185,7 @@ export class E10RequestRuntime {
184
185
  clearTimeout(timer);
185
186
  }
186
187
  }
187
- async readResponse(response, responseType, bigIntStrings = false) {
188
+ async readResponse(response, responseType, bigIntStrings = DEFAULT_BIG_INT_STRINGS) {
188
189
  if (responseType === 'binary') {
189
190
  return {
190
191
  status: response.status,
@@ -6,9 +6,11 @@ import { fnaShortcut } from './fna/index.js';
6
6
  import { hrmShortcut } from './hrm/index.js';
7
7
  import { invoiceShortcut } from './invoice/index.js';
8
8
  import { mailShortcut } from './mail/index.js';
9
+ import { meetingShortcut } from './meeting/index.js';
9
10
  import { planShortcut } from './plan/index.js';
10
11
  import { jiuchuanhuiShortcut } from './jiuchuanhui/index.js';
11
12
  import { assetShortcut } from './asset/index.js';
13
+ import { yimiaobanShortcut } from './yimiaoban/index.js';
12
14
  export const builtinShortcuts = [
13
15
  invoiceShortcut,
14
16
  ehrShortcut,
@@ -17,8 +19,10 @@ export const builtinShortcuts = [
17
19
  fnaShortcut,
18
20
  hrmShortcut,
19
21
  mailShortcut,
22
+ meetingShortcut,
20
23
  planShortcut,
21
24
  esbShortcut,
22
25
  jiuchuanhuiShortcut,
23
26
  assetShortcut,
27
+ yimiaobanShortcut
24
28
  ];
@@ -0,0 +1,70 @@
1
+ import { createHmac, randomBytes, timingSafeEqual } from 'node:crypto';
2
+ import { existsSync } from 'node:fs';
3
+ import { chmod, mkdir, readFile, writeFile } from 'node:fs/promises';
4
+ import { homedir } from 'node:os';
5
+ import { resolve } from 'node:path';
6
+ import { meetingValidationError } from './errors.js';
7
+ function stateRoot(ctx) {
8
+ const configured = ctx?.config?.path();
9
+ if (configured)
10
+ return resolve(configured, '..');
11
+ return process.env.WEAVER_WORK_CLI_HOME || process.env.WORK_CLI_HOME || resolve(homedir(), '.weaver-work-cli');
12
+ }
13
+ async function loadKey(ctx) {
14
+ const keysDir = resolve(stateRoot(ctx), 'keys');
15
+ const filename = resolve(keysDir, 'meeting-continuation.key');
16
+ await mkdir(keysDir, { recursive: true, mode: 0o700 });
17
+ if (existsSync(filename))
18
+ return readFile(filename);
19
+ const key = randomBytes(32);
20
+ try {
21
+ await writeFile(filename, key, { flag: 'wx', mode: 0o600 });
22
+ if (process.platform !== 'win32')
23
+ await chmod(filename, 0o600).catch(() => undefined);
24
+ return key;
25
+ }
26
+ catch {
27
+ return readFile(filename);
28
+ }
29
+ }
30
+ function encode(value) {
31
+ return Buffer.from(JSON.stringify(value)).toString('base64url');
32
+ }
33
+ function signature(payload, key) {
34
+ return createHmac('sha256', key).update(payload).digest('base64url');
35
+ }
36
+ export function createMeetingContinuationSigner(ctx) {
37
+ return {
38
+ async issue(payload, ttlMs = 10 * 60_000) {
39
+ const issuedAt = Date.now();
40
+ const body = encode({ ...payload, issuedAt, expiresAt: issuedAt + ttlMs });
41
+ const sig = signature(body, await loadKey(ctx));
42
+ return `${body}.${sig}`;
43
+ },
44
+ async verify(token, expectedOperation) {
45
+ if (typeof token !== 'string' || !token.includes('.')) {
46
+ throw meetingValidationError('continuation_invalid', 'continuation 无效');
47
+ }
48
+ const [body, provided] = token.split('.');
49
+ const expected = signature(body, await loadKey(ctx));
50
+ const valid = provided.length === expected.length
51
+ && timingSafeEqual(Buffer.from(provided), Buffer.from(expected));
52
+ if (!valid)
53
+ throw meetingValidationError('continuation_invalid', 'continuation 签名校验失败');
54
+ let value;
55
+ try {
56
+ value = JSON.parse(Buffer.from(body, 'base64url').toString('utf8'));
57
+ }
58
+ catch {
59
+ throw meetingValidationError('continuation_invalid', 'continuation 内容无法解析');
60
+ }
61
+ if (value.operation !== expectedOperation) {
62
+ throw meetingValidationError('continuation_mismatch', 'continuation 不属于当前操作');
63
+ }
64
+ if (!Number.isSafeInteger(value.expiresAt) || value.expiresAt < Date.now()) {
65
+ throw meetingValidationError('continuation_expired', 'continuation 已过期');
66
+ }
67
+ return value;
68
+ },
69
+ };
70
+ }
@@ -0,0 +1,63 @@
1
+ import { CliError, ExitCode } from '../../core/errors.js';
2
+ export class MeetingCliError extends CliError {
3
+ type;
4
+ subtype;
5
+ code;
6
+ retryable;
7
+ stage;
8
+ details;
9
+ partialData;
10
+ constructor(type, subtype, message, options = {}) {
11
+ super(message, options.exitCode ?? ExitCode.InternalError);
12
+ this.name = 'MeetingCliError';
13
+ this.type = type;
14
+ this.subtype = subtype;
15
+ this.code = options.code;
16
+ this.retryable = options.retryable ?? false;
17
+ this.stage = options.stage;
18
+ this.details = options.details;
19
+ this.partialData = options.partialData;
20
+ }
21
+ }
22
+ export function meetingValidationError(subtype, message, details) {
23
+ return new MeetingCliError('validation', subtype, message, {
24
+ details,
25
+ exitCode: ExitCode.UsageError,
26
+ });
27
+ }
28
+ export function meetingAuthError(subtype, message, details) {
29
+ return new MeetingCliError('authentication', subtype, message, {
30
+ details,
31
+ exitCode: ExitCode.AuthError,
32
+ });
33
+ }
34
+ export function meetingNetworkError(subtype, message, options = {}) {
35
+ return new MeetingCliError('network', subtype, message, {
36
+ ...options,
37
+ retryable: options.retryable ?? true,
38
+ exitCode: ExitCode.NetworkError,
39
+ });
40
+ }
41
+ export function meetingConfirmationError(action, details) {
42
+ return new MeetingCliError('confirmation', 'required', '该操作需要明确确认', {
43
+ details: { action, ...details },
44
+ exitCode: ExitCode.ConfirmationRequired,
45
+ });
46
+ }
47
+ export function meetingPartialError(message, partialData, details) {
48
+ return new MeetingCliError('partial', 'write_uncertain', message, {
49
+ details,
50
+ partialData,
51
+ exitCode: ExitCode.Partial,
52
+ });
53
+ }
54
+ /**
55
+ * 冲突命中(cansub=false 禁止提交)或冲突检测接口失败:一律拒绝建会。
56
+ * 与业务规范一致:禁止提交 → 直接拒绝;仅提醒 → 由调用方看 warnings。
57
+ */
58
+ export function meetingConflictError(subtype, message, details) {
59
+ return new MeetingCliError('policy', subtype, message, {
60
+ details,
61
+ exitCode: ExitCode.ConfirmationRequired,
62
+ });
63
+ }
@@ -0,0 +1,167 @@
1
+ import { requireE10Session } from '../../internal/e10/context.js';
2
+ import { getActiveProfile } from '../../internal/e10/auth/session.js';
3
+ import { E10ApiError, createE10RequestRuntime } from '../../internal/e10/request-runtime.js';
4
+ import { MeetingCliError, meetingAuthError, meetingNetworkError, meetingValidationError } from './errors.js';
5
+ function inferProfile(ctx) {
6
+ const options = ctx.getGlobalOptions();
7
+ return options.profile || getActiveProfile();
8
+ }
9
+ /**
10
+ * E10 标准版(Java REST)与 EB 版(eBuilder formdata)响应契约:
11
+ * `code=200` 成功;或 `status=true` 成功(EB formdata 与部分标准接口)。
12
+ */
13
+ export function isBusinessSuccess(value) {
14
+ if (!value || typeof value !== 'object')
15
+ return false;
16
+ if (typeof value.status === 'boolean')
17
+ return value.status;
18
+ if (value.code !== undefined && value.code !== null)
19
+ return Number(value.code) === 200;
20
+ return false;
21
+ }
22
+ export function businessMessage(value) {
23
+ if (!value || typeof value !== 'object')
24
+ return '泛微接口返回业务失败';
25
+ // actionMsg.message 是服务端权限/业务提示的常见载体(如"您没有变更权限!"),
26
+ // 必须优先保留原文,供 Agent 原样告知用户。
27
+ const candidates = [
28
+ value.msg,
29
+ value.message,
30
+ value.errMsg,
31
+ value.actionMsg?.message,
32
+ value.actionMsg?.msg,
33
+ value.data?.message,
34
+ value.data?.msg,
35
+ ];
36
+ for (const item of candidates) {
37
+ if (typeof item === 'string' && item.trim())
38
+ return item;
39
+ }
40
+ return '泛微接口返回业务失败';
41
+ }
42
+ function businessCode(value) {
43
+ if (!value || typeof value !== 'object')
44
+ return undefined;
45
+ const code = value.code ?? value.status;
46
+ if (typeof code === 'string' || typeof code === 'number')
47
+ return code;
48
+ return undefined;
49
+ }
50
+ function assertPath(requestPath) {
51
+ if (typeof requestPath !== 'string' || !requestPath.startsWith('/') || requestPath.startsWith('//')) {
52
+ throw meetingValidationError('path_invalid', '请求路径必须是相对绝对路径');
53
+ }
54
+ if (/^https?:/i.test(requestPath) || /[\r\n]/.test(requestPath) || requestPath.includes('/../')) {
55
+ throw meetingValidationError('path_invalid', '请求路径不允许跨域或路径逃逸');
56
+ }
57
+ if (!requestPath.startsWith('/api/')) {
58
+ throw meetingValidationError('path_not_allowed', '会议模块只能调用 /api/ 路径');
59
+ }
60
+ }
61
+ function omitEmptyQueryValues(query) {
62
+ if (!query)
63
+ return undefined;
64
+ return Object.fromEntries(Object.entries(query).filter(([, value]) => value !== '' && value !== undefined));
65
+ }
66
+ function mapRuntimeNetworkError(error) {
67
+ if (error instanceof E10ApiError && error.type === 'network') {
68
+ throw meetingNetworkError(error.subtype, error.message, {
69
+ retryable: error.retryable,
70
+ details: error.details,
71
+ });
72
+ }
73
+ throw error;
74
+ }
75
+ export function createMeetingHost(ctx, dependencies = {}) {
76
+ let e10Session = null;
77
+ let sessionContext = null;
78
+ const requireSession = dependencies.requireSession || requireE10Session;
79
+ async function ensureSession() {
80
+ if (!e10Session) {
81
+ e10Session = await requireSession(ctx);
82
+ sessionContext = {
83
+ baseUrl: e10Session.getBaseUrl(),
84
+ profile: inferProfile(ctx),
85
+ userId: e10Session.userId,
86
+ tenantKey: e10Session.tenantKey,
87
+ eteamsId: e10Session.eteamsId,
88
+ };
89
+ }
90
+ return e10Session;
91
+ }
92
+ async function getSession() {
93
+ await ensureSession();
94
+ return sessionContext;
95
+ }
96
+ async function runtime() {
97
+ return createE10RequestRuntime(ctx, await ensureSession(), {
98
+ interceptors: dependencies.interceptors,
99
+ fetch: dependencies.fetch,
100
+ });
101
+ }
102
+ async function request(request) {
103
+ const { method, path, query, body, operation, headers: extraHeaders = {}, timeoutMs = 30_000 } = request;
104
+ assertPath(path);
105
+ // 标准版会议接口硬性约定(standalone skill 实测):不能带 workflow 专用头
106
+ // (Referer: /workflow/newflow 等),否则 400 系统错误;等价于 standalone
107
+ // 执行器的 --simple-header(仅 Content-Type + Cookie,由 runtime 托管)。
108
+ const resolvedMethod = method || (body === undefined ? 'GET' : 'POST');
109
+ let response;
110
+ try {
111
+ response = await (await runtime()).requestJson({
112
+ module: 'meeting',
113
+ operation,
114
+ method: resolvedMethod,
115
+ path,
116
+ query: omitEmptyQueryValues(query),
117
+ body,
118
+ headers: { ...extraHeaders },
119
+ // 会议接口存在裸数字长 ID(如 meetingExtend.mtId / creater),超 2^53 会被
120
+ // JSON.parse 舍入;开启大整数保护后按字符串保留,保证 ID 回查不失真。
121
+ bigIntStrings: true,
122
+ timeoutMs,
123
+ });
124
+ }
125
+ catch (error) {
126
+ mapRuntimeNetworkError(error);
127
+ }
128
+ if (response.status === 401 || response.status === 302) {
129
+ throw meetingAuthError('session_expired', 'E10 登录态已失效,请先向用户确认 E10 登录域名,再运行 weaver-work-cli auth login --base-url <域名> 完成登录', { httpStatus: response.status });
130
+ }
131
+ if (response.parseError) {
132
+ if (response.status < 200 || response.status >= 300) {
133
+ throw new MeetingCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
134
+ code: response.status,
135
+ details: { status: response.status },
136
+ exitCode: 1,
137
+ });
138
+ }
139
+ throw meetingNetworkError('protocol', `接口返回不是合法 JSON(HTTP ${response.status})`, { retryable: false });
140
+ }
141
+ if (response.status < 200 || response.status >= 300) {
142
+ throw new MeetingCliError('api', 'http_error', `泛微接口 HTTP ${response.status}`, {
143
+ code: response.status,
144
+ details: { status: response.status, message: businessMessage(response.value) },
145
+ exitCode: 1,
146
+ });
147
+ }
148
+ if (!isBusinessSuccess(response.value)) {
149
+ const code = businessCode(response.value);
150
+ const message = businessMessage(response.value);
151
+ if (code === 403) {
152
+ throw new MeetingCliError('api', 'permission_denied', message || '当前租户未开放该会议接口权限,请联系管理员', {
153
+ code,
154
+ details: { code, message },
155
+ exitCode: 1,
156
+ });
157
+ }
158
+ throw new MeetingCliError('api', 'business_error', message, {
159
+ code,
160
+ details: { code, message },
161
+ exitCode: 1,
162
+ });
163
+ }
164
+ return response.value;
165
+ }
166
+ return { request, getSession };
167
+ }
@@ -0,0 +1,123 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import { ExitCode, isCliError, toErrorEnvelope } from '../../core/errors.js';
3
+ import { createMeetingContinuationSigner } from './continuation.js';
4
+ import { createMeetingHost } from './host.js';
5
+ import { meetingManifest } from './manifest.js';
6
+ import { executeMeetingOperation } from './operations.js';
7
+ import { meetingValidationError } from './errors.js';
8
+ function asOptions(value) {
9
+ return value && typeof value === 'object' && !Array.isArray(value) ? value : {};
10
+ }
11
+ async function readStdin() {
12
+ const chunks = [];
13
+ for await (const chunk of process.stdin) {
14
+ chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
15
+ }
16
+ return Buffer.concat(chunks).toString('utf8');
17
+ }
18
+ /**
19
+ * Windows PowerShell 的管道与 Set-Content -Encoding utf8 会在内容前写入 UTF-8 BOM,
20
+ * 且可能重复写入多次;JSON.parse 不接受前导 BOM,必须先剥离。
21
+ */
22
+ function stripBom(value) {
23
+ return value.replace(/^\uFEFF+/, '');
24
+ }
25
+ export async function inputFrom(options, extra = {}) {
26
+ const opts = asOptions(options);
27
+ let input = {};
28
+ if (opts.input && opts.inputJson) {
29
+ throw meetingValidationError('input_conflict', '不能同时传 --input 和 --input-json');
30
+ }
31
+ if (opts.inputJson) {
32
+ input = parseJsonInput(String(opts.inputJson), '--input-json');
33
+ }
34
+ if (opts.input) {
35
+ let raw;
36
+ try {
37
+ raw = stripBom(opts.input === '-' ? await readStdin() : await readFile(String(opts.input), 'utf8'));
38
+ }
39
+ catch {
40
+ throw meetingValidationError('input_unreadable', `无法读取 --input:${String(opts.input)}`);
41
+ }
42
+ input = parseJsonInput(raw, '--input');
43
+ }
44
+ return {
45
+ ...input,
46
+ ...Object.fromEntries(Object.entries(extra).filter(([, value]) => value !== undefined && value !== '')),
47
+ };
48
+ }
49
+ function parseJsonInput(raw, label) {
50
+ try {
51
+ const parsed = JSON.parse(stripBom(raw));
52
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
53
+ throw new Error('input must be an object');
54
+ }
55
+ return parsed;
56
+ }
57
+ catch {
58
+ throw meetingValidationError('input_invalid', `${label} 不是合法 JSON 对象`);
59
+ }
60
+ }
61
+ export function successEnvelope(operation, result) {
62
+ return {
63
+ schemaVersion: 1,
64
+ ok: true,
65
+ operation,
66
+ data: result.data === undefined ? null : result.data,
67
+ ...(result.meta ? { meta: result.meta } : {}),
68
+ ...(result.warnings && result.warnings.length > 0 ? { warnings: result.warnings } : { warnings: [] }),
69
+ };
70
+ }
71
+ function failureEnvelope(error) {
72
+ const structured = error;
73
+ return {
74
+ schemaVersion: 1,
75
+ ok: false,
76
+ ...(structured?.partialData === undefined ? {} : { data: structured.partialData }),
77
+ error: toErrorEnvelope(error),
78
+ };
79
+ }
80
+ async function executeAndPrint(ctx, operation, input) {
81
+ try {
82
+ const result = await executeMeetingOperation(operation, input, {
83
+ ctx,
84
+ host: createMeetingHost(ctx),
85
+ continuation: createMeetingContinuationSigner(ctx),
86
+ });
87
+ ctx.output.write(successEnvelope(operation, result), { json: true });
88
+ }
89
+ catch (error) {
90
+ console.error(JSON.stringify(failureEnvelope(error), null, 2));
91
+ process.exitCode = isCliError(error) ? error.exitCode : ExitCode.GeneralError;
92
+ }
93
+ }
94
+ export const meetingShortcut = {
95
+ name: 'meeting',
96
+ description: '泛微E10会议管理 CLI(标准版:查询/冲突/预约/回执/签到)',
97
+ category: 'shortcut',
98
+ register({ program, action }) {
99
+ const meeting = program.command('meeting').description('泛微E10会议管理(模式探测/会议列表/会议室/类型/冲突检测/预约/回执/签到)');
100
+ meeting.command('schema')
101
+ .description('Print meeting operation manifest for agents')
102
+ .action(action((ctx) => {
103
+ ctx.output.write(meetingManifest, { json: true });
104
+ }));
105
+ meeting.command('run')
106
+ .description('Run a meeting operation with JSON input')
107
+ .argument('<operation>', 'operation name, e.g. meeting.room.list')
108
+ .option('--input <path>', 'JSON input file, or - for stdin')
109
+ .option('--input-json <json>', 'inline JSON input object')
110
+ .action(action(async (ctx, operation, options) => {
111
+ await executeAndPrint(ctx, String(operation), await inputFrom(options));
112
+ }));
113
+ // 高频便捷命令(薄包装,最终调用同一 operation 执行器)
114
+ meeting.command('today')
115
+ .description('查询今天的会议(meeting.list 薄包装)')
116
+ .action(action(async (ctx) => {
117
+ const today = new Date();
118
+ const pad = (value) => String(value).padStart(2, '0');
119
+ const date = `${today.getFullYear()}-${pad(today.getMonth() + 1)}-${pad(today.getDate())}`;
120
+ await executeAndPrint(ctx, 'meeting.list', { filterDate: date });
121
+ }));
122
+ },
123
+ };
@@ -0,0 +1,64 @@
1
+ import { meetingOperationDefinitions } from './operations/registry.js';
2
+ /**
3
+ * 未实现能力(禁用边界,不得编造或绕过):
4
+ * - EB 版业务链路(query_mtDetail_list / create_meeting / 三件套 / 议题 / 纪要 / 报表):
5
+ * 依赖 eBuilder formdata 动态字段与工作流集成,未随 v1 实现。
6
+ * - 周期会议走工作流(repeatWorkFlowBaseIds 非空)与 onlyFlowCreate=1 的流程建会:
7
+ * 需要 workflow 集成,create.prepare 会主动拒绝并提示。
8
+ * - 提前结束会议(meeting.over):服务端为空实现(假成功),CLI 不提供。
9
+ * - 签到二维码(getQrCode 入参契约未确认)、签到写操作(signMeeting 等)、
10
+ * 会议室/会议类型管理(add/update/delete/lock/详情):待后续版本评估。
11
+ *
12
+ * 已实现的生命周期写操作:meeting.change(变更:按会议状态自动选 operateType,
13
+ * 草稿=UPDATE / 正常=CHANGE)、meeting.cancel(取消)、meeting.delete(彻底删除),
14
+ * 均走 prepare→apply 确认链。权限由服务端按会议状态判定并返回原文提示,CLI 原样透传。
15
+ */
16
+ const WITHHELD = [
17
+ {
18
+ name: 'meeting.eb.*',
19
+ reason: 'EB 版业务链路依赖 eBuilder formdata 动态字段结构与工作流集成,v1 未实现;请使用 EB 版独立流程或后续版本。',
20
+ },
21
+ {
22
+ name: 'meeting.create.workflow',
23
+ reason: 'onlyFlowCreate=1 或周期会议配置工作流时必须走流程建会;CLI v1 未集成工作流发起,create.prepare 会主动拒绝。',
24
+ },
25
+ {
26
+ name: 'meeting.over',
27
+ reason: '服务端提前结束会议(overMeeting)为空实现:MeetingDetailController.overMeetingDetail 中 service 调用被注释,接口直接返回成功但不生效;CLI 不提供该能力,禁止绕过(调用只会得到“假成功”)。',
28
+ },
29
+ {
30
+ name: 'meeting.sign.qrcode',
31
+ reason: '签到二维码接口(getQrCode)入参契约未在源资料中确认,未实现,不得猜测参数。',
32
+ },
33
+ {
34
+ name: 'meeting.sign.submit',
35
+ reason: '签到写操作(signMeeting 等)未实现,不得绕过确认链直接调用。',
36
+ },
37
+ {
38
+ name: 'meeting.room.admin / meeting.type.admin',
39
+ reason: '会议室/会议类型管理(add/update/delete/lock/详情)未实现,待后续版本评估。',
40
+ },
41
+ ];
42
+ function buildOperations() {
43
+ return meetingOperationDefinitions().map((definition) => ({
44
+ name: definition.name,
45
+ resource: definition.resource,
46
+ summary: definition.summary,
47
+ capability: definition.capability,
48
+ risk: definition.risk,
49
+ ...(definition.requiresConfirmation ? { requiresConfirmation: true } : {}),
50
+ inputSchema: definition.inputSchema,
51
+ }));
52
+ }
53
+ export const meetingManifest = {
54
+ schemaVersion: 1,
55
+ name: 'meeting',
56
+ title: '泛微E10会议管理(标准版:查询/冲突/预约/取消/回执/签到)',
57
+ version: '1.0.0',
58
+ host: 'e10-meeting',
59
+ status: 'builtin',
60
+ sourceSkill: 'weaver-e10-meeting',
61
+ capabilities: ['env', 'meeting', 'room', 'type', 'conflict', 'lifecycle', 'receipt', 'sign'],
62
+ operations: buildOperations(),
63
+ withheldOperations: WITHHELD,
64
+ };
@@ -0,0 +1,58 @@
1
+ import { meetingSuccess } from './types.js';
2
+ import { extractRows } from './shared.js';
3
+ // ---------------------------------------------------------------------------
4
+ // 会议类型列表(mt_type browser 组件;总数走 count 接口)
5
+ // ---------------------------------------------------------------------------
6
+ const meetingTypeList = {
7
+ name: 'meeting.type.list',
8
+ resource: 'type',
9
+ method: 'type.list',
10
+ action: 'meeting.type.list',
11
+ summary: '查询会议类型列表(mt_type browser 组件,权限友好;includeCount=true 时再调 count/mt_type 取总数)',
12
+ capability: 'type',
13
+ risk: 'read',
14
+ inputSchema: {
15
+ type: 'object',
16
+ additionalProperties: false,
17
+ properties: {
18
+ name: { type: 'string', description: '按类型名称过滤' },
19
+ current: { type: 'integer', description: '页码,默认 1' },
20
+ pageSize: { type: 'integer', description: '页大小,默认 100' },
21
+ includeCount: { type: 'boolean', description: '是否调用 count/mt_type 返回总数' },
22
+ },
23
+ },
24
+ handler: async (args, { host }) => {
25
+ const body = {
26
+ browserMultiple: false,
27
+ current: typeof args.current === 'number' && args.current > 0 ? args.current : 1,
28
+ pageSize: typeof args.pageSize === 'number' && args.pageSize > 0 ? args.pageSize : 100,
29
+ formDatas: typeof args.name === 'string' && args.name.trim()
30
+ ? { name: [{ nameCondition: 'in', name: args.name.trim() }] }
31
+ : {},
32
+ quickSearchDatas: {},
33
+ type: 'and',
34
+ };
35
+ const response = await host.request({
36
+ method: 'POST',
37
+ path: '/api/meeting/common/browser/data/mt_type',
38
+ operation: 'meeting.type.list',
39
+ body,
40
+ });
41
+ const { rows } = extractRows(response);
42
+ let total;
43
+ if (args.includeCount === true) {
44
+ const countResponse = await host.request({
45
+ method: 'POST',
46
+ path: '/api/meeting/common/browser/count/mt_type',
47
+ operation: 'meeting.type.list',
48
+ body: { ...body, formDatas: typeof args.name === 'string' && args.name.trim() ? body.formDatas : {} },
49
+ });
50
+ total = countResponse?.data?.total;
51
+ }
52
+ return meetingSuccess('meeting.type.list', {
53
+ ...(total !== undefined ? { total } : {}),
54
+ types: rows,
55
+ });
56
+ },
57
+ };
58
+ export const meetingCatalogOperations = [meetingTypeList];