magic-builder 1.3.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.
- package/README.md +20 -1
- package/package.json +1 -1
- package/src/commands/config.js +2 -1
- package/src/commands/doc.js +29 -6
- package/src/commands/page.js +108 -2
- package/src/commands/performance.js +240 -6
- package/src/commands/skill.js +7 -3
- package/src/lib/config.js +13 -0
- package/src/lib/help.js +72 -7
- package/src/lib/multipart.js +3 -0
package/README.md
CHANGED
|
@@ -120,6 +120,8 @@ magic-builder file list --title logo
|
|
|
120
120
|
magic-builder file delete --id <id>
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
+
文件上传采用 `sign -> PUT -> audit confirm` 流程。预签名成功后会返回 `audit_id`,CLI 在对象上传完成后自动确认审计;确认失败时命令返回失败,重复执行确认不会新增审计记录。
|
|
124
|
+
|
|
123
125
|
Generate a Magic link:
|
|
124
126
|
|
|
125
127
|
```bash
|
|
@@ -146,6 +148,18 @@ magic-builder performance --review-url <people-review-url> --markdown ./performa
|
|
|
146
148
|
|
|
147
149
|
With `--review-url`, the CLI loads the operator and tenant from `/perf/api/user/settings`, then reuses the current draft version, template, units, fields, source ids, and root review id from `/perf/api/foundation/draft`. Business ids are not hard-coded into the generated request.
|
|
148
150
|
|
|
151
|
+
Update the text fields in the self-review draft while preserving both rating fields and any omitted text:
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
magic-builder perf self-review --review-url <people-review-url> \
|
|
155
|
+
--good "What went well" \
|
|
156
|
+
--improve-file ./improvements.md \
|
|
157
|
+
--values-comment-file ./values-comment.md
|
|
158
|
+
magic-builder perf self-review --review-url <people-review-url> --good-file ./good.md --dry-run
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Each field can be provided as literal text or through its matching `--*-file` option. At least one field is required. The command reloads the latest `self_review` draft before every run and writes by default; `--dry-run` prints the payload without saving it.
|
|
162
|
+
|
|
149
163
|
Formally submit the latest draft to the current review stage:
|
|
150
164
|
|
|
151
165
|
```bash
|
|
@@ -166,8 +180,9 @@ cat ./request.curl | magic-builder extract-cookie --out-dir ./secrets
|
|
|
166
180
|
Create or append a Feishu document HTML Box:
|
|
167
181
|
|
|
168
182
|
```bash
|
|
169
|
-
magic-builder doc create --html app.html --title "Demo"
|
|
183
|
+
magic-builder doc create --html app.html --title "Demo" # defaults to --edition external
|
|
170
184
|
magic-builder doc append --html app.html --doc-token <docx-token>
|
|
185
|
+
magic-builder doc create --html app.html --title "ByteDance Demo" --edition bytedance
|
|
171
186
|
```
|
|
172
187
|
|
|
173
188
|
Publish a document widget draft as a new app version:
|
|
@@ -215,6 +230,10 @@ magic-builder skill check-update
|
|
|
215
230
|
magic-builder skill update
|
|
216
231
|
```
|
|
217
232
|
|
|
233
|
+
Skills are installed to `~/.agents/skills` by default so Codex, Trae, Doubao,
|
|
234
|
+
and other compatible agents can share them. Use `--skills-root <dir>` to
|
|
235
|
+
override the destination.
|
|
236
|
+
|
|
218
237
|
Check local environment:
|
|
219
238
|
|
|
220
239
|
```bash
|
package/package.json
CHANGED
package/src/commands/config.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const { CONFIG_DIR, getAppsConfigPath, getBaseUrl, getWidgetPublishConfigPath } = require('../lib/config');
|
|
3
|
+
const { CONFIG_DIR, getAppsConfigPath, getBaseUrl, getWidgetPublishConfigPath, getPerformanceConfigPath } = require('../lib/config');
|
|
4
4
|
const { TOKEN_FILE, showToken } = require('../lib/auth');
|
|
5
5
|
const { success, fail } = require('../lib/output');
|
|
6
6
|
|
|
@@ -11,6 +11,7 @@ async function run(args, opts) {
|
|
|
11
11
|
base_url: getBaseUrl(opts),
|
|
12
12
|
config_dir: CONFIG_DIR,
|
|
13
13
|
widget_publish_config_file: getWidgetPublishConfigPath(),
|
|
14
|
+
performance_config_file: getPerformanceConfigPath(),
|
|
14
15
|
token_file: TOKEN_FILE,
|
|
15
16
|
apps_config_file: getAppsConfigPath(),
|
|
16
17
|
token: showToken() || '',
|
package/src/commands/doc.js
CHANGED
|
@@ -4,7 +4,10 @@ const { readFileSync } = require('fs');
|
|
|
4
4
|
const { execFileSync } = require('child_process');
|
|
5
5
|
const { success, fail } = require('../lib/output');
|
|
6
6
|
|
|
7
|
-
const
|
|
7
|
+
const HTML_BOX_COMPONENT_TYPE_IDS = {
|
|
8
|
+
external: 'blk_6358a421bca0001c190a9805',
|
|
9
|
+
bytedance: 'blk_6900429af84180025ce76527',
|
|
10
|
+
};
|
|
8
11
|
|
|
9
12
|
async function run(args, opts) {
|
|
10
13
|
const sub = args[0];
|
|
@@ -16,14 +19,17 @@ async function run(args, opts) {
|
|
|
16
19
|
const html = readFileSync(opts.html, 'utf8');
|
|
17
20
|
const record = buildRecord(opts, html);
|
|
18
21
|
const identity = resolveIdentity(opts.as);
|
|
22
|
+
const { edition, componentTypeId } = resolveEdition(opts.edition);
|
|
19
23
|
const created = sub === 'append'
|
|
20
24
|
? { docToken: opts.docToken, url: `https://bytedance.larkoffice.com/docx/${opts.docToken}` }
|
|
21
25
|
: createDocument(opts.title, opts.summary, identity);
|
|
22
|
-
const htmlBoxBlockId = insertHtmlBox(created.docToken, record, identity);
|
|
26
|
+
const htmlBoxBlockId = insertHtmlBox(created.docToken, record, identity, componentTypeId);
|
|
23
27
|
|
|
24
28
|
success({
|
|
25
29
|
ok: true,
|
|
26
30
|
identity,
|
|
31
|
+
edition,
|
|
32
|
+
component_type_id: componentTypeId,
|
|
27
33
|
doc_token: created.docToken,
|
|
28
34
|
doc_url: created.url,
|
|
29
35
|
html_box_block_id: htmlBoxBlockId,
|
|
@@ -51,6 +57,16 @@ function resolveIdentity(requestedIdentity) {
|
|
|
51
57
|
return 'bot';
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
function resolveEdition(requestedEdition) {
|
|
61
|
+
const edition = requestedEdition || 'external';
|
|
62
|
+
if (!Object.prototype.hasOwnProperty.call(HTML_BOX_COMPONENT_TYPE_IDS, edition)) {
|
|
63
|
+
const error = new Error('--edition must be external or bytedance');
|
|
64
|
+
error.code = 'E_INVALID_ARGS';
|
|
65
|
+
throw error;
|
|
66
|
+
}
|
|
67
|
+
return { edition, componentTypeId: HTML_BOX_COMPONENT_TYPE_IDS[edition] };
|
|
68
|
+
}
|
|
69
|
+
|
|
54
70
|
function createDocument(title, summary, identity) {
|
|
55
71
|
const intro = summary || `这个妙笔应用以交互式网页的形式展示「${title}」,可直接在文档中查看和体验。`;
|
|
56
72
|
const content = `<title>${escapeXml(title)}</title><p>${escapeXml(intro)}</p>`;
|
|
@@ -73,7 +89,7 @@ function createDocument(title, summary, identity) {
|
|
|
73
89
|
};
|
|
74
90
|
}
|
|
75
91
|
|
|
76
|
-
function insertHtmlBox(docToken, recordObject, identity) {
|
|
92
|
+
function insertHtmlBox(docToken, recordObject, identity, componentTypeId) {
|
|
77
93
|
const record = JSON.stringify(recordObject);
|
|
78
94
|
const resp = runJson([
|
|
79
95
|
'api',
|
|
@@ -87,15 +103,18 @@ function insertHtmlBox(docToken, recordObject, identity) {
|
|
|
87
103
|
block_type: 40,
|
|
88
104
|
add_ons: {
|
|
89
105
|
component_id: '',
|
|
90
|
-
component_type_id:
|
|
106
|
+
component_type_id: componentTypeId,
|
|
91
107
|
record,
|
|
92
108
|
},
|
|
93
109
|
}],
|
|
94
110
|
index: -1,
|
|
95
111
|
}),
|
|
96
112
|
]);
|
|
97
|
-
if (resp
|
|
113
|
+
if (!isSuccessfulLarkResponse(resp)) throw new Error(`failed to insert HTML Box: ${JSON.stringify(resp)}`);
|
|
98
114
|
const block = resp?.data?.children?.[0];
|
|
115
|
+
if (block?.add_ons?.component_type_id && block.add_ons.component_type_id !== componentTypeId) {
|
|
116
|
+
throw new Error(`inserted HTML Box component type mismatch: ${JSON.stringify(block?.add_ons)}`);
|
|
117
|
+
}
|
|
99
118
|
const returnedRecord = parseRecordValue(block?.add_ons?.record);
|
|
100
119
|
if (!recordIncludes(recordObject, returnedRecord)) {
|
|
101
120
|
throw new Error(`inserted HTML Box record mismatch: ${JSON.stringify(block?.add_ons)}`);
|
|
@@ -103,6 +122,10 @@ function insertHtmlBox(docToken, recordObject, identity) {
|
|
|
103
122
|
return block?.block_id || '';
|
|
104
123
|
}
|
|
105
124
|
|
|
125
|
+
function isSuccessfulLarkResponse(resp) {
|
|
126
|
+
return resp?.ok === true || resp?.code === 0;
|
|
127
|
+
}
|
|
128
|
+
|
|
106
129
|
function runJson(commandArgs) {
|
|
107
130
|
const out = execFileSync('lark-cli', commandArgs, {
|
|
108
131
|
encoding: 'utf8',
|
|
@@ -149,4 +172,4 @@ function escapeXml(value) {
|
|
|
149
172
|
.replaceAll("'", ''');
|
|
150
173
|
}
|
|
151
174
|
|
|
152
|
-
module.exports = { run };
|
|
175
|
+
module.exports = { HTML_BOX_COMPONENT_TYPE_IDS, resolveEdition, isSuccessfulLarkResponse, run };
|
package/src/commands/page.js
CHANGED
|
@@ -17,7 +17,103 @@ async function run(args, opts) {
|
|
|
17
17
|
if (sub === 'list') return list(opts);
|
|
18
18
|
if (sub === 'export') return exportApps(opts);
|
|
19
19
|
if (sub === 'delete') return deletePage(opts);
|
|
20
|
-
|
|
20
|
+
if (sub === 'collaborators') return collaborators(args.slice(1), opts);
|
|
21
|
+
if (sub === 'access') return access(args.slice(1), opts);
|
|
22
|
+
fail('Usage: magic-builder page <publish|list|export|delete|collaborators|access>', 'E_INVALID_ARGS');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
async function authenticatedRequest(opts, endpoint, requestOpts = {}) {
|
|
26
|
+
let token;
|
|
27
|
+
try { token = await getToken(opts); } catch (e) { fail(e.message, 'E_NO_TOKEN', 2); }
|
|
28
|
+
try { return await request(`${getBaseUrl(opts)}${endpoint}`, { token, ...requestOpts }); }
|
|
29
|
+
catch (e) { failFromHttpError(e); }
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function requiredPageId(opts, command) {
|
|
33
|
+
const id = normalizeId(opts.id);
|
|
34
|
+
if (!id) fail(`Missing --id. Usage: magic-builder page ${command} --id <id>`, 'E_INVALID_ARGS');
|
|
35
|
+
return id;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function readJsonFile(file) {
|
|
39
|
+
if (!file) return null;
|
|
40
|
+
if (!fs.existsSync(file)) fail(`File not found: ${file}`, 'E_NOT_FOUND');
|
|
41
|
+
try { return JSON.parse(fs.readFileSync(file, 'utf8')); }
|
|
42
|
+
catch (e) { fail(`Invalid JSON file: ${e.message}`, 'E_INVALID_ARGS'); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function parseIds(value, pattern, label, allowEmpty = false) {
|
|
46
|
+
const raw = Array.isArray(value) ? value : String(value ?? '').split(/[,,;;\n\r\t|]/);
|
|
47
|
+
const values = Array.from(new Set(raw.map((item) => String(item || '').trim()).filter(Boolean)));
|
|
48
|
+
const invalid = values.filter((id) => !pattern.test(id));
|
|
49
|
+
if (invalid.length) fail(`Invalid ${label}: ${invalid.join(', ')}`, 'E_INVALID_ARGS');
|
|
50
|
+
if (!allowEmpty && !values.length) fail(`Missing ${label}`, 'E_INVALID_ARGS');
|
|
51
|
+
return values;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function collaborators(args, opts) {
|
|
55
|
+
const action = String(args[0] || '').toLowerCase();
|
|
56
|
+
const id = requiredPageId(opts, `collaborators ${action || '<list|add|remove|set>'}`);
|
|
57
|
+
const endpoint = `/api/html-box/${encodeURIComponent(id)}/collaborators`;
|
|
58
|
+
if (action === 'list') {
|
|
59
|
+
const res = await authenticatedRequest(opts, endpoint);
|
|
60
|
+
if (res.code !== 0) fail(res.msg || 'Collaborator query failed', 'E_COLLABORATORS_FAILED');
|
|
61
|
+
return success(res.data || res, opts);
|
|
62
|
+
}
|
|
63
|
+
if (!['add', 'remove', 'set'].includes(action)) fail('Usage: magic-builder page collaborators <list|add|remove|set> --id <id>', 'E_INVALID_ARGS');
|
|
64
|
+
const file = readJsonFile(opts.file);
|
|
65
|
+
const openIds = parseIds(file?.open_ids ?? file?.openIds ?? opts.openIds, /^ou_[A-Za-z0-9_-]+$/, 'open_id');
|
|
66
|
+
const res = await authenticatedRequest(opts, endpoint, { method: 'PATCH', body: { operation: action, open_ids: openIds } });
|
|
67
|
+
if (res.code !== 0) fail(res.msg || 'Collaborator update failed', 'E_COLLABORATORS_FAILED');
|
|
68
|
+
success(res.data || res, opts);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function parseBoolean(value, label) {
|
|
72
|
+
if (typeof value === 'boolean') return value;
|
|
73
|
+
const normalized = String(value ?? '').trim().toLowerCase();
|
|
74
|
+
if (['true', '1', 'yes', 'on', 'enabled'].includes(normalized)) return true;
|
|
75
|
+
if (['false', '0', 'no', 'off', 'disabled'].includes(normalized)) return false;
|
|
76
|
+
fail(`${label} must be true or false`, 'E_INVALID_ARGS');
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function access(args, opts) {
|
|
80
|
+
const action = String(args[0] || '').toLowerCase();
|
|
81
|
+
const id = requiredPageId(opts, `access ${action || '<get|enable|disable|require-login|users|groups|set>'}`);
|
|
82
|
+
const endpoint = `/api/html-box/${encodeURIComponent(id)}/access`;
|
|
83
|
+
if (action === 'get') {
|
|
84
|
+
const res = await authenticatedRequest(opts, endpoint);
|
|
85
|
+
if (res.code !== 0) fail(res.msg || 'Access query failed', 'E_ACCESS_FAILED');
|
|
86
|
+
return success(res.data || res, opts);
|
|
87
|
+
}
|
|
88
|
+
let body;
|
|
89
|
+
if (action === 'enable' || action === 'disable') {
|
|
90
|
+
body = { access_control_enabled: action === 'enable' };
|
|
91
|
+
} else if (action === 'require-login') {
|
|
92
|
+
if (opts.enabled === undefined) fail('Missing --enabled true|false', 'E_INVALID_ARGS');
|
|
93
|
+
body = { require_feishu_login: parseBoolean(opts.enabled, '--enabled') };
|
|
94
|
+
} else if (action === 'users' || action === 'groups') {
|
|
95
|
+
const operation = String(args[1] || '').toLowerCase();
|
|
96
|
+
if (!['add', 'remove', 'set'].includes(operation)) fail(`Usage: magic-builder page access ${action} <add|remove|set> --id <id>`, 'E_INVALID_ARGS');
|
|
97
|
+
const file = readJsonFile(opts.file);
|
|
98
|
+
const isUsers = action === 'users';
|
|
99
|
+
const source = isUsers ? (file?.open_ids ?? file?.user_open_ids ?? opts.openIds) : (file?.chat_ids ?? file?.group_chat_ids ?? opts.chatIds);
|
|
100
|
+
const ids = parseIds(source, isUsers ? /^ou_[A-Za-z0-9_-]+$/ : /^oc_[A-Za-z0-9_-]+$/, isUsers ? 'open_id' : 'chat_id', operation === 'set');
|
|
101
|
+
body = { operation, target: action, [isUsers ? 'access_users' : 'access_groups']: ids.map((value) => ({ id: value })) };
|
|
102
|
+
} else if (action === 'set') {
|
|
103
|
+
const file = readJsonFile(opts.file);
|
|
104
|
+
if (!file || typeof file !== 'object' || Array.isArray(file)) fail('Missing --file <access.json>', 'E_INVALID_ARGS');
|
|
105
|
+
body = {};
|
|
106
|
+
if (Object.prototype.hasOwnProperty.call(file, 'access_control_enabled')) body.access_control_enabled = parseBoolean(file.access_control_enabled, 'access_control_enabled');
|
|
107
|
+
if (Object.prototype.hasOwnProperty.call(file, 'require_feishu_login')) body.require_feishu_login = parseBoolean(file.require_feishu_login, 'require_feishu_login');
|
|
108
|
+
if (Object.prototype.hasOwnProperty.call(file, 'user_open_ids')) body.access_users = parseIds(file.user_open_ids, /^ou_[A-Za-z0-9_-]+$/, 'open_id', true).map((value) => ({ id: value }));
|
|
109
|
+
if (Object.prototype.hasOwnProperty.call(file, 'group_chat_ids')) body.access_groups = parseIds(file.group_chat_ids, /^oc_[A-Za-z0-9_-]+$/, 'chat_id', true).map((value) => ({ id: value }));
|
|
110
|
+
if (!Object.keys(body).length) fail('Access JSON contains no supported settings', 'E_INVALID_ARGS');
|
|
111
|
+
} else {
|
|
112
|
+
fail('Usage: magic-builder page access <get|enable|disable|require-login|users|groups|set> --id <id>', 'E_INVALID_ARGS');
|
|
113
|
+
}
|
|
114
|
+
const res = await authenticatedRequest(opts, endpoint, { method: 'PATCH', body });
|
|
115
|
+
if (res.code !== 0) fail(res.msg || 'Access update failed', 'E_ACCESS_FAILED');
|
|
116
|
+
success(res.data || res, opts);
|
|
21
117
|
}
|
|
22
118
|
|
|
23
119
|
async function publish(args, opts) {
|
|
@@ -39,7 +135,9 @@ async function publish(args, opts) {
|
|
|
39
135
|
const existingId = opts.id || config[relPath]?.id || config[relPath]?.remoteId;
|
|
40
136
|
|
|
41
137
|
const body = { html, title };
|
|
138
|
+
if (opts.customId !== undefined) body.custom_id = String(opts.customId).trim();
|
|
42
139
|
if (opts.openSource) body.is_open_source = true;
|
|
140
|
+
if (opts.comments !== undefined) body.comments_enabled = opts.comments === true || String(opts.comments).toLowerCase() !== 'false';
|
|
43
141
|
|
|
44
142
|
if (!opts.quiet) process.stderr.write(`${existingId ? 'Updating' : 'Publishing'} ${relPath}... `);
|
|
45
143
|
|
|
@@ -56,13 +154,17 @@ async function publish(args, opts) {
|
|
|
56
154
|
const data = res.data || res;
|
|
57
155
|
const id = normalizeId(data.id || data.record_id || existingId);
|
|
58
156
|
const htmlBoxUrl = data.html_box_url || `${baseUrl}/html-box/${id}`;
|
|
157
|
+
const appUrl = data.app_url || `${baseUrl}/app/${id}`;
|
|
158
|
+
const url = data.url || (data.runtime === 'html-box' ? htmlBoxUrl : appUrl);
|
|
59
159
|
const dashboardUrl = data.dashboard_url || `${baseUrl}/dashboard/${id}`;
|
|
60
160
|
|
|
61
161
|
config[relPath] = {
|
|
62
162
|
id,
|
|
63
163
|
remoteId: id,
|
|
64
164
|
title,
|
|
165
|
+
customId: opts.customId !== undefined ? String(opts.customId).trim() : (config[relPath]?.customId || ''),
|
|
65
166
|
urls: {
|
|
167
|
+
app: appUrl,
|
|
66
168
|
html_box: htmlBoxUrl,
|
|
67
169
|
dashboard: dashboardUrl,
|
|
68
170
|
panel: data.panel_url || '',
|
|
@@ -76,6 +178,10 @@ async function publish(args, opts) {
|
|
|
76
178
|
success({
|
|
77
179
|
id,
|
|
78
180
|
title,
|
|
181
|
+
custom_id: data.custom_id || body.custom_id || '',
|
|
182
|
+
url,
|
|
183
|
+
runtime: data.runtime || (url === htmlBoxUrl ? 'html-box' : 'app'),
|
|
184
|
+
app_url: appUrl,
|
|
79
185
|
html_box_url: htmlBoxUrl,
|
|
80
186
|
dashboard_url: dashboardUrl,
|
|
81
187
|
panel_url: data.panel_url || '',
|
|
@@ -273,4 +379,4 @@ function formatHtmlLimitMessage(html) {
|
|
|
273
379
|
return `HTML exceeds publish limit (${HTML_LIMIT} characters; ${HTML_CODE_FIELD_COUNT} HTML code fields x ${HTML_CODE_FIELD_LIMIT} characters). Current: ${html.length}; over by: ${overflow}. ${hint}`;
|
|
274
380
|
}
|
|
275
381
|
|
|
276
|
-
module.exports = { run };
|
|
382
|
+
module.exports = { run, parseIds, parseBoolean };
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const { existsSync, readFileSync } = require('fs');
|
|
4
4
|
const { resolve } = require('path');
|
|
5
5
|
const { success, fail } = require('../lib/output');
|
|
6
|
+
const { getPerformanceConfigPath, loadPerformanceConfig } = require('../lib/config');
|
|
6
7
|
|
|
7
8
|
const DEFAULTS = {
|
|
8
9
|
endpoint: 'https://people.bytedance.net/perf/api/foundation/v2/draft',
|
|
@@ -13,8 +14,8 @@ const DEFAULTS = {
|
|
|
13
14
|
|
|
14
15
|
async function run(args, opts) {
|
|
15
16
|
const action = args[0] || 'draft';
|
|
16
|
-
if (args.length > 1 || !['draft', 'submit'].includes(action)) {
|
|
17
|
-
fail('Usage: magic-builder performance [submit] --review-url <url> --cookie <content|file>', 'E_INVALID_ARGS');
|
|
17
|
+
if (args.length > 1 || !['draft', 'submit', 'self-review', 'key-works', 'review-users', 'invite-review'].includes(action)) {
|
|
18
|
+
fail('Usage: magic-builder performance [submit|self-review|key-works|review-users|invite-review] --review-url <url> --cookie <content|file>', 'E_INVALID_ARGS');
|
|
18
19
|
}
|
|
19
20
|
|
|
20
21
|
const cookie = readCookie(opts.cookie, opts.cookieFile);
|
|
@@ -23,7 +24,44 @@ async function run(args, opts) {
|
|
|
23
24
|
const csrf = getCookie(cookie, 'x-f-csrf');
|
|
24
25
|
if (!csrf) fail('Cookie does not contain x-f-csrf.', 'E_INVALID_ARGS');
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
if (action === 'key-works') {
|
|
28
|
+
const data = await getKeyWorks(opts, cookie, csrf);
|
|
29
|
+
success(data, opts);
|
|
30
|
+
return;
|
|
31
|
+
}
|
|
32
|
+
if (action === 'review-users') {
|
|
33
|
+
const data = await getReviewUsers(opts, cookie, csrf);
|
|
34
|
+
success(data, opts);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (action === 'invite-review') {
|
|
38
|
+
const payload = readInviteReviewPayload(opts);
|
|
39
|
+
const endpoint = String(opts.endpoint || DEFAULTS.endpoint);
|
|
40
|
+
if (opts.dryRun) {
|
|
41
|
+
success({ dryRun: true, action: 'invite-review', endpoint, payload }, opts);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const review = parseReviewLocation(opts.reviewUrl || opts.referer);
|
|
45
|
+
const data = await postPerformance(endpoint, payload, cookie, csrf, {
|
|
46
|
+
referer: String(opts.referer || review.referer || 'https://people.bytedance.net/performance/perf/review'),
|
|
47
|
+
tenantId: String(opts.tenantId || ''),
|
|
48
|
+
});
|
|
49
|
+
success(data, opts);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const config = await resolveConfig(opts, cookie, action === 'self-review' ? 'self_review' : 'confirm_invitation');
|
|
54
|
+
if (action === 'self-review') {
|
|
55
|
+
const updates = readSelfReviewInputs(opts);
|
|
56
|
+
const payload = buildSelfReviewPayloadFromDraft(config.draft, updates);
|
|
57
|
+
if (opts.dryRun) {
|
|
58
|
+
success({ dryRun: true, action: 'self-review', endpoint: config.endpoint, payload }, opts);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
const data = await postPerformance(config.endpoint, payload, cookie, csrf, config);
|
|
62
|
+
success(data, opts);
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
27
65
|
if (action === 'submit') {
|
|
28
66
|
const payload = buildStagePayloadFromDraft(config.draft, config);
|
|
29
67
|
if (opts.dryRun) {
|
|
@@ -53,6 +91,136 @@ async function run(args, opts) {
|
|
|
53
91
|
success(data, opts);
|
|
54
92
|
}
|
|
55
93
|
|
|
94
|
+
function readInviteReviewPayload(opts) {
|
|
95
|
+
if (opts.payload && opts.payloadFile) {
|
|
96
|
+
fail('Pass either --payload or --payload-file, not both.', 'E_INVALID_ARGS');
|
|
97
|
+
}
|
|
98
|
+
const input = opts.payloadFile
|
|
99
|
+
? readFileSync(resolve(String(opts.payloadFile)), 'utf8')
|
|
100
|
+
: String(opts.payload || '');
|
|
101
|
+
if (!input.trim()) fail('invite-review requires --payload <json> or --payload-file <file>.', 'E_INVALID_ARGS');
|
|
102
|
+
const payload = parseJson(input);
|
|
103
|
+
if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
104
|
+
fail('Invite-review payload must be a valid JSON object.', 'E_INVALID_ARGS');
|
|
105
|
+
}
|
|
106
|
+
const missing = ['key', 'data', 'version'].filter(key => payload[key] === undefined || payload[key] === null);
|
|
107
|
+
if (missing.length) fail(`Invite-review payload is missing ${missing.join(', ')}.`, 'E_INVALID_ARGS');
|
|
108
|
+
if (!String(payload.key).includes('__invite_review__')) {
|
|
109
|
+
fail('Invite-review payload key must contain __invite_review__.', 'E_INVALID_ARGS');
|
|
110
|
+
}
|
|
111
|
+
return normalizePerformancePayload(payload);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function normalizePerformancePayload(payload) {
|
|
115
|
+
const normalized = structuredClone(payload);
|
|
116
|
+
normalizePerformanceNode(normalized);
|
|
117
|
+
return normalized;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizePerformanceNode(node) {
|
|
121
|
+
if (!node || typeof node !== 'object') return;
|
|
122
|
+
if (Array.isArray(node)) {
|
|
123
|
+
node.forEach(normalizePerformanceNode);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
for (const [key, value] of Object.entries(node)) {
|
|
127
|
+
if (key === 'insert' && typeof value === 'string') {
|
|
128
|
+
node[key] = value.replace(/\\n(?=\r?\n|$)/g, '\n');
|
|
129
|
+
continue;
|
|
130
|
+
}
|
|
131
|
+
if (typeof value === 'string' && ['text', 'value', 'rich_text'].includes(key)) {
|
|
132
|
+
const embedded = parseJson(value);
|
|
133
|
+
if (embedded && typeof embedded === 'object') {
|
|
134
|
+
normalizePerformanceNode(embedded);
|
|
135
|
+
node[key] = JSON.stringify(embedded);
|
|
136
|
+
}
|
|
137
|
+
continue;
|
|
138
|
+
}
|
|
139
|
+
normalizePerformanceNode(value);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
async function getKeyWorks(opts, cookie, csrf) {
|
|
144
|
+
if (!opts.rootReviewId) {
|
|
145
|
+
fail('key-works requires --root-review-id <id>.', 'E_INVALID_ARGS');
|
|
146
|
+
}
|
|
147
|
+
const personalConfig = opts.performanceConfig || loadPerformanceConfig();
|
|
148
|
+
const templateId = String(opts.templateId || personalConfig.invite_review_template_id || personalConfig.inviteReviewTemplateId || '');
|
|
149
|
+
if (!templateId) {
|
|
150
|
+
fail(`Missing template id. Use --template-id <id> or set invite_review_template_id in ${getPerformanceConfigPath()}.`, 'E_INVALID_ARGS');
|
|
151
|
+
}
|
|
152
|
+
const endpoint = assertPeoplePerfEndpoint(opts.stageEndpoint || DEFAULTS.submitEndpoint);
|
|
153
|
+
endpoint.searchParams.set('template_id', templateId);
|
|
154
|
+
endpoint.searchParams.set('root_review_id', String(opts.rootReviewId));
|
|
155
|
+
const referer = String(opts.referer || opts.reviewUrl || 'https://people.bytedance.net/performance/perf/review');
|
|
156
|
+
const headers = {
|
|
157
|
+
accept: 'application/json, text/plain, */*',
|
|
158
|
+
'accept-language': 'zh-CN,zh;q=0.9',
|
|
159
|
+
cookie,
|
|
160
|
+
referer,
|
|
161
|
+
'x-f-csrf': csrf,
|
|
162
|
+
'x-f-lang': 'zh-CN',
|
|
163
|
+
'x-f-timezone': 'Asia/Shanghai',
|
|
164
|
+
};
|
|
165
|
+
if (opts.tenantId) headers['rpc-persist-lane-c-perfx-tenant-id'] = String(opts.tenantId);
|
|
166
|
+
const response = await fetch(endpoint, { method: 'GET', headers });
|
|
167
|
+
const text = await response.text();
|
|
168
|
+
const data = parseJson(text);
|
|
169
|
+
if (!response.ok) {
|
|
170
|
+
const error = new Error(data?.message || data?.msg || `Performance request failed: HTTP ${response.status}`);
|
|
171
|
+
error.code = response.status === 401 || response.status === 403 ? 'E_AUTH_FAILED' : 'E_REQUEST_FAILED';
|
|
172
|
+
throw error;
|
|
173
|
+
}
|
|
174
|
+
if (data === null) {
|
|
175
|
+
const error = new Error('Performance response is not valid JSON.');
|
|
176
|
+
error.code = 'E_REQUEST_FAILED';
|
|
177
|
+
throw error;
|
|
178
|
+
}
|
|
179
|
+
return data;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function getReviewUsers(opts, cookie, csrf) {
|
|
183
|
+
const location = opts.reviewUrl ? parseReviewLocation(opts.reviewUrl) : {};
|
|
184
|
+
const reviewId = String(opts.reviewId || location.reviewId || '');
|
|
185
|
+
const stageId = String(opts.stageId || location.formId || '');
|
|
186
|
+
if (!reviewId || !stageId) {
|
|
187
|
+
fail('review-users requires --review-url <url> or both --review-id <id> and --stage-id <id>.', 'E_INVALID_ARGS');
|
|
188
|
+
}
|
|
189
|
+
const base = assertPeoplePerfEndpoint(opts.reviewUsersEndpoint || DEFAULTS.submitEndpoint);
|
|
190
|
+
const endpoint = new URL(`${base.origin}/perf/api/review/v2/stage/${encodeURIComponent(reviewId)}/list_invite_review`);
|
|
191
|
+
const referer = String(opts.referer || location.referer || `https://people.bytedance.net/performance/perf/review/${reviewId}/${stageId}/members`);
|
|
192
|
+
const headers = {
|
|
193
|
+
accept: 'application/json, text/plain, */*',
|
|
194
|
+
'accept-language': 'zh-CN,zh;q=0.9',
|
|
195
|
+
'content-type': 'application/json;charset=UTF-8',
|
|
196
|
+
cookie,
|
|
197
|
+
origin: 'https://people.bytedance.net',
|
|
198
|
+
referer,
|
|
199
|
+
'x-f-csrf': csrf,
|
|
200
|
+
'x-f-lang': 'zh-CN',
|
|
201
|
+
'x-f-timezone': 'Asia/Shanghai',
|
|
202
|
+
};
|
|
203
|
+
if (opts.tenantId) headers['rpc-persist-lane-c-perfx-tenant-id'] = String(opts.tenantId);
|
|
204
|
+
const response = await fetch(endpoint, {
|
|
205
|
+
method: 'POST',
|
|
206
|
+
headers,
|
|
207
|
+
body: JSON.stringify({ stage_id: stageId }),
|
|
208
|
+
});
|
|
209
|
+
const text = await response.text();
|
|
210
|
+
const data = parseJson(text);
|
|
211
|
+
if (!response.ok) {
|
|
212
|
+
const error = new Error(data?.message || data?.msg || `Performance request failed: HTTP ${response.status}`);
|
|
213
|
+
error.code = response.status === 401 || response.status === 403 ? 'E_AUTH_FAILED' : 'E_REQUEST_FAILED';
|
|
214
|
+
throw error;
|
|
215
|
+
}
|
|
216
|
+
if (data === null) {
|
|
217
|
+
const error = new Error('Performance response is not valid JSON.');
|
|
218
|
+
error.code = 'E_REQUEST_FAILED';
|
|
219
|
+
throw error;
|
|
220
|
+
}
|
|
221
|
+
return data;
|
|
222
|
+
}
|
|
223
|
+
|
|
56
224
|
async function postPerformance(endpoint, payload, cookie, csrf, config) {
|
|
57
225
|
assertPeoplePerfEndpoint(endpoint);
|
|
58
226
|
const response = await fetch(endpoint, {
|
|
@@ -129,14 +297,14 @@ function getCookie(cookie, name) {
|
|
|
129
297
|
return '';
|
|
130
298
|
}
|
|
131
299
|
|
|
132
|
-
async function resolveConfig(opts, cookie) {
|
|
300
|
+
async function resolveConfig(opts, cookie, draftKind = 'confirm_invitation') {
|
|
133
301
|
const review = parseReviewLocation(opts.reviewUrl || opts.referer, opts.reviewId, opts.formId);
|
|
134
302
|
if (review.reviewId && review.formId) {
|
|
135
303
|
const settings = await requestJson(opts.settingsEndpoint || DEFAULTS.settingsEndpoint, cookie, review.referer);
|
|
136
304
|
const operatorId = String(opts.operatorId || settings?.data?.user?.id || '');
|
|
137
305
|
const tenantId = String(opts.tenantId || settings?.data?.tenant?.id || '');
|
|
138
306
|
if (!operatorId) fail('Unable to resolve operator id from /perf/api/user/settings.', 'E_REQUEST_FAILED');
|
|
139
|
-
const key =
|
|
307
|
+
const key = buildDraftKey(review.reviewId, draftKind, operatorId, review.formId);
|
|
140
308
|
const draftUrl = new URL(opts.draftEndpoint || DEFAULTS.draftEndpoint);
|
|
141
309
|
draftUrl.searchParams.set('key', key);
|
|
142
310
|
const draftResponse = await requestJson(draftUrl.toString(), cookie, review.referer);
|
|
@@ -182,6 +350,36 @@ async function resolveConfig(opts, cookie) {
|
|
|
182
350
|
};
|
|
183
351
|
}
|
|
184
352
|
|
|
353
|
+
function buildDraftKey(reviewId, draftKind, operatorId, formId) {
|
|
354
|
+
return `${reviewId}__${draftKind}__${operatorId}__${formId}`;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function readSelfReviewInputs(opts) {
|
|
358
|
+
const inputs = [
|
|
359
|
+
['good', 'goodFile'],
|
|
360
|
+
['improve', 'improveFile'],
|
|
361
|
+
['valuesComment', 'valuesCommentFile'],
|
|
362
|
+
];
|
|
363
|
+
const updates = {};
|
|
364
|
+
for (const [valueKey, fileKey] of inputs) {
|
|
365
|
+
const hasValue = Object.prototype.hasOwnProperty.call(opts, valueKey);
|
|
366
|
+
const hasFile = Object.prototype.hasOwnProperty.call(opts, fileKey);
|
|
367
|
+
if (hasValue && hasFile) {
|
|
368
|
+
fail(`Pass either --${toKebab(valueKey)} or --${toKebab(fileKey)}, not both.`, 'E_INVALID_ARGS');
|
|
369
|
+
}
|
|
370
|
+
if (hasFile) updates[valueKey] = readFileSync(resolve(String(opts[fileKey])), 'utf8');
|
|
371
|
+
else if (hasValue) updates[valueKey] = String(opts[valueKey]);
|
|
372
|
+
}
|
|
373
|
+
if (!Object.keys(updates).length) {
|
|
374
|
+
fail('Pass at least one of --good, --improve, or --values-comment (or the matching --*-file option).', 'E_INVALID_ARGS');
|
|
375
|
+
}
|
|
376
|
+
return updates;
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
function toKebab(value) {
|
|
380
|
+
return String(value).replace(/[A-Z]/g, letter => `-${letter.toLowerCase()}`);
|
|
381
|
+
}
|
|
382
|
+
|
|
185
383
|
function parseReviewLocation(value, reviewId, formId) {
|
|
186
384
|
if (reviewId && formId) {
|
|
187
385
|
return {
|
|
@@ -295,6 +493,42 @@ function buildPayloadFromDraft(markdown, draft, config) {
|
|
|
295
493
|
};
|
|
296
494
|
}
|
|
297
495
|
|
|
496
|
+
function buildSelfReviewPayloadFromDraft(draft, updates) {
|
|
497
|
+
const snapshot = structuredClone(draft);
|
|
498
|
+
const units = snapshot?.data?.units;
|
|
499
|
+
if (!Array.isArray(units)) throw selfReviewSchemaError('Current self-review draft does not contain data.units.');
|
|
500
|
+
const fields = units.flatMap(unit => Array.isArray(unit?.fields) ? unit.fields : []);
|
|
501
|
+
const textFields = fields.filter(field => field?.entityType === 'text');
|
|
502
|
+
const tagTextFields = fields.filter(field => field?.entityType === 'tag_text');
|
|
503
|
+
if (textFields.length !== 2 || tagTextFields.length !== 1) {
|
|
504
|
+
throw selfReviewSchemaError('Unable to identify the self-review text fields from the current template.');
|
|
505
|
+
}
|
|
506
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'good')) {
|
|
507
|
+
textFields[0].value = selfReviewTextValue(updates.good);
|
|
508
|
+
}
|
|
509
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'improve')) {
|
|
510
|
+
textFields[1].value = selfReviewTextValue(updates.improve);
|
|
511
|
+
}
|
|
512
|
+
if (Object.prototype.hasOwnProperty.call(updates, 'valuesComment')) {
|
|
513
|
+
tagTextFields[0].json_value = selfReviewTextValue(updates.valuesComment);
|
|
514
|
+
}
|
|
515
|
+
return {
|
|
516
|
+
key: snapshot.key,
|
|
517
|
+
data: snapshot.data,
|
|
518
|
+
version: Number(snapshot.version),
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function selfReviewSchemaError(message) {
|
|
523
|
+
const error = new Error(message);
|
|
524
|
+
error.code = 'E_REQUEST_FAILED';
|
|
525
|
+
return error;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
function selfReviewTextValue(text) {
|
|
529
|
+
return { '0': { ops: markdownToDelta(String(text)), zoneId: '0', zoneType: 'Z' } };
|
|
530
|
+
}
|
|
531
|
+
|
|
298
532
|
function buildStagePayloadFromDraft(draft, config = {}) {
|
|
299
533
|
if (!draft?.data?.keyWorks) fail('Current draft does not contain keyWorks.', 'E_REQUEST_FAILED');
|
|
300
534
|
const keyWorks = structuredClone(draft.data.keyWorks);
|
|
@@ -445,4 +679,4 @@ function parseJson(text) {
|
|
|
445
679
|
try { return text ? JSON.parse(text) : {}; } catch (_) { return null; }
|
|
446
680
|
}
|
|
447
681
|
|
|
448
|
-
module.exports = { run, readCookie, normalizeCookie, getCookie, parseReviewLocation, splitMarkdownSections, markdownToDelta, buildPayload, buildPayloadFromDraft, buildStagePayloadFromDraft, assertPeoplePerfEndpoint };
|
|
682
|
+
module.exports = { run, getKeyWorks, getReviewUsers, readInviteReviewPayload, normalizePerformancePayload, readCookie, normalizeCookie, getCookie, readSelfReviewInputs, buildDraftKey, parseReviewLocation, splitMarkdownSections, markdownToDelta, buildPayload, buildPayloadFromDraft, buildSelfReviewPayloadFromDraft, buildStagePayloadFromDraft, assertPeoplePerfEndpoint };
|
package/src/commands/skill.js
CHANGED
|
@@ -24,7 +24,7 @@ async function run(args, opts) {
|
|
|
24
24
|
if (sub !== 'check-update' && sub !== 'update' && sub !== 'install') {
|
|
25
25
|
fail('Usage: magic-builder skill <check-update|update|install>', 'E_INVALID_ARGS');
|
|
26
26
|
}
|
|
27
|
-
const skillsRoot = path.resolve(opts.skillsRoot ||
|
|
27
|
+
const skillsRoot = path.resolve(opts.skillsRoot || defaultSkillsRoot());
|
|
28
28
|
const environment = resolveEnvironment(String(opts.environment || 'auto'), skillsRoot);
|
|
29
29
|
const remote = await prepareRemotePackage();
|
|
30
30
|
const report = buildReport(remote, skillsRoot, environment);
|
|
@@ -106,7 +106,7 @@ function buildCloudUpdateDescriptor(report, options = {}) {
|
|
|
106
106
|
}] : [],
|
|
107
107
|
instructions: [
|
|
108
108
|
'Use the cloud runtime managed skill update/install mechanism.',
|
|
109
|
-
'Do not copy files into ~/.
|
|
109
|
+
'Do not copy files into ~/.agents/skills or overwrite local filesystem paths in cloud mode.',
|
|
110
110
|
'If no managed cloud updater is exposed, report this descriptor and do not claim the update was applied.',
|
|
111
111
|
],
|
|
112
112
|
};
|
|
@@ -189,4 +189,8 @@ function ensureSafeSkillId(id) {
|
|
|
189
189
|
if (!/^[a-z0-9][a-z0-9-]*$/i.test(id)) throw new Error(`Unsafe skill id in update package: ${id}`);
|
|
190
190
|
}
|
|
191
191
|
|
|
192
|
-
|
|
192
|
+
function defaultSkillsRoot(homeDir = os.homedir()) {
|
|
193
|
+
return path.join(homeDir, '.agents', 'skills');
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
module.exports = { run, defaultSkillsRoot };
|
package/src/lib/config.js
CHANGED
|
@@ -9,6 +9,7 @@ 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
11
|
const WIDGET_PUBLISH_CONFIG_FILE = 'widget-publish.json';
|
|
12
|
+
const PERFORMANCE_CONFIG_FILE = 'performance.json';
|
|
12
13
|
|
|
13
14
|
function normalizeMagicBaseUrl(value) {
|
|
14
15
|
const raw = String(value || DEFAULT_BASE_URL).trim().replace(/\/+$/, '');
|
|
@@ -28,6 +29,16 @@ function getWidgetPublishConfigPath() {
|
|
|
28
29
|
return path.join(CONFIG_DIR, WIDGET_PUBLISH_CONFIG_FILE);
|
|
29
30
|
}
|
|
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
|
+
|
|
31
42
|
function loadAppsConfig() {
|
|
32
43
|
const p = fs.existsSync(getAppsConfigPath())
|
|
33
44
|
? getAppsConfigPath()
|
|
@@ -50,6 +61,8 @@ module.exports = {
|
|
|
50
61
|
getBaseUrl,
|
|
51
62
|
getAppsConfigPath,
|
|
52
63
|
getWidgetPublishConfigPath,
|
|
64
|
+
getPerformanceConfigPath,
|
|
65
|
+
loadPerformanceConfig,
|
|
53
66
|
loadAppsConfig,
|
|
54
67
|
saveAppsConfig,
|
|
55
68
|
};
|
package/src/lib/help.js
CHANGED
|
@@ -15,7 +15,7 @@ SYNTAX:
|
|
|
15
15
|
|
|
16
16
|
COMMANDS:
|
|
17
17
|
auth Login and manage Magic developer tokens
|
|
18
|
-
page Publish
|
|
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
|
|
@@ -39,6 +39,8 @@ EXAMPLES:
|
|
|
39
39
|
magic-builder page list --scope mine
|
|
40
40
|
magic-builder page export --id recxxx --out app.html
|
|
41
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
|
|
42
44
|
magic-builder faas publish handler.js --name report-api
|
|
43
45
|
magic-builder faas list
|
|
44
46
|
magic-builder faas delete --id recxxx
|
|
@@ -85,11 +87,19 @@ COMMANDS:
|
|
|
85
87
|
auth set <token>
|
|
86
88
|
auth show
|
|
87
89
|
|
|
88
|
-
page publish <html> --title <title> [--id <id>] [--open-source]
|
|
90
|
+
page publish <html> --title <title> [--id <id>] [--custom-id <id>] [--open-source] [--comments]
|
|
89
91
|
page list [--title <keyword>] [--scope mine|public]
|
|
90
92
|
page export --id <id> --out <file|dir> [--scope mine|public]
|
|
91
93
|
page export --title <keyword> --out <dir> [--all] [--scope mine|public]
|
|
92
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>
|
|
93
103
|
|
|
94
104
|
faas publish <file.js> --name <name> [--id <id>]
|
|
95
105
|
faas publish --code <code> --name <name> [--id <id>]
|
|
@@ -103,14 +113,20 @@ COMMANDS:
|
|
|
103
113
|
link create --title <text>
|
|
104
114
|
link create --fid <id>
|
|
105
115
|
|
|
106
|
-
doc create --html <file> --title <title> [--summary <text>] [--as bot|user]
|
|
107
|
-
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]
|
|
108
118
|
|
|
109
119
|
feedback create --feedback <text> [--title <title>] [--summary <text>]
|
|
110
120
|
feedback create --feedback-file <file> [--dry-run]
|
|
111
121
|
|
|
112
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]
|
|
113
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]
|
|
114
130
|
perf --review-url <url> --cookie-file <file> --markdown-file <file> [--dry-run]
|
|
115
131
|
|
|
116
132
|
widget-publish --app-id <id> --block-type-id <id> [--version <x.y.z>] [--change-log <text>]
|
|
@@ -161,11 +177,19 @@ SYNTAX:
|
|
|
161
177
|
page: `@HELP magic-builder/page
|
|
162
178
|
|
|
163
179
|
SYNTAX:
|
|
164
|
-
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]
|
|
165
181
|
magic-builder page list [--title <keyword>] [--scope mine|public]
|
|
166
182
|
magic-builder page export --id <id> --out <file|dir> [--scope mine|public]
|
|
167
183
|
magic-builder page export --title <keyword> --out <dir> [--all] [--scope mine|public]
|
|
168
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>
|
|
169
193
|
`,
|
|
170
194
|
faas: `@HELP magic-builder/faas
|
|
171
195
|
|
|
@@ -191,8 +215,8 @@ SYNTAX:
|
|
|
191
215
|
doc: `@HELP magic-builder/doc
|
|
192
216
|
|
|
193
217
|
SYNTAX:
|
|
194
|
-
magic-builder doc create --html <file> --title <title> [--summary <text>] [--as bot|user]
|
|
195
|
-
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]
|
|
196
220
|
`,
|
|
197
221
|
feedback: `@HELP magic-builder/feedback
|
|
198
222
|
|
|
@@ -204,8 +228,14 @@ SYNTAX:
|
|
|
204
228
|
|
|
205
229
|
SYNTAX:
|
|
206
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>
|
|
207
233
|
magic-builder perf submit --review-url <url> --cookie-file <file> --template-group-id <id> --dry-run
|
|
208
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]
|
|
209
239
|
magic-builder perf --review-url <url> --cookie-file <file> --markdown-file <file>
|
|
210
240
|
|
|
211
241
|
COOKIE:
|
|
@@ -215,9 +245,38 @@ DRAFT:
|
|
|
215
245
|
--review-url resolves operator, tenant, form ids, and the current version
|
|
216
246
|
from /perf/api/user/settings and /perf/api/foundation/draft.
|
|
217
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
|
+
|
|
218
271
|
OPTIONS:
|
|
219
272
|
--dry-run Print the generated payload without sending it
|
|
220
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
|
|
221
280
|
--template-group-id <id> Template group id (only needed when absent from the draft)
|
|
222
281
|
--submit-endpoint <url> Override the formal submission endpoint
|
|
223
282
|
--yes Confirm the irreversible formal submission
|
|
@@ -230,6 +289,12 @@ OPTIONS:
|
|
|
230
289
|
--source-id <id> Override the form source id
|
|
231
290
|
--field-source-id <id> Override the writable field source id
|
|
232
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
|
|
233
298
|
--endpoint <url> Override the draft API endpoint
|
|
234
299
|
`,
|
|
235
300
|
perf: `@HELP magic-builder/performance
|
package/src/lib/multipart.js
CHANGED
|
@@ -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,
|