openyida 2026.9.4 → 2026.9.7

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 (63) hide show
  1. package/README.md +7 -6
  2. package/bin/yida.js +1 -1
  3. package/lib/app/app-list.js +78 -53
  4. package/lib/app/canvas-compile.js +147 -0
  5. package/lib/app/page-linter.js +24 -0
  6. package/lib/app/publish.js +14 -1
  7. package/lib/app/services/canvas-page-schema-builder.js +175 -0
  8. package/lib/app/update-app.js +8 -2
  9. package/lib/bridge/bridge.js +15 -3
  10. package/lib/connector/api.js +3 -1
  11. package/lib/connector/connector-add-action.js +2 -0
  12. package/lib/connector/connector-create-connection.js +119 -16
  13. package/lib/connector/connector-create.js +40 -32
  14. package/lib/connector/connector-detail.js +2 -1
  15. package/lib/connector/connector-gen-template.js +5 -17
  16. package/lib/connector/connector-list-connections.js +5 -4
  17. package/lib/connector/connector-test.js +48 -2
  18. package/lib/connector/contract.js +132 -10
  19. package/lib/connector/operation-normalizer.js +20 -4
  20. package/lib/connector/yida-system-token.js +280 -0
  21. package/lib/core/agent-capabilities.js +24 -2
  22. package/lib/core/command-manifest.js +8 -8
  23. package/lib/core/locales/en.js +43 -2
  24. package/lib/core/locales/zh.js +43 -2
  25. package/lib/core/redact.js +4 -3
  26. package/lib/integration/connector-presets.js +95 -9
  27. package/lib/integration/integration-connector-schema.js +79 -15
  28. package/lib/integration/integration-create.js +131 -5
  29. package/lib/integration/integration-readback.js +136 -0
  30. package/lib/process/configure-process.js +69 -16
  31. package/package.json +1 -1
  32. package/scripts/postinstall.js +1 -1
  33. package/yida-skills/SKILL.md +3 -1
  34. package/yida-skills/references/resource-context.md +1 -1
  35. package/yida-skills/skills/yida-app/workflow/step-1-resource-context.md +1 -1
  36. package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
  37. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +4 -3
  38. package/yida-skills/skills/yida-app/workflow/step-7-page-code.md +3 -1
  39. package/yida-skills/skills/yida-app/workflow/step-8-publish-navigation.md +2 -0
  40. package/yida-skills/skills/yida-app/workflow/step-9-output-finish.md +19 -3
  41. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +25 -22
  42. package/yida-skills/skills/yida-canvas-custom-page/references/canvas-authoring-examples.md +19 -18
  43. package/yida-skills/skills/yida-canvas-custom-page/references/data-bridge-guide.md +41 -37
  44. package/yida-skills/skills/yida-canvas-custom-page/references/employeefield-verification.md +2 -2
  45. package/yida-skills/skills/yida-canvas-custom-page/references/native-components-bridge.md +1 -1
  46. package/yida-skills/skills/yida-canvas-data-binding/SKILL.md +3 -3
  47. package/yida-skills/skills/yida-canvas-data-binding/references/connector-binding.md +23 -0
  48. package/yida-skills/skills/yida-connector/SKILL.md +15 -8
  49. package/yida-skills/skills/yida-connector/examples/operations-search-formdata-v2.json +2 -2
  50. package/yida-skills/skills/yida-connector/references/connector-action-format.md +9 -0
  51. package/yida-skills/skills/yida-connector/templates/api-document-template.md +6 -19
  52. package/yida-skills/skills/yida-data-management/SKILL.md +8 -4
  53. package/yida-skills/skills/yida-design/SKILL.md +1 -1
  54. package/yida-skills/skills/yida-design/workflow/output-design.md +2 -0
  55. package/yida-skills/skills/yida-design/workflow/step-2-theme-system.md +4 -3
  56. package/yida-skills/skills/yida-dingtalk-openapi/SKILL.md +75 -0
  57. package/yida-skills/skills/yida-dingtalk-openapi/references/api-contract.md +98 -0
  58. package/yida-skills/skills/yida-dingtalk-openapi/references/connector-mapping.md +27 -0
  59. package/yida-skills/skills/yida-integration/SKILL.md +29 -0
  60. package/yida-skills/skills/yida-publish-page/SKILL.md +3 -2
  61. package/yida-skills/skills/yida-requirement-analysis/SKILL.md +2 -1
  62. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +3 -0
  63. package/yida-skills/skills-index.json +54 -0
@@ -269,14 +269,187 @@ function openyidaCreateUtilsBridge(context, yidaBridge) {
269
269
  return bridge;
270
270
  }
271
271
 
272
+ function openyidaConnectorCsrfToken() {
273
+ var targets = openyidaYidaApiBridgeTargets();
274
+ for (var i = 0; i < targets.length; i += 1) {
275
+ try {
276
+ var config = targets[i] && targets[i].g_config;
277
+ if (config && config._csrf_token) {
278
+ return String(config._csrf_token);
279
+ }
280
+ } catch (err) {}
281
+ }
282
+ return '';
283
+ }
284
+
285
+ function openyidaConnectorBridgeError(code, message) {
286
+ var error = new Error(message);
287
+ error.code = code;
288
+ return error;
289
+ }
290
+
291
+ function openyidaResolveConnectorName(binding) {
292
+ var connectorName = binding && binding.connectorName ? String(binding.connectorName) : '';
293
+ var legacyConnectorId = binding && binding.connectorId ? String(binding.connectorId) : '';
294
+ if (!connectorName && legacyConnectorId.indexOf('Http_') === 0) {
295
+ connectorName = legacyConnectorId;
296
+ }
297
+ if (!connectorName || connectorName.indexOf('Http_') !== 0) {
298
+ throw openyidaConnectorBridgeError(
299
+ 'CONNECTOR_RUNTIME_NAME_REQUIRED',
300
+ 'connectorName must be the internal Http_* connector name; numeric connectorId is only for CLI management'
301
+ );
302
+ }
303
+ if (legacyConnectorId.indexOf('Http_') === 0 && legacyConnectorId !== connectorName) {
304
+ throw openyidaConnectorBridgeError(
305
+ 'CONNECTOR_RUNTIME_IDENTITY_MISMATCH',
306
+ 'connectorName and legacy connectorId refer to different connectors'
307
+ );
308
+ }
309
+ return connectorName;
310
+ }
311
+
312
+ function openyidaParseConnectorJson(value) {
313
+ if (typeof value !== 'string') { return value; }
314
+ try { return JSON.parse(value); } catch (err) { return value; }
315
+ }
316
+
317
+ function openyidaConnectorErrorMessage(value, depth) {
318
+ if (depth > 4 || value === null || value === undefined) { return ''; }
319
+ var parsed = openyidaParseConnectorJson(value);
320
+ if (typeof parsed === 'string') { return parsed; }
321
+ if (typeof parsed !== 'object') { return ''; }
322
+ var directKeys = ['errorMsg', 'errorMessage', 'message'];
323
+ for (var i = 0; i < directKeys.length; i += 1) {
324
+ var direct = parsed[directKeys[i]];
325
+ if (direct !== undefined && direct !== null && String(direct).trim()) {
326
+ return String(direct);
327
+ }
328
+ }
329
+ if (parsed.error && typeof parsed.error === 'object') {
330
+ var nestedError = openyidaConnectorErrorMessage(parsed.error, depth + 1);
331
+ if (nestedError) { return nestedError; }
332
+ }
333
+ if (Array.isArray(parsed.errors) && parsed.errors.length > 0) {
334
+ var listedError = openyidaConnectorErrorMessage(parsed.errors[0], depth + 1);
335
+ if (listedError) { return listedError; }
336
+ }
337
+ var nestedKeys = ['content', 'serviceReturnValue', 'result', 'data'];
338
+ for (var j = 0; j < nestedKeys.length; j += 1) {
339
+ if (parsed[nestedKeys[j]] !== undefined && parsed[nestedKeys[j]] !== null) {
340
+ var nested = openyidaConnectorErrorMessage(parsed[nestedKeys[j]], depth + 1);
341
+ if (nested) { return nested; }
342
+ }
343
+ }
344
+ return '';
345
+ }
346
+
347
+ function openyidaConnectorPayloadFailed(value) {
348
+ var parsed = openyidaParseConnectorJson(value);
349
+ return !!(parsed && typeof parsed === 'object'
350
+ && (parsed.success === false || parsed.hasError === true));
351
+ }
352
+
353
+ function openyidaNormalizeConnectorInputs(inputs) {
354
+ var source = inputs && typeof inputs === 'object' ? inputs : {};
355
+ var body = Object.prototype.hasOwnProperty.call(source, 'body') ? source.body : {};
356
+ if (body === null || body === undefined) { body = {}; }
357
+ if (typeof body !== 'object' || Array.isArray(body)) {
358
+ throw openyidaConnectorBridgeError(
359
+ 'CONNECTOR_BODY_OBJECT_REQUIRED',
360
+ 'connector inputs.body must be an object; pass the value directly instead of JSON.stringify(...)'
361
+ );
362
+ }
363
+ return {
364
+ path: source.path && typeof source.path === 'object' ? source.path : {},
365
+ query: source.query && typeof source.query === 'object' ? source.query : {},
366
+ header: source.header && typeof source.header === 'object' ? source.header : {},
367
+ body: body
368
+ };
369
+ }
370
+
371
+ function openyidaCreateConnectorApiBridge() {
372
+ function invoke(binding, inputs) {
373
+ if (!binding || !binding.operationId) {
374
+ return Promise.reject(openyidaConnectorBridgeError(
375
+ 'CONNECTOR_RUNTIME_IDENTITY_REQUIRED',
376
+ 'connectorName and operationId are required'
377
+ ));
378
+ }
379
+ var connectorName;
380
+ var normalizedInputs;
381
+ try {
382
+ connectorName = openyidaResolveConnectorName(binding);
383
+ normalizedInputs = openyidaNormalizeConnectorInputs(inputs);
384
+ } catch (error) {
385
+ return Promise.reject(error);
386
+ }
387
+ var connectorInfo = {
388
+ connectorId: connectorName,
389
+ actionId: String(binding.operationId),
390
+ type: 'httpConnector'
391
+ };
392
+ if (binding.connectionId !== undefined && binding.connectionId !== null && binding.connectionId !== '') {
393
+ connectorInfo.connection = String(binding.connectionId);
394
+ }
395
+ var csrfToken = openyidaConnectorCsrfToken();
396
+ var endpoint = '/query/publicService/invokeService.json';
397
+ if (csrfToken) {
398
+ endpoint += '?_csrf_token=' + encodeURIComponent(csrfToken);
399
+ }
400
+ var body = 'inputs=' + encodeURIComponent(JSON.stringify(normalizedInputs))
401
+ + '&serviceInfo=' + encodeURIComponent(JSON.stringify({ connectorInfo: connectorInfo }));
402
+ var headers = { 'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8' };
403
+ if (csrfToken) {
404
+ headers.global_csrf_token = csrfToken;
405
+ }
406
+ if (typeof window === 'undefined' || typeof window.fetch !== 'function') {
407
+ return Promise.reject(new Error('window.fetch is not available'));
408
+ }
409
+ return window.fetch(endpoint, {
410
+ method: 'POST',
411
+ credentials: 'include',
412
+ headers: headers,
413
+ body: body
414
+ }).then(function(response) {
415
+ if (!response || response.ok === false || typeof response.json !== 'function') {
416
+ throw new Error('connector invocation HTTP request failed');
417
+ }
418
+ return response.json();
419
+ }).then(function(payload) {
420
+ if (!payload || openyidaConnectorPayloadFailed(payload)) {
421
+ throw openyidaConnectorBridgeError(
422
+ 'CONNECTOR_INVOCATION_FAILED',
423
+ openyidaConnectorErrorMessage(payload, 0) || 'connector invocation failed'
424
+ );
425
+ }
426
+ var value = payload.content && Object.prototype.hasOwnProperty.call(payload.content, 'serviceReturnValue')
427
+ ? payload.content.serviceReturnValue
428
+ : payload.content;
429
+ value = openyidaParseConnectorJson(value);
430
+ if (openyidaConnectorPayloadFailed(value)) {
431
+ throw openyidaConnectorBridgeError(
432
+ 'CONNECTOR_SERVICE_FAILED',
433
+ openyidaConnectorErrorMessage(value, 0) || 'connector service returned failure'
434
+ );
435
+ }
436
+ return value;
437
+ });
438
+ }
439
+ return { ready: true, invoke: invoke };
440
+ }
441
+
272
442
  function openyidaInstallYidaApiBridge() {
273
443
  var bridge = openyidaCreateYidaApiBridge(this);
274
444
  var utilsBridge = openyidaCreateUtilsBridge(this, bridge);
445
+ var connectorBridge = openyidaCreateConnectorApiBridge();
275
446
  try {
276
447
  window.__OPENYIDA_YIDA_API__ = bridge;
277
448
  window.openyidaYidaApi = bridge;
278
449
  window.__OPENYIDA_UTILS__ = utilsBridge;
279
450
  window.openyidaUtils = utilsBridge;
451
+ window.__OPENYIDA_CONNECTOR_API__ = connectorBridge;
452
+ window.openyidaConnectorApi = connectorBridge;
280
453
  } catch (err) {}
281
454
  openyidaYidaApiBridgeTargets().forEach(function(target) {
282
455
  try {
@@ -284,6 +457,8 @@ function openyidaInstallYidaApiBridge() {
284
457
  target.openyidaYidaApi = bridge;
285
458
  target.__OPENYIDA_UTILS__ = utilsBridge;
286
459
  target.openyidaUtils = utilsBridge;
460
+ target.__OPENYIDA_CONNECTOR_API__ = connectorBridge;
461
+ target.openyidaConnectorApi = connectorBridge;
287
462
  } catch (err) {}
288
463
  });
289
464
  return bridge;
@@ -294,6 +294,8 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
294
294
 
295
295
  const currentThemeColor = pickAppField(currentApp, 'themeColor', 'THEME_COLOR');
296
296
  const currentCustomThemeStyle = pickAppField(currentApp, 'customThemeStyle', 'CUSTOM_THEME_STYLE');
297
+ const addWaterMark = pickAppField(currentApp, 'addWaterMark', 'ADDWATERMARK');
298
+ const sentryMode = pickAppField(currentApp, 'sentryMode', 'SENTRY_MODE');
297
299
  if (params.colour === 'custom' && !params.themeColor && !currentThemeColor) {
298
300
  throw new Error(t('update_app.custom_theme_color_required'));
299
301
  }
@@ -342,10 +344,14 @@ function buildUpdateAppPostData(params, currentApp = {}, authRef) {
342
344
  navigation: currentApp.navigation || (currentApp.config && currentApp.config.NAVIGATION) || 'TODO,DONE,SUBMIT',
343
345
  pageHeader: currentApp.pageHeader || '',
344
346
  pageFooter: currentApp.pageFooter || '',
345
- addWaterMark: currentApp.addWaterMark || (currentApp.config && currentApp.config.ADDWATERMARK) || 'y',
346
- sentryMode: currentApp.sentryMode || (currentApp.config && currentApp.config.SENTRY_MODE) || 'y',
347
347
  };
348
348
 
349
+ if (addWaterMark !== undefined && addWaterMark !== null) {
350
+ postDataObj.addWaterMark = addWaterMark;
351
+ }
352
+ if (sentryMode !== undefined && sentryMode !== null) {
353
+ postDataObj.sentryMode = sentryMode;
354
+ }
349
355
  if (params.hideAppNav !== null && params.hideAppNav !== undefined) {
350
356
  postDataObj.hideAppNav = params.hideAppNav;
351
357
  } else {
@@ -389,11 +389,19 @@ function normalizeCreateAppInput(body = {}) {
389
389
  }
390
390
 
391
391
  function normalizeAppListInput(body = {}) {
392
- const size = Number(body.size || body.pageSize || 20);
392
+ const type = String(body.type ?? 'managed').trim();
393
+ const page = Number(body.page ?? body.pageIndex ?? 1);
394
+ const size = Number(body.size ?? body.pageSize ?? 16);
395
+ if (!['managed', 'created'].includes(type)) {
396
+ throw new Error('type must be managed or created');
397
+ }
398
+ if (!Number.isInteger(page) || page < 1) {
399
+ throw new Error('page must be a positive integer');
400
+ }
393
401
  if (!Number.isInteger(size) || size < 1 || size > 100) {
394
402
  throw new Error('size must be an integer between 1 and 100');
395
403
  }
396
- return { size };
404
+ return { type, page, size };
397
405
  }
398
406
 
399
407
  function parseCreateAppJsonOutput(stdout) {
@@ -723,8 +731,12 @@ function defaultListApps(params = {}) {
723
731
  const args = [
724
732
  cliPath,
725
733
  'app-list',
734
+ '--type',
735
+ params.type ?? 'managed',
736
+ '--page',
737
+ String(params.page ?? 1),
726
738
  '--size',
727
- String(params.size || 20),
739
+ String(params.size ?? 16),
728
740
  ];
729
741
 
730
742
  return new Promise((resolve, reject) => {
@@ -322,9 +322,11 @@ async function saveConnector(params, authRef) {
322
322
  }, authRef);
323
323
  assertConnectorReadback(params, detail);
324
324
 
325
+ const detailConnectorId = detail && (detail.id || detail.connectorId);
326
+
325
327
  return {
326
328
  ...result,
327
- connectorId,
329
+ connectorId: detailConnectorId || connectorId,
328
330
  connectorName,
329
331
  detail,
330
332
  readbackVerified: true,
@@ -28,6 +28,7 @@ const {
28
28
  createActionSnapshot,
29
29
  mergeAddedOperations,
30
30
  } = require('./connector-action-update');
31
+ const { assertNoExplicitSystemToken } = require('./yida-system-token');
31
32
 
32
33
  function showUsage() {
33
34
  console.log(`
@@ -105,6 +106,7 @@ async function run(args) {
105
106
  newOperations = [newOperations];
106
107
  }
107
108
  newOperations = normalizeOperations(newOperations);
109
+ assertNoExplicitSystemToken(newOperations, { source: '--operations' });
108
110
  console.log(`✓ 已加载 ${newOperations.length} 个执行动作`);
109
111
  newOperations.forEach((operation, index) => {
110
112
  console.log(` [${index + 1}] ${operation.operationId}: ${operation.summary}`);
@@ -7,14 +7,15 @@
7
7
  * --username <username> 基本身份验证 - 用户名
8
8
  * --password <password> 基本身份验证 - 密码
9
9
  * --api-key <key> API 密钥 - 密钥值
10
- * --app-key <key> 钉钉开放平台 - App Key
11
- * --app-secret <secret> 钉钉开放平台 - App Secret
12
10
  * --app-code <code> 阿里云 API 网关 - AppCode
11
+ * --interactive 在当前终端隐藏输入钉钉 App Key / App Secret
13
12
  */
14
13
 
15
14
  'use strict';
16
15
  const { fail } = require('../core/chalk');
17
16
  const { safeJsonStringify } = require('../core/redact');
17
+ const { CliError } = require('../core/cli-error');
18
+ const { t } = require('../core/i18n');
18
19
 
19
20
  const {
20
21
  getAuthRef,
@@ -36,14 +37,13 @@ function showUsage() {
36
37
  --username <username> 基本身份验证 - 用户名
37
38
  --password <password> 基本身份验证 - 密码
38
39
  --api-key <key> API 密钥 - 密钥值
39
- --app-key <key> 钉钉开放平台 - App Key
40
- --app-secret <secret> 钉钉开放平台 - App Secret
41
40
  --app-code <code> 阿里云 API 网关 - AppCode
41
+ --interactive 在 TTY 终端隐藏输入钉钉 App Key / App Secret(推荐)
42
42
 
43
43
  示例:
44
44
  openyida connector create-connection 910264 "测试账号" --username "admin" --password "123456"
45
45
  openyida connector create-connection 910258 "生产密钥" --api-key "sk-xxxxxxxx"
46
- openyida connector create-connection 910244 "钉钉账号" --app-key "dingxxx" --app-secret "xxx"
46
+ openyida connector create-connection 910244 "钉钉账号" --interactive
47
47
  openyida connector create-connection 910264 "阿里云账号" --app-code "your-app-code"
48
48
  `);
49
49
  }
@@ -85,6 +85,98 @@ function buildSecurityValue(options, authType) {
85
85
  }
86
86
  }
87
87
 
88
+ function secretInputError(code, message, details = {}) {
89
+ return new CliError(message, {
90
+ code,
91
+ details: {
92
+ retrySafe: true,
93
+ sideEffectState: 'none',
94
+ ...details,
95
+ },
96
+ });
97
+ }
98
+
99
+ function readHiddenLine(prompt, input = process.stdin, output = process.stderr) {
100
+ if (!input || !output || input.isTTY !== true || typeof input.setRawMode !== 'function') {
101
+ return Promise.reject(secretInputError(
102
+ 'CONNECTOR_SECRET_INPUT_TTY_REQUIRED',
103
+ t('connector_auth.tty_required'),
104
+ { nextStep: t('connector_auth.tty_next_step') }
105
+ ));
106
+ }
107
+
108
+ return new Promise((resolve, reject) => {
109
+ let value = '';
110
+ const wasRaw = !!input.isRaw;
111
+ const cleanup = () => {
112
+ input.removeListener('data', onData);
113
+ if (!wasRaw) {input.setRawMode(false);}
114
+ input.pause();
115
+ };
116
+ const finish = () => {
117
+ cleanup();
118
+ output.write('\n');
119
+ resolve(value);
120
+ };
121
+ const abort = () => {
122
+ cleanup();
123
+ output.write('\n');
124
+ reject(secretInputError('CONNECTOR_SECRET_INPUT_CANCELLED', t('connector_auth.input_cancelled')));
125
+ };
126
+ const onData = (chunk) => {
127
+ const text = String(chunk);
128
+ for (const char of text) {
129
+ if (char === '\u0003') {abort(); return;}
130
+ if (char === '\r' || char === '\n') {finish(); return;}
131
+ if (char === '\u007f' || char === '\b') {
132
+ value = Array.from(value).slice(0, -1).join('');
133
+ } else if (char >= ' ') {
134
+ value += char;
135
+ }
136
+ }
137
+ };
138
+
139
+ output.write(prompt);
140
+ input.setEncoding('utf8');
141
+ input.setRawMode(true);
142
+ input.resume();
143
+ input.on('data', onData);
144
+ });
145
+ }
146
+
147
+ async function collectInteractiveCredentials(options, authType, deps = {}) {
148
+ if (!options.interactive) {return options;}
149
+ if (options.appKey || options.appSecret) {
150
+ throw secretInputError(
151
+ 'CONNECTOR_SECRET_INPUT_CONFLICT',
152
+ t('connector_auth.interactive_conflict')
153
+ );
154
+ }
155
+ if (authType !== 'DingAuth' && authType !== 'DingTrustGW') {
156
+ throw secretInputError(
157
+ 'CONNECTOR_INTERACTIVE_AUTH_UNSUPPORTED',
158
+ t('connector_auth.interactive_auth_unsupported', authType)
159
+ );
160
+ }
161
+
162
+ const readSecret = deps.readSecret || readHiddenLine;
163
+ const input = deps.input || process.stdin;
164
+ const output = deps.output || process.stderr;
165
+ if (input.isTTY !== true || typeof input.setRawMode !== 'function') {
166
+ throw secretInputError(
167
+ 'CONNECTOR_SECRET_INPUT_TTY_REQUIRED',
168
+ t('connector_auth.tty_required'),
169
+ { nextStep: t('connector_auth.tty_next_step') }
170
+ );
171
+ }
172
+ const appKey = await readSecret(t('connector_auth.prompt_app_key'), input, output);
173
+ const appSecret = await readSecret(t('connector_auth.prompt_app_secret'), input, output);
174
+ if (!appKey || !appSecret) {
175
+ throw secretInputError('CONNECTOR_SECRET_INPUT_EMPTY', t('connector_auth.input_empty'));
176
+ }
177
+ return { ...options, appKey, appSecret };
178
+ }
179
+
88
180
  function parseArgs(args) {
89
181
  const options = {
90
182
  connectorId: args[0],
@@ -111,6 +203,9 @@ function parseArgs(args) {
111
203
  case '--app-code':
112
204
  options.appCode = args[++i];
113
205
  break;
206
+ case '--interactive':
207
+ options.interactive = true;
208
+ break;
114
209
  case '--json':
115
210
  options.json = true;
116
211
  break;
@@ -120,18 +215,19 @@ function parseArgs(args) {
120
215
  return options;
121
216
  }
122
217
 
123
- async function run(args) {
218
+ async function run(args, deps = {}) {
124
219
  if (!args || args.length < 2 || args[0] === '--help' || args[0] === '-h') {
125
220
  showUsage();
126
221
  process.exit(0);
127
222
  }
128
223
 
129
224
  const options = parseArgs(args);
225
+ const log = options.json ? () => {} : console.log;
130
226
  const authRef = getAuthRef();
131
227
 
132
- console.log('🔧 正在创建鉴权账号...\n');
133
- console.log(`连接器 ID: ${options.connectorId}`);
134
- console.log(`账号名称: ${options.connectionName}\n`);
228
+ log('🔧 正在创建鉴权账号...\n');
229
+ log(`连接器 ID: ${options.connectorId}`);
230
+ log(`账号名称: ${options.connectionName}\n`);
135
231
 
136
232
  const connector = await findConnectorById(options.connectorId, authRef);
137
233
  if (!connector) {
@@ -139,8 +235,8 @@ async function run(args) {
139
235
  process.exit(1);
140
236
  }
141
237
 
142
- console.log(`连接器: ${connector.displayName}`);
143
- console.log(`连接器名: ${connector.connectorName}\n`);
238
+ log(`连接器: ${connector.displayName}`);
239
+ log(`连接器名: ${connector.connectorName}\n`);
144
240
 
145
241
  const detail = await getConnectorDetail(connector.connectorName, authRef);
146
242
  const securitySchemes = JSON.parse(detail.securitySchemes || '{}');
@@ -151,9 +247,10 @@ async function run(args) {
151
247
  process.exit(1);
152
248
  }
153
249
 
154
- console.log(`鉴权类型: ${authType}\n`);
250
+ log(`鉴权类型: ${authType}\n`);
155
251
 
156
- const securityValue = buildSecurityValue(options, authType);
252
+ const credentialOptions = await collectInteractiveCredentials(options, authType, deps);
253
+ const securityValue = buildSecurityValue(credentialOptions, authType);
157
254
 
158
255
  const result = await createConnection({
159
256
  connectionName: options.connectionName,
@@ -163,8 +260,8 @@ async function run(args) {
163
260
  authType: AUTH_TYPE_CODES[authType] ?? AUTH_TYPE_CODES.NONE,
164
261
  }, authRef);
165
262
 
166
- console.log('✅ 鉴权账号创建成功!');
167
- console.log(`账号 ID: ${result.id || result.connectionId || '-'}`);
263
+ log('✅ 鉴权账号创建成功!');
264
+ log(`账号 ID: ${result.id || result.connectionId || '-'}`);
168
265
  const output = {
169
266
  success: true,
170
267
  connectionId: result.id || result.connectionId,
@@ -177,4 +274,10 @@ async function run(args) {
177
274
  return output;
178
275
  }
179
276
 
180
- module.exports = { run };
277
+ module.exports = {
278
+ buildSecurityValue,
279
+ collectInteractiveCredentials,
280
+ parseArgs,
281
+ readHiddenLine,
282
+ run,
283
+ };
@@ -13,8 +13,6 @@
13
13
  * --api-key-label <label> API Key 显示标签
14
14
  * --api-key-name <name> API Key 参数名
15
15
  * --api-key-location <loc> API Key 位置: HEADER 或 QUERY
16
- * --app-key <key> 钉钉 App Key
17
- * --app-secret <secret> 钉钉 App Secret
18
16
  * --desc <description> 连接器描述
19
17
  * --icon <icon> 图标代码
20
18
  * --operations <file> 执行动作配置文件 (JSON 格式,创建时必填)
@@ -33,8 +31,11 @@ const {
33
31
  } = require('./api');
34
32
  const { normalizeOperations } = require('./operation-normalizer');
35
33
  const { safeJsonStringify } = require('../core/redact');
34
+ const { CliError } = require('../core/cli-error');
35
+ const { t } = require('../core/i18n');
36
36
  const { HTTP_CONNECTOR_MODE } = require('./contract');
37
37
  const { buildConnectorSaveParams, createActionSnapshot } = require('./connector-action-update');
38
+ const { assertNoExplicitSystemToken } = require('./yida-system-token');
38
39
 
39
40
  function showUsage() {
40
41
  console.log(`
@@ -53,8 +54,6 @@ function showUsage() {
53
54
  --api-key-label <label> API Key 显示标签 (默认: API Key)
54
55
  --api-key-name <name> API Key 参数名 (默认: X-API-Key)
55
56
  --api-key-location <loc> API Key 位置: HEADER 或 QUERY (默认: HEADER)
56
- --app-key <key> 钉钉 App Key
57
- --app-secret <secret> 钉钉 App Secret
58
57
  --desc <description> 连接器描述
59
58
  --icon <icon> 图标代码 (默认: chaxun%%#FFA200)
60
59
  --operations <file> 执行动作配置文件 (JSON 格式,创建时必填)
@@ -136,11 +135,15 @@ function parseArgs(args) {
136
135
  options.apiKeyLocation = args[++i]?.toUpperCase();
137
136
  break;
138
137
  case '--app-key':
139
- options.appKey = args[++i];
140
- break;
141
138
  case '--app-secret':
142
- options.appSecret = args[++i];
143
- break;
139
+ throw new CliError(t('connector_auth.create_rejects_credentials'), {
140
+ code: 'CONNECTOR_CREDENTIALS_NOT_ACCEPTED_ON_CREATE',
141
+ details: {
142
+ retrySafe: true,
143
+ sideEffectState: 'none',
144
+ nextStep: t('connector_auth.create_credentials_next_step'),
145
+ },
146
+ });
144
147
  case '--desc':
145
148
  options.description = args[++i];
146
149
  break;
@@ -179,9 +182,6 @@ function buildSecuritySchemes(options) {
179
182
  },
180
183
  });
181
184
  case 'DINGTALK':
182
- if (!options.appKey || !options.appSecret) {
183
- throw new Error('钉钉鉴权需要提供 --app-key 和 --app-secret');
184
- }
185
185
  return JSON.stringify({ DingAuth: {} });
186
186
  case 'ALIYUN':
187
187
  return JSON.stringify({ AliyunApiGateway: {} });
@@ -222,12 +222,13 @@ async function run(args) {
222
222
  }
223
223
 
224
224
  const options = parseArgs(args);
225
+ const log = options.json ? () => {} : console.log;
225
226
  const authRef = getAuthRef();
226
227
 
227
228
  // 更新模式
228
229
  if (options.isUpdateMode) {
229
- console.log('🔧 正在更新连接器...\n');
230
- console.log(`连接器 ID: ${options.connectorId}\n`);
230
+ log('🔧 正在更新连接器...\n');
231
+ log(`连接器 ID: ${options.connectorId}\n`);
231
232
 
232
233
  const connector = await findConnectorById(options.connectorId, authRef);
233
234
  if (!connector) {
@@ -235,8 +236,8 @@ async function run(args) {
235
236
  process.exit(1);
236
237
  }
237
238
 
238
- console.log(`找到连接器: ${connector.displayName}`);
239
- console.log(`当前描述: ${connector.connectorDesc || '(空)'}\n`);
239
+ log(`找到连接器: ${connector.displayName}`);
240
+ log(`当前描述: ${connector.connectorDesc || '(空)'}\n`);
240
241
 
241
242
  const detail = await getConnectorDetail(connector.connectorName, authRef);
242
243
  const snapshot = createActionSnapshot({
@@ -251,17 +252,17 @@ async function run(args) {
251
252
  connectorDesc: newDesc,
252
253
  }, authRef);
253
254
 
254
- console.log('✅ 连接器更新成功!');
255
- console.log('\n新描述:');
256
- console.log(newDesc);
255
+ log('✅ 连接器更新成功!');
256
+ log('\n新描述:');
257
+ log(newDesc);
257
258
  return;
258
259
  }
259
260
 
260
261
  // 创建模式
261
- console.log('🔧 正在创建连接器...\n');
262
- console.log(`名称: ${options.name}`);
263
- console.log(`基础域名: ${options.baseUrl}`);
264
- console.log(`鉴权方式: ${options.authType}`);
262
+ log('🔧 正在创建连接器...\n');
263
+ log(`名称: ${options.name}`);
264
+ log(`基础域名: ${options.baseUrl}`);
265
+ log(`鉴权方式: ${options.authType}`);
265
266
 
266
267
  if (!options.operationsFile) {
267
268
  fail('错误: 创建连接器必须提供 --operations 参数');
@@ -278,7 +279,8 @@ async function run(args) {
278
279
  process.exit(1);
279
280
  }
280
281
  operations = normalizeOperations(operations);
281
- console.log(`✓ 已加载 ${operations.length} 个执行动作`);
282
+ assertNoExplicitSystemToken(operations, { source: '--operations' });
283
+ log(`✓ 已加载 ${operations.length} 个执行动作`);
282
284
  } catch (e) {
283
285
  fail(`读取执行动作配置文件失败: ${e.message}`);
284
286
  process.exit(1);
@@ -309,25 +311,31 @@ async function run(args) {
309
311
  const detailUrl = connectorId
310
312
  ? `https://yidalogin.aliwork.com/platformManage/customConnectorFactory/update?id=${connectorId}&connectorName=${connectorName}&mode=http`
311
313
  : null;
314
+ const accountManageUrl = connectorName
315
+ ? `https://yidalogin.aliwork.com/platformManage/customConnectorFactory?connectorName=${encodeURIComponent(connectorName)}&action=accountManage`
316
+ : null;
312
317
 
313
- console.log('\n✅ 连接器创建成功!');
318
+ log('\n✅ 连接器创建成功!');
314
319
  if (connectorId) {
315
- console.log(`\nID: ${connectorId}`);
320
+ log(`\nID: ${connectorId}`);
316
321
  }
317
- console.log(`连接器名称: ${connectorName}`);
318
- console.log(`显示名称: ${options.name}`);
319
- console.log(`基础域名: ${options.baseUrl}`);
322
+ log(`连接器名称: ${connectorName}`);
323
+ log(`显示名称: ${options.name}`);
324
+ log(`基础域名: ${options.baseUrl}`);
320
325
  if (detailUrl) {
321
- console.log(`\n🔗 查看连接器:\n ${detailUrl}`);
326
+ log(`\n🔗 查看连接器:\n ${detailUrl}`);
327
+ log(`\n🔐 添加授权账号:\n ${accountManageUrl}`);
322
328
  } else {
323
- console.log('\n💡 下一步: 查看连接器列表获取 ID');
324
- console.log(' openyida connector list');
329
+ log('\n💡 下一步: 查看连接器列表获取 ID');
330
+ log(' openyida connector list');
325
331
  }
326
332
  const result = {
327
333
  success: true,
328
334
  connectorId,
329
335
  connectorName,
330
336
  displayName: options.name,
337
+ detailUrl,
338
+ accountManageUrl,
331
339
  readbackVerified: saveResult.readbackVerified === true,
332
340
  };
333
341
  if (options.json) {
@@ -336,4 +344,4 @@ async function run(args) {
336
344
  return result;
337
345
  }
338
346
 
339
- module.exports = { buildSecuritySchemes, parseBaseUrl, run };
347
+ module.exports = { buildSecuritySchemes, parseArgs, parseBaseUrl, run };
@@ -42,6 +42,7 @@ async function run(args) {
42
42
  const result = {
43
43
  success: true,
44
44
  connectorId: String(connectorId),
45
+ connectorName: detail.connectorName || connector.connectorName,
45
46
  detail: { ...detail, id: detail.id === undefined ? connector.id : detail.id },
46
47
  };
47
48
  if (json) {
@@ -53,7 +54,7 @@ async function run(args) {
53
54
  console.log('📋 基本信息');
54
55
  console.log('─'.repeat(50));
55
56
  console.log(`显示名称: ${detail.displayName || '-'}`);
56
- console.log(`连接器名: ${detail.connectorName || '-'}`);
57
+ console.log(`连接器名: ${result.connectorName || '-'}`);
57
58
  console.log(`描述: ${detail.connectorDesc ? detail.connectorDesc.split('---')[0].trim() : '-'}`);
58
59
  console.log(`域名: ${detail.host || '-'}`);
59
60
  console.log(`基础路径: ${detail.baseUrl || '-'}`);