magic-builder 1.2.0 → 1.3.1

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.
@@ -0,0 +1,339 @@
1
+ 'use strict';
2
+
3
+ const { existsSync, readFileSync } = require('fs');
4
+ const { resolve } = require('path');
5
+ const { getWidgetPublishConfigPath } = require('../lib/config');
6
+ const { success, fail } = require('../lib/output');
7
+
8
+ const DEFAULT_ORIGIN = 'https://open.larkoffice.com';
9
+
10
+ async function run(args, opts) {
11
+ if (args.length) {
12
+ fail('Usage: magic-builder widget-publish --app-id <id> --block-type-id <id>', 'E_INVALID_ARGS');
13
+ }
14
+
15
+ const appId = String(opts.appId || '').trim();
16
+ const blockTypeId = String(opts.blockTypeId || '').trim();
17
+ if (!appId || !blockTypeId) {
18
+ fail('Missing --app-id or --block-type-id.', 'E_INVALID_ARGS');
19
+ }
20
+
21
+ const auth = loadPublishAuth(opts.config);
22
+ const client = createOpenPlatformClient(auth, opts.origin || DEFAULT_ORIGIN);
23
+ const detail = await client.post(`/developers/v2/block/detail/${appId}/${blockTypeId}`, {});
24
+ const block = extractBlock(detail);
25
+ const pkgId = extractDraftPkgId(detail, block);
26
+ if (!pkgId) throw commandError('Draft widget pkgId was not found in block detail.', 'E_INVALID_RESPONSE');
27
+
28
+ const updatePayload = { block: buildBlockUpdatePayload(block, pkgId, blockTypeId) };
29
+ if (!opts.dryRun) {
30
+ await client.post(`/developers/v2/block/detail/update/${appId}/${blockTypeId}`, updatePayload);
31
+ }
32
+ const versionChangeResponse = await client.post(`/developers/v1/app_version/change/${appId}`, {});
33
+ const versionChange = unwrapData(versionChangeResponse) || {};
34
+ const versionsResponse = await client.post(`/developers/v1/app_version/list/${appId}`, {});
35
+ const versions = extractVersionList(versionsResponse);
36
+ const appVersion = opts.version || getNextPatchVersion(versions);
37
+ const createPayload = buildCreateVersionPayload(
38
+ appVersion,
39
+ opts.changeLog,
40
+ getLatestVersion(versions),
41
+ versionChange
42
+ );
43
+
44
+ if (opts.dryRun) {
45
+ success({
46
+ dryRun: true,
47
+ appId,
48
+ blockTypeId,
49
+ pkgId,
50
+ appVersion,
51
+ updatePayload,
52
+ createPayload,
53
+ }, opts);
54
+ return;
55
+ }
56
+
57
+ const createResponse = await client.post(`/developers/v1/app_version/create/${appId}`, createPayload);
58
+ const appVersionId = extractAppVersionId(createResponse);
59
+ if (!appVersionId) throw commandError('Created app version id was not found in response.', 'E_INVALID_RESPONSE');
60
+
61
+ const publishResponse = await client.post(`/developers/v1/publish/commit/${appId}/${appVersionId}`, {});
62
+ success({
63
+ appId,
64
+ blockTypeId,
65
+ pkgId,
66
+ appVersion,
67
+ appVersionId,
68
+ published: true,
69
+ response: publishResponse,
70
+ }, opts);
71
+ }
72
+
73
+ function loadPublishAuth(configFile) {
74
+ const path = resolve(String(configFile || getWidgetPublishConfigPath()));
75
+ if (!existsSync(path)) {
76
+ throw commandError(`Widget publish config not found: ${path}`, 'E_NO_CONFIG');
77
+ }
78
+
79
+ let config;
80
+ try {
81
+ config = JSON.parse(readFileSync(path, 'utf8'));
82
+ } catch (error) {
83
+ throw commandError(`Invalid widget publish config: ${error.message}`, 'E_INVALID_CONFIG');
84
+ }
85
+
86
+ const cookie = String(config.cookie || '').trim();
87
+ const csrfToken = String(config.csrfToken || config.csrf_token || '').trim();
88
+ if (!cookie || !csrfToken) {
89
+ throw commandError('Widget publish config must contain cookie and csrfToken.', 'E_INVALID_CONFIG');
90
+ }
91
+ return {
92
+ cookie,
93
+ csrfToken,
94
+ timezoneOffset: Number.isFinite(Number(config.timezoneOffset)) ? Number(config.timezoneOffset) : -480,
95
+ locale: String(config.locale || 'zh-CN'),
96
+ };
97
+ }
98
+
99
+ function createOpenPlatformClient(auth, origin = DEFAULT_ORIGIN, fetchImpl = fetch) {
100
+ const baseUrl = String(origin || DEFAULT_ORIGIN).replace(/\/+$/, '');
101
+ return {
102
+ async post(path, body) {
103
+ const response = await fetchImpl(`${baseUrl}${path}`, {
104
+ method: 'POST',
105
+ headers: {
106
+ accept: '*/*',
107
+ 'accept-language': `${auth.locale},zh;q=0.9`,
108
+ 'cache-control': 'no-cache',
109
+ 'content-type': 'application/json',
110
+ cookie: auth.cookie,
111
+ origin: baseUrl,
112
+ pragma: 'no-cache',
113
+ referer: getRequestReferer(baseUrl, path),
114
+ 'x-csrf-token': auth.csrfToken,
115
+ 'x-timezone-offset': String(auth.timezoneOffset),
116
+ },
117
+ body: JSON.stringify(body || {}),
118
+ });
119
+ const text = await response.text();
120
+ const data = parseJson(text);
121
+ if (!response.ok || isApiFailure(data)) {
122
+ const reason = data?.msg || data?.message || `HTTP ${response.status}`;
123
+ throw commandError(
124
+ `Open Platform request failed at ${path}: ${reason}`,
125
+ response.status === 401 || response.status === 403 ? 'E_AUTH_FAILED' : 'E_REQUEST_FAILED'
126
+ );
127
+ }
128
+ return data ?? text;
129
+ },
130
+ };
131
+ }
132
+
133
+ function extractBlock(response) {
134
+ const data = unwrapData(response);
135
+ return data?.block || data?.detail?.block || data?.blockDetail?.block || data?.detail || data;
136
+ }
137
+
138
+ function extractDraftPkgId(response, block = {}) {
139
+ const candidates = [
140
+ response?.data?.blockVersions?.testVersion?.id,
141
+ response?.data?.blockVersions?.testVersion?.pkgId,
142
+ response?.data?.blockVersions?.draftVersion?.id,
143
+ response?.data?.blockVersions?.draftVersion?.pkgId,
144
+ block?.draftVersion?.pkgId,
145
+ block?.draftPkg?.pkgId,
146
+ block?.draftPackage?.pkgId,
147
+ block?.draft?.pkgId,
148
+ findDraftPkgId(response),
149
+ findValue(response, ['draftPkgId', 'draftPackageId']),
150
+ block?.pkgId,
151
+ ];
152
+ return String(candidates.find(Boolean) || '').trim();
153
+ }
154
+
155
+ function buildBlockUpdatePayload(block = {}, pkgId, blockTypeId) {
156
+ return {
157
+ pkgId: String(pkgId),
158
+ icon: block.icon || '',
159
+ i18nTitle: block.i18nTitle || {},
160
+ i18nDesc: block.i18nDesc || {},
161
+ status: block.status !== false,
162
+ blockTypeId: block.blockTypeId || block.blockTypeID || blockTypeId,
163
+ pkgUpdateType: Number.isFinite(Number(block.pkgUpdateType)) ? Number(block.pkgUpdateType) : 0,
164
+ docXHost: block.docXHost || block.docxHost || {},
165
+ };
166
+ }
167
+
168
+ function extractVersionList(response) {
169
+ const data = unwrapData(response);
170
+ if (Array.isArray(data)) return data;
171
+ for (const key of ['items', 'list', 'appVersions', 'versions']) {
172
+ if (Array.isArray(data?.[key])) return data[key];
173
+ }
174
+ return findArray(data, ['items', 'list', 'appVersions', 'versions']);
175
+ }
176
+
177
+ function getNextPatchVersion(versions) {
178
+ const parsed = versions
179
+ .map(item => String(item?.appVersion || item?.version || item?.versionName || item || ''))
180
+ .map(parseSemver)
181
+ .filter(Boolean)
182
+ .sort(compareSemver);
183
+ const latest = parsed.at(-1) || [1, 0, -1];
184
+ return `${latest[0]}.${latest[1]}.${latest[2] + 1}`;
185
+ }
186
+
187
+ function getLatestVersion(versions) {
188
+ return versions
189
+ .map(item => ({ item, version: parseSemver(item?.appVersion || item?.version || item?.versionName || item) }))
190
+ .filter(entry => entry.version)
191
+ .sort((a, b) => compareSemver(a.version, b.version))
192
+ .at(-1)?.item || {};
193
+ }
194
+
195
+ function buildCreateVersionPayload(
196
+ appVersion,
197
+ changeLog = '更新文档小组件版本',
198
+ latestVersion = {},
199
+ versionChange = {}
200
+ ) {
201
+ const shareConfig = versionChange.changeAppShareConfig?.b2cShareSplitConfigSuggest;
202
+ return {
203
+ appVersion: String(appVersion),
204
+ mobileDefaultAbility: versionChange.mobileDefaultAbility || latestVersion.mobileDefaultAbility || 'bot',
205
+ pcDefaultAbility: versionChange.pcDefaultAbility || latestVersion.pcDefaultAbility || 'bot',
206
+ changeLog: String(changeLog || '更新文档小组件版本'),
207
+ visibleSuggest: latestVersion.visibleSuggest || { departments: [], members: [], groups: [], isAll: 1 },
208
+ applyReasonConfig: versionChange.applyReasonConfig || latestVersion.applyReasonConfig || {
209
+ apiPrivilegeNeedReason: true,
210
+ contactPrivilegeNeedReason: true,
211
+ dataPrivilegeReasonMap: {},
212
+ visibleScopeNeedReason: true,
213
+ apiPrivilegeReasonMap: {},
214
+ contactPrivilegeReason: '',
215
+ isDataPrivilegeExpandMap: { vc: false },
216
+ visibleScopeReason: '',
217
+ dataPrivilegeNeedReason: true,
218
+ isAutoAudit: false,
219
+ isContactExpand: false,
220
+ },
221
+ b2cShareSplitConfigSuggest: shareConfig || latestVersion.b2cShareSplitConfigSuggest || {
222
+ b2cGroupChatShareEnable: false,
223
+ b2cP2PChatShareEnable: false,
224
+ b2cP2PChatNeedAudit: false,
225
+ },
226
+ autoPublish: false,
227
+ blackVisibleSuggest: latestVersion.blackVisibleSuggest || { departments: [], members: [], groups: [], isAll: 0 },
228
+ };
229
+ }
230
+
231
+ function extractAppVersionId(response) {
232
+ const data = unwrapData(response);
233
+ return String(
234
+ data?.appVersionId
235
+ || data?.versionId
236
+ || data?.appVersion?.id
237
+ || data?.version?.id
238
+ || data?.id
239
+ || findValue(data, ['appVersionId', 'versionId'])
240
+ || ''
241
+ ).trim();
242
+ }
243
+
244
+ function unwrapData(value) {
245
+ let current = value;
246
+ for (let i = 0; i < 3; i++) {
247
+ if (!current || typeof current !== 'object' || Array.isArray(current) || !('data' in current)) break;
248
+ current = current.data;
249
+ }
250
+ return current;
251
+ }
252
+
253
+ function findValue(value, names, predicate = () => true, path = '') {
254
+ if (!value || typeof value !== 'object') return '';
255
+ for (const [name, child] of Object.entries(value)) {
256
+ const childPath = path ? `${path}.${name}` : name;
257
+ if (names.includes(name) && child != null && predicate({ name, path: childPath })) return child;
258
+ const nested = findValue(child, names, predicate, childPath);
259
+ if (nested) return nested;
260
+ }
261
+ return '';
262
+ }
263
+
264
+ function findDraftPkgId(value, path = '') {
265
+ if (!value || typeof value !== 'object') return '';
266
+ const marker = [
267
+ path,
268
+ value.status,
269
+ value.versionStatus,
270
+ value.pkgStatus,
271
+ value.type,
272
+ value.name,
273
+ ].filter(Boolean).join(' ');
274
+ if (/draft|草稿/i.test(marker) && value.pkgId) return value.pkgId;
275
+ for (const [name, child] of Object.entries(value)) {
276
+ const found = findDraftPkgId(child, path ? `${path}.${name}` : name);
277
+ if (found) return found;
278
+ }
279
+ return '';
280
+ }
281
+
282
+ function findArray(value, names) {
283
+ if (!value || typeof value !== 'object') return [];
284
+ for (const [name, child] of Object.entries(value)) {
285
+ if (names.includes(name) && Array.isArray(child)) return child;
286
+ const found = findArray(child, names);
287
+ if (found.length) return found;
288
+ }
289
+ return [];
290
+ }
291
+
292
+ function getRequestReferer(baseUrl, path) {
293
+ const blockMatch = path.match(/\/block\/detail(?:\/update)?\/([^/]+)\/([^/]+)/);
294
+ if (blockMatch) return `${baseUrl}/app/${blockMatch[1]}/blocks/${blockMatch[2]}`;
295
+ const publishMatch = path.match(/\/publish\/commit\/([^/]+)\/([^/]+)/);
296
+ if (publishMatch) return `${baseUrl}/app/${publishMatch[1]}/version/${publishMatch[2]}`;
297
+ const appMatch = path.match(/\/app_version\/(?:list|create)\/([^/]+)/);
298
+ if (appMatch) return `${baseUrl}/app/${appMatch[1]}/version/create`;
299
+ return `${baseUrl}/`;
300
+ }
301
+
302
+ function parseSemver(value) {
303
+ const match = String(value || '').trim().match(/^(\d+)\.(\d+)\.(\d+)$/);
304
+ return match ? match.slice(1).map(Number) : null;
305
+ }
306
+
307
+ function compareSemver(a, b) {
308
+ return a[0] - b[0] || a[1] - b[1] || a[2] - b[2];
309
+ }
310
+
311
+ function parseJson(text) {
312
+ try { return JSON.parse(text); } catch (_) { return null; }
313
+ }
314
+
315
+ function isApiFailure(data) {
316
+ if (!data || typeof data !== 'object') return false;
317
+ if ('code' in data && ![0, '0', 200, '200'].includes(data.code)) return true;
318
+ return data.success === false;
319
+ }
320
+
321
+ function commandError(message, code) {
322
+ const error = new Error(message);
323
+ error.code = code;
324
+ return error;
325
+ }
326
+
327
+ module.exports = {
328
+ run,
329
+ loadPublishAuth,
330
+ createOpenPlatformClient,
331
+ extractBlock,
332
+ extractDraftPkgId,
333
+ buildBlockUpdatePayload,
334
+ extractVersionList,
335
+ getNextPatchVersion,
336
+ getLatestVersion,
337
+ buildCreateVersionPayload,
338
+ extractAppVersionId,
339
+ };
package/src/index.js CHANGED
@@ -15,6 +15,7 @@ module.exports = {
15
15
  feedback: require('./commands/feedback'),
16
16
  performance: require('./commands/performance'),
17
17
  perf: require('./commands/performance'),
18
+ widgetPublish: require('./commands/widget-publish'),
18
19
  extractCookie: require('./commands/extract-cookie'),
19
20
  skill: require('./commands/skill'),
20
21
  version: require('./commands/version'),
package/src/lib/config.js CHANGED
@@ -8,6 +8,8 @@ const DEFAULT_BASE_URL = 'https://magic.solutionsuite.cn';
8
8
  const CONFIG_DIR = path.join(os.homedir(), '.magic-builder');
9
9
  const APPS_CONFIG_FILE = 'magic-apps.json';
10
10
  const LEGACY_APPS_CONFIG_FILE = '.magic-apps.json';
11
+ const WIDGET_PUBLISH_CONFIG_FILE = 'widget-publish.json';
12
+ const PERFORMANCE_CONFIG_FILE = 'performance.json';
11
13
 
12
14
  function normalizeMagicBaseUrl(value) {
13
15
  const raw = String(value || DEFAULT_BASE_URL).trim().replace(/\/+$/, '');
@@ -23,6 +25,20 @@ function getAppsConfigPath() {
23
25
  return path.join(CONFIG_DIR, APPS_CONFIG_FILE);
24
26
  }
25
27
 
28
+ function getWidgetPublishConfigPath() {
29
+ return path.join(CONFIG_DIR, WIDGET_PUBLISH_CONFIG_FILE);
30
+ }
31
+
32
+ function getPerformanceConfigPath() {
33
+ return path.join(CONFIG_DIR, PERFORMANCE_CONFIG_FILE);
34
+ }
35
+
36
+ function loadPerformanceConfig() {
37
+ const p = getPerformanceConfigPath();
38
+ if (!fs.existsSync(p)) return {};
39
+ try { return JSON.parse(fs.readFileSync(p, 'utf8')); } catch (_) { return {}; }
40
+ }
41
+
26
42
  function loadAppsConfig() {
27
43
  const p = fs.existsSync(getAppsConfigPath())
28
44
  ? getAppsConfigPath()
@@ -38,4 +54,15 @@ function saveAppsConfig(config) {
38
54
  fs.writeFileSync(getAppsConfigPath(), JSON.stringify(config, null, 2));
39
55
  }
40
56
 
41
- module.exports = { DEFAULT_BASE_URL, CONFIG_DIR, normalizeMagicBaseUrl, getBaseUrl, getAppsConfigPath, loadAppsConfig, saveAppsConfig };
57
+ module.exports = {
58
+ DEFAULT_BASE_URL,
59
+ CONFIG_DIR,
60
+ normalizeMagicBaseUrl,
61
+ getBaseUrl,
62
+ getAppsConfigPath,
63
+ getWidgetPublishConfigPath,
64
+ getPerformanceConfigPath,
65
+ loadPerformanceConfig,
66
+ loadAppsConfig,
67
+ saveAppsConfig,
68
+ };
package/src/lib/help.js CHANGED
@@ -15,13 +15,14 @@ SYNTAX:
15
15
 
16
16
  COMMANDS:
17
17
  auth Login and manage Magic developer tokens
18
- page Publish, list, export, and delete Magic pages
18
+ page Publish and manage Magic pages, collaborators, and access
19
19
  faas Publish, list, and delete Magic FaaS functions
20
20
  file Upload, list, and delete TOS files
21
21
  link Generate Magic share links
22
22
  doc Create or append Feishu Doc HTML Box apps
23
23
  feedback Create Magic feedback records
24
- performance Submit Markdown content to a People performance draft (alias: perf)
24
+ performance Write or formally submit a People performance review (alias: perf)
25
+ widget-publish Publish a document widget draft as a new app version
25
26
  extract-cookie Extract Cookie data from a curl command
26
27
  skill Check or update the Magic Builder skill package
27
28
  version Show CLI version
@@ -38,6 +39,8 @@ EXAMPLES:
38
39
  magic-builder page list --scope mine
39
40
  magic-builder page export --id recxxx --out app.html
40
41
  magic-builder page delete --id recxxx
42
+ magic-builder page collaborators add --id recxxx --open-ids ou_xxx,ou_yyy
43
+ magic-builder page access groups add --id recxxx --chat-ids oc_xxx
41
44
  magic-builder faas publish handler.js --name report-api
42
45
  magic-builder faas list
43
46
  magic-builder faas delete --id recxxx
@@ -48,6 +51,7 @@ EXAMPLES:
48
51
  magic-builder doc create --html app.html --title "Demo"
49
52
  magic-builder feedback create --feedback "打不开页面"
50
53
  magic-builder performance --cookie ./cookie.txt --markdown ./performance.md
54
+ magic-builder widget-publish --app-id cli_xxx --block-type-id blk_xxx
51
55
  magic-builder extract-cookie --curl-file ./request.curl
52
56
  magic-builder skill install
53
57
  magic-builder skill check-update
@@ -83,11 +87,19 @@ COMMANDS:
83
87
  auth set <token>
84
88
  auth show
85
89
 
86
- page publish <html> --title <title> [--id <id>] [--open-source]
90
+ page publish <html> --title <title> [--id <id>] [--custom-id <id>] [--open-source] [--comments]
87
91
  page list [--title <keyword>] [--scope mine|public]
88
92
  page export --id <id> --out <file|dir> [--scope mine|public]
89
93
  page export --title <keyword> --out <dir> [--all] [--scope mine|public]
90
94
  page delete --id <id>
95
+ page collaborators list --id <id>
96
+ page collaborators add|remove|set --id <id> --open-ids <ou_a,ou_b>
97
+ page collaborators set --id <id> --file <collaborators.json>
98
+ page access get|enable|disable --id <id>
99
+ page access require-login --id <id> --enabled true|false
100
+ page access users add|remove|set --id <id> --open-ids <ou_a,ou_b>
101
+ page access groups add|remove|set --id <id> --chat-ids <oc_a,oc_b>
102
+ page access set --id <id> --file <access.json>
91
103
 
92
104
  faas publish <file.js> --name <name> [--id <id>]
93
105
  faas publish --code <code> --name <name> [--id <id>]
@@ -101,16 +113,26 @@ COMMANDS:
101
113
  link create --title <text>
102
114
  link create --fid <id>
103
115
 
104
- doc create --html <file> --title <title> [--summary <text>] [--as bot|user]
105
- doc append --html <file> --doc-token <token> [--as bot|user]
116
+ doc create --html <file> --title <title> [--summary <text>] [--as bot|user] [--edition external|bytedance]
117
+ doc append --html <file> --doc-token <token> [--as bot|user] [--edition external|bytedance]
106
118
 
107
119
  feedback create --feedback <text> [--title <title>] [--summary <text>]
108
120
  feedback create --feedback-file <file> [--dry-run]
109
121
 
110
122
  performance --review-url <url> --cookie <content|file> --markdown <content|file|url> [--dry-run]
123
+ perf self-review --review-url <url> [--good <text>|--good-file <file>]
124
+ [--improve <text>|--improve-file <file>] [--values-comment <text>|--values-comment-file <file>] [--dry-run]
125
+ perf submit --review-url <url> --cookie-file <file> --template-group-id <id> [--dry-run|--yes]
126
+ perf key-works --root-review-id <id> [--template-id <id>] --cookie-file <file>
127
+ perf review-users --review-id <id> --stage-id <id> --cookie-file <file>
128
+ perf review-users --review-url <url> --cookie-file <file>
129
+ perf invite-review --payload-file <file> --review-url <url> --cookie-file <file> [--dry-run]
111
130
  perf --review-url <url> --cookie-file <file> --markdown-file <file> [--dry-run]
112
131
 
113
- extract-cookie --curl <content|file> [--out <file>|--out-dir <dir>]
132
+ widget-publish --app-id <id> --block-type-id <id> [--version <x.y.z>] [--change-log <text>]
133
+ [--config <file>] [--dry-run]
134
+
135
+ extract-cookie --curl <content|file> [--out <file>|--out-dir <dir>] [--widget-publish]
114
136
 
115
137
  skill install [--environment auto|local|cloud] [--skills-root <dir>]
116
138
  skill check-update [--environment auto|local|cloud] [--skills-root <dir>]
@@ -155,11 +177,19 @@ SYNTAX:
155
177
  page: `@HELP magic-builder/page
156
178
 
157
179
  SYNTAX:
158
- magic-builder page publish <html> --title <title> [--id <id>] [--open-source]
180
+ magic-builder page publish <html> --title <title> [--id <id>] [--custom-id <id>] [--open-source] [--comments]
159
181
  magic-builder page list [--title <keyword>] [--scope mine|public]
160
182
  magic-builder page export --id <id> --out <file|dir> [--scope mine|public]
161
183
  magic-builder page export --title <keyword> --out <dir> [--all] [--scope mine|public]
162
184
  magic-builder page delete --id <id>
185
+ magic-builder page collaborators list --id <id>
186
+ magic-builder page collaborators add|remove|set --id <id> --open-ids <ou_a,ou_b>
187
+ magic-builder page collaborators set --id <id> --file <collaborators.json>
188
+ magic-builder page access get|enable|disable --id <id>
189
+ magic-builder page access require-login --id <id> --enabled true|false
190
+ magic-builder page access users add|remove|set --id <id> --open-ids <ou_a,ou_b>
191
+ magic-builder page access groups add|remove|set --id <id> --chat-ids <oc_a,oc_b>
192
+ magic-builder page access set --id <id> --file <access.json>
163
193
  `,
164
194
  faas: `@HELP magic-builder/faas
165
195
 
@@ -185,8 +215,8 @@ SYNTAX:
185
215
  doc: `@HELP magic-builder/doc
186
216
 
187
217
  SYNTAX:
188
- magic-builder doc create --html <file> --title <title> [--summary <text>] [--as bot|user]
189
- magic-builder doc append --html <file> --doc-token <token> [--as bot|user]
218
+ magic-builder doc create --html <file> --title <title> [--summary <text>] [--as bot|user] [--edition external|bytedance]
219
+ magic-builder doc append --html <file> --doc-token <token> [--as bot|user] [--edition external|bytedance]
190
220
  `,
191
221
  feedback: `@HELP magic-builder/feedback
192
222
 
@@ -198,6 +228,14 @@ SYNTAX:
198
228
 
199
229
  SYNTAX:
200
230
  magic-builder performance --review-url <url> --cookie <content|file> --markdown <content|file|url>
231
+ magic-builder perf self-review --review-url <url> --good <text> --improve <text> --values-comment <text>
232
+ magic-builder perf self-review --review-url <url> --good-file <file> --improve-file <file> --values-comment-file <file>
233
+ magic-builder perf submit --review-url <url> --cookie-file <file> --template-group-id <id> --dry-run
234
+ magic-builder perf submit --review-url <url> --cookie-file <file> --template-group-id <id> --yes
235
+ magic-builder perf key-works --root-review-id <id> [--template-id <id>] --cookie-file <file>
236
+ magic-builder perf review-users --review-id <id> --stage-id <id> --cookie-file <file>
237
+ magic-builder perf review-users --review-url <url> --cookie-file <file>
238
+ magic-builder perf invite-review --payload-file <file> --review-url <url> --cookie-file <file> [--dry-run]
201
239
  magic-builder perf --review-url <url> --cookie-file <file> --markdown-file <file>
202
240
 
203
241
  COOKIE:
@@ -207,9 +245,41 @@ DRAFT:
207
245
  --review-url resolves operator, tenant, form ids, and the current version
208
246
  from /perf/api/user/settings and /perf/api/foundation/draft.
209
247
 
248
+ SELF REVIEW:
249
+ self-review updates only the explicitly passed text fields and preserves
250
+ the current performance and values ratings. It writes by default; use
251
+ --dry-run to inspect the generated payload without saving it.
252
+
253
+ KEY WORKS:
254
+ key-works reads the review-stage data for the specified template and root
255
+ review. The template id defaults to invite_review_template_id in
256
+ ~/.magic-builder/performance.json and can be overridden with --template-id.
257
+ It is read-only and prints the complete API response as JSON.
258
+
259
+ REVIEW USERS:
260
+ review-users reads the invited review-user list for a review and stage. It
261
+ accepts either --review-url or explicit --review-id and --stage-id. Explicit
262
+ ids override values parsed from the URL. It does not modify review data and
263
+ prints the complete API response as JSON.
264
+
265
+ INVITE REVIEW:
266
+ invite-review writes a complete invite-review draft payload to People. Pass
267
+ the current payload as JSON or a JSON file. Use --dry-run to inspect it
268
+ without writing. The payload must contain key, data, and version, and its key
269
+ must identify an invite_review draft.
270
+
210
271
  OPTIONS:
211
272
  --dry-run Print the generated payload without sending it
212
273
  --review-url <url> People performance review URL (recommended)
274
+ --good <text> Update the "did well" self-review text
275
+ --good-file <file> Read the "did well" text from a file
276
+ --improve <text> Update the "to improve" self-review text
277
+ --improve-file <file> Read the "to improve" text from a file
278
+ --values-comment <text> Update the values self-review comment
279
+ --values-comment-file <file> Read the values comment from a file
280
+ --template-group-id <id> Template group id (only needed when absent from the draft)
281
+ --submit-endpoint <url> Override the formal submission endpoint
282
+ --yes Confirm the irreversible formal submission
213
283
  --draft-version <number> Override the version loaded from the current draft
214
284
  --review-id <id> Override the performance review id
215
285
  --form-id <id> Override the invitation/form id used by the draft key
@@ -219,17 +289,42 @@ OPTIONS:
219
289
  --source-id <id> Override the form source id
220
290
  --field-source-id <id> Override the writable field source id
221
291
  --tenant-id <id> Override the People tenant id
292
+ --referer <url> Override the People request Referer
293
+ --stage-endpoint <url> Override the key-works query endpoint
294
+ --stage-id <id> Review stage id used by review-users
295
+ --review-users-endpoint <url> Override the review-users API origin
296
+ --payload <json> Complete invite-review draft payload
297
+ --payload-file <file> Read invite-review draft payload from a file
222
298
  --endpoint <url> Override the draft API endpoint
223
299
  `,
224
300
  perf: `@HELP magic-builder/performance
225
301
 
226
302
  Alias of "magic-builder performance".
303
+ `,
304
+ 'widget-publish': `@HELP magic-builder/widget-publish
305
+
306
+ SYNTAX:
307
+ magic-builder widget-publish --app-id <id> --block-type-id <id>
308
+ [--version <x.y.z>] [--change-log <text>] [--config <file>] [--dry-run]
309
+
310
+ AUTH CONFIG:
311
+ Defaults to ~/.magic-builder/widget-publish.json:
312
+ {
313
+ "cookie": "open_locale=zh-CN; session=...",
314
+ "csrfToken": "...",
315
+ "timezoneOffset": -480
316
+ }
317
+
318
+ FLOW:
319
+ Load widget detail and latest uploaded pkgId, update the widget package, load the app's
320
+ version defaults, calculate the next version, create it, then submit publishing.
227
321
  `,
228
322
  'extract-cookie': `@HELP magic-builder/extract-cookie
229
323
 
230
324
  SYNTAX:
231
325
  magic-builder extract-cookie --curl <content|file> [--out <file>]
232
326
  magic-builder extract-cookie --curl-file <file> [--out-dir <dir>]
327
+ magic-builder extract-cookie --curl-file <file> --widget-publish
233
328
  cat request.curl | magic-builder extract-cookie [--out <file>]
234
329
 
235
330
  OPTIONS:
@@ -237,6 +332,7 @@ OPTIONS:
237
332
  --curl-file <file> Read curl command from a local file
238
333
  --out <file> Output file (default: ./cookie.txt)
239
334
  --out-dir <dir> Output directory; created when missing
335
+ --widget-publish Write Cookie and x-csrf-token to the widget publish config
240
336
  `,
241
337
  skill: `@HELP magic-builder/skill
242
338
 
@@ -21,6 +21,7 @@ async function uploadSingle(filePath, opts = {}) {
21
21
 
22
22
  const signRes = await request(`${baseUrl}/api/tos/sign`, {
23
23
  method: 'POST',
24
+ token,
24
25
  body: signBody,
25
26
  });
26
27
 
@@ -48,6 +49,8 @@ async function uploadSingle(filePath, opts = {}) {
48
49
  token,
49
50
  body: {
50
51
  action: 'record',
52
+ audit_id: signRes.data.audit_id,
53
+ audit_table_id: signRes.data.audit_table_id,
51
54
  url,
52
55
  key: signRes.data.key,
53
56
  filename,