openyida 2026.10.20 → 2026.10.21

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 (38) hide show
  1. package/README.md +4 -1
  2. package/bin/yida.js +9 -0
  3. package/lib/app/agent-app-type.js +12 -0
  4. package/lib/app/cool-card.js +5 -0
  5. package/lib/app/upgrade-app-theme.js +2 -1
  6. package/lib/core/command-manifest.js +10 -5
  7. package/lib/core/locales/en.js +12 -1
  8. package/lib/core/locales/zh.js +12 -1
  9. package/lib/core/sample.js +1 -0
  10. package/lib/core/task-center.js +3 -1
  11. package/lib/core/update.js +1 -1
  12. package/lib/feedback/feedback.js +1 -0
  13. package/lib/feedback/github.js +130 -0
  14. package/lib/print-template/builder.js +232 -0
  15. package/lib/print-template/codes.js +46 -0
  16. package/lib/print-template/contract.js +31 -0
  17. package/lib/print-template/detail.js +36 -0
  18. package/lib/print-template/model.js +97 -0
  19. package/lib/print-template/print-template.js +302 -0
  20. package/lib/print-template/scenes.js +29 -0
  21. package/lib/samples/yida-print-template/approval.json +14 -0
  22. package/lib/samples/yida-print-template/codes.json +12 -0
  23. package/lib/samples/yida-print-template/delivery.json +14 -0
  24. package/lib/samples/yida-print-template/order.json +14 -0
  25. package/lib/samples/yida-print-template/quotation.json +13 -0
  26. package/lib/samples/yida-print-template/receipt.json +13 -0
  27. package/lib/task-center/lookups.js +11 -4
  28. package/package.json +1 -1
  29. package/yida-skills/SKILL.md +3 -2
  30. package/yida-skills/skills/yida-cool-card/SKILL.md +3 -1
  31. package/yida-skills/skills/yida-feedback/SKILL.md +1 -1
  32. package/yida-skills/skills/yida-feedback/references/github-contribution.md +19 -2
  33. package/yida-skills/skills/yida-print-template/SKILL.md +134 -0
  34. package/yida-skills/skills/yida-print-template/references/detail.md +40 -0
  35. package/yida-skills/skills/yida-print-template/references/guide.md +35 -0
  36. package/yida-skills/skills/yida-print-template/references/scenarios.md +72 -0
  37. package/yida-skills/skills/yida-task-center/references/task-filters.md +2 -0
  38. package/yida-skills/skills-index.json +14 -1
package/README.md CHANGED
@@ -446,6 +446,8 @@ openyida feedback report --input summary.json --output-dir feedback-draft-001
446
446
 
447
447
  Your AI prepares the summary from observed evidence and selected reproduction screenshots. PRs include before/after test comparisons. Before anything is published, review the final text and images, confirm sensitive information, and remove anything you do not want to share. See the [report input format](yida-skills/skills/yida-feedback/references/report-input.md). Review business information before sharing. With a GitHub account, authorize your AI to submit an Issue or contribute a verified public OpenYida fix through your personal Fork. Without an account, create a GitHub account first. If that is not possible, share the report in the AI开发者群 (AI developer group).
448
448
 
449
+ When you choose to submit an OpenYida Issue, the feedback Skill can check your signed-in GitHub account with `openyida feedback github status --json`. Star and a personal Fork are optional: `feedback github star --confirm` and `feedback github fork --confirm` run only after you choose them. Existing actions are reused. This check is separate from installation and automatic updates; neither action is required to submit an Issue.
450
+
449
451
  <!-- OPENYIDA_COMMANDS_START -->
450
452
  <!-- This section is generated by `npm run docs:commands`. Do not edit command rows by hand. -->
451
453
 
@@ -509,6 +511,7 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
509
511
  | `openyida create-form add-option <appType> <formUuid> <fieldLabel> <option1> [option2] ...` | Update a form page |
510
512
  | `openyida list-forms <appType> [--keyword <text>]` | List forms/pages in an app |
511
513
  | `openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...` | Manage aggregate tables (virtualView) |
514
+ | `openyida print-template <list\|inspect\|fields\|build\|create\|copy\|save\|restore\|settings\|status\|order\|render\|files> <appType> <formUuid> [templateId] [--config <file>] [--scene <order\|quotation\|delivery\|receipt\|approval\|codes>] [--bindings <file>] [--name <title>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--status START\|STOP] [--instance <id>] [--page <n>] [--size <n>] [--allow-permission-change] [--json] \| print-template <list\|render> --detail-url <url> [--template-id <id>] [--json]` | Manage native YiDA print templates, generate document layouts, back up, restore and request PDFs |
512
515
  | `openyida form-view <list\|inspect\|save\|create\|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]` | Native form views: list, inspect and configure columns, widths, frozen columns and filters |
513
516
  | `openyida custom-button <list\|inspect\|options\|create\|update\|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]` | Native custom buttons: inspect and configure form edits, automation flows and page links |
514
517
  | `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
@@ -628,7 +631,7 @@ Your AI prepares the summary from observed evidence and selected reproduction sc
628
631
  | `openyida formula test <appType> <formUuid> [formula\|file] [--data-file file \| --cases file] [--field fieldId] [--timezone-offset minutes] [--context form\|validation] [--binding platform\|numeric-literals] [--expect JSON] [--json]` | Evaluate a non-writing formula in Yida and compare its result |
629
632
  | `openyida update` | Check and update to latest version |
630
633
  | `openyida export-conversation [options]` | Export AI conversation records |
631
- | `openyida feedback <report\|setup\|url\|dismiss\|status> [options]` | Prepare local issue reports, configure feedback forms and reminders |
634
+ | `openyida feedback <report\|github\|setup\|url\|dismiss\|status> [options]` | Prepare local issue reports, configure feedback forms and reminders |
632
635
  | `openyida batch <file>\|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]` | Run OpenYida commands in batch |
633
636
  | `openyida flash-to-prd --file <path> --name "<project>"` | Convert flash notes or meeting notes to a PRD prompt |
634
637
  | `openyida ai <text\|image> [options]` | Call Yida AI text and image recognition APIs |
package/bin/yida.js CHANGED
@@ -837,6 +837,11 @@ async function main() {
837
837
  await run(args);
838
838
  break;
839
839
  }
840
+ case 'print-template': {
841
+ const { run } = require('../lib/print-template/print-template');
842
+ await run(args);
843
+ break;
844
+ }
840
845
  case 'form-view': {
841
846
  const { run } = require('../lib/app/form-view');
842
847
  await run(args);
@@ -1333,6 +1338,10 @@ async function main() {
1333
1338
  }
1334
1339
 
1335
1340
  case 'feedback': {
1341
+ if (args[0] === 'github') {
1342
+ await require('../lib/feedback/github').run(args.slice(1));
1343
+ break;
1344
+ }
1336
1345
  const { run: runFeedback } = require('../lib/feedback/feedback');
1337
1346
  await runFeedback(args);
1338
1347
  break;
@@ -0,0 +1,12 @@
1
+ 'use strict';
2
+
3
+ // Server-derived provenance: channels may grow; only absence or "none" means non-AI.
4
+ function isAgentAppType(value) {
5
+ if (typeof value !== 'string') {
6
+ return false;
7
+ }
8
+ const type = value.trim().toLowerCase();
9
+ return type !== '' && type !== 'none';
10
+ }
11
+
12
+ module.exports = { isAgentAppType };
@@ -6,6 +6,7 @@ const crypto = require('crypto');
6
6
  const { CliError } = require('../core/cli-error');
7
7
  const { t } = require('../core/i18n');
8
8
  const { createAuthRef, createYidaClient } = require('../core/yida-client');
9
+ const { inferEnvironmentNameFromUrl } = require('../core/env-manager');
9
10
  const { buildApplicationSettingsUrls } = require('./application-entry-urls');
10
11
 
11
12
  const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
@@ -124,6 +125,10 @@ async function execute(parsed, client) {
124
125
  let { cardPageCode } = checked;
125
126
  if (flags['--out'] && fs.existsSync(path.resolve(flags['--out']))) { fail('OUTPUT_EXISTS', flags['--out']); }
126
127
  client = client || createYidaClient({ authRef: createAuthRef() });
128
+ // Use the authenticated target, not a global environment preference, before creating any resource.
129
+ if (action === 'create' && inferEnvironmentNameFromUrl(client.getAuthRef().baseUrl) === 'alibaba') {
130
+ fail('UNSUPPORTED_ENVIRONMENT', t('cool_card.unsupported_environment'), { environment: 'alibaba' });
131
+ }
127
132
  const prefix = `/${appType}/query/cardDesign/`;
128
133
  const get = async (name, params) => unwrap(await client.get(prefix + name + '.json', params), name);
129
134
  const base = { success: true, action, appType, resourceType: 'native-cool-card', capabilities };
@@ -1,6 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  const querystring = require('querystring');
4
+ const { isAgentAppType } = require('./agent-app-type');
4
5
  const { httpGet, httpPost, requestWithAutoLogin } = require('../core/utils');
5
6
  const { createAuthRef } = require('../core/yida-client');
6
7
  const { throwCommandError, throwUsage } = require('../core/command-errors');
@@ -64,7 +65,7 @@ async function readThemeEligibility(appType, auth) {
64
65
  }
65
66
  // agentAppType is derived by Tianshu from FROM_BUILDER_AI and BUILDER_AI_SOURCE.
66
67
  // aiApp/FROM_AI is a separate feature flag and must not be used as provenance.
67
- if (!['local', 'cloud', 'qwenwork'].includes(context.agentAppType.trim().toLowerCase())) {
68
+ if (!isAgentAppType(context.agentAppType)) {
68
69
  throwCommandError(t('upgrade_app_theme.ai_only'), { code: 'APP_THEME_AI_APP_REQUIRED' });
69
70
  }
70
71
  return context;
@@ -8,6 +8,7 @@ const { getPageNavigationContract } = require('../design-plan/navigation-policy'
8
8
  const { ENTRY_MODES } = require('../design-plan/entry-navigation');
9
9
  const { DIVIDER_TYPES, DEFAULT_DIVIDER_TYPE } = require('../app/form-field-validator');
10
10
  const { FORMULA_COMMANDS } = require('../formula/cli-contract');
11
+ const { printTemplateCommand, printTemplateEffects, printTemplatePermission } = require('../print-template/contract');
11
12
 
12
13
  const SIDE_EFFECT_BASES = Object.freeze({
13
14
  local_read: Object.freeze({
@@ -550,6 +551,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
550
551
  })],
551
552
  ['cool-card', sideEffect('mixed', { mutates_yida: true, mutates_local: true, read_actions: ['list', 'templates', 'get'], mutating_actions: ['create'] })],
552
553
  ['data-card', sideEffect('mixed', { mutates_yida: true, mutates_local: true, read_actions: ['list', 'get', 'probe', 'release-check', 'refs'], mutating_actions: ['create', 'save', 'publish', 'release-get', 'release-save', 'release-test'], note: 'release-get reads broadcast settings but the native backend may initialize the application cool app; classify it conservatively as a possible mutation.' })],
554
+ ['print-template', sideEffect('mixed', printTemplateEffects)],
553
555
  ['form-view', sideEffect('mixed', {
554
556
  mutates_yida: true,
555
557
  mutates_local: true,
@@ -645,8 +647,8 @@ const COMMAND_SIDE_EFFECTS = new Map([
645
647
  ['feedback', sideEffect('mixed', {
646
648
  mutates_yida: true,
647
649
  mutates_local: true,
648
- read_actions: ['url', 'status'],
649
- mutating_actions: ['setup', 'dismiss', 'report'],
650
+ read_actions: ['url', 'status', 'github status'],
651
+ mutating_actions: ['setup', 'dismiss', 'report', 'github star', 'github fork'],
650
652
  })],
651
653
  ['i18n', sideEffect('mixed', {
652
654
  mutates_yida: true,
@@ -845,6 +847,7 @@ const COMMAND_PERMISSIONS = new Map([
845
847
  ask_actions: ['connect', 'run', 'disconnect', 'logout'],
846
848
  })),
847
849
 
850
+ ...permissionEntries(['print-template'], actionDependentPermission(printTemplatePermission)),
848
851
  ...permissionEntries(['cool-card'], actionDependentPermission({ preauthorized_actions: ['create'] })),
849
852
  ...permissionEntries(['data-card'], actionDependentPermission({ preauthorized_actions: ['create', 'save', 'publish', 'release-get'], ask_actions: ['release-save', 'release-test'] })),
850
853
  ...permissionEntries([
@@ -1383,7 +1386,7 @@ const COMMAND_GROUPS = [
1383
1386
  { name: 'json', type: 'boolean', source: 'option', builder_options: ['--json'] },
1384
1387
  { name: 'confirm', type: 'boolean', source: 'option', builder_options: ['--confirm'] },
1385
1388
  ],
1386
- notes: ['--prepare reads remote schemas/source bundles into a new local backup directory and produces a per-resource migration plan without remote writes; it requires explicit upgrade intent but no mutation confirmation. Before --confirm, use the independent upgrade Skill to prepare/review sources, migrate legacy JSX to Canvas without business/layout changes, and remove verified form-detail style injection. Only mutate after an explicit user request to upgrade an existing app to the modern theme AND an affirmative ask_human reply for that app. These flags attest to both checks; they do not replace human confirmation. If ask_human is unavailable, stop before mutation. The CLI itself checks server-derived agentAppType and app/corp identity before offering any confirmation or preparing backups. agentAppType comes from FROM_BUILDER_AI=y plus BUILDER_AI_SOURCE; source alone or FROM_AI does not qualify. Only local/cloud/qwenwork qualifies. Reject non-AI or unverified apps without offering an upgrade or exposing source fields. Skill guidance and confirmation flags cannot bypass this gate. Do not check V4, appThemeEnable, organization theme gray or upgrade gray. After ask_human approval, migrate legacy navigation layout and colors before persisting CREATED_WITH_MODERN_THEME=y and APP_THEME_MODE=modern, then read all changed settings back. Already modern navigation colors stay unchanged. Report partial/unknown writes on failure. Success verifies configuration only, not page migration or runtime rendering. appUpgradeComplete remains false until the Skill verifies all page/form migrations.'],
1389
+ notes: ['--prepare reads remote schemas/source bundles into a new local backup directory and produces a per-resource migration plan without remote writes; it requires explicit upgrade intent but no mutation confirmation. Before --confirm, use the independent upgrade Skill to prepare/review sources, migrate legacy JSX to Canvas without business/layout changes, and remove verified form-detail style injection. Only mutate after an explicit user request to upgrade an existing app to the modern theme AND an affirmative ask_human reply for that app. These flags attest to both checks; they do not replace human confirmation. If ask_human is unavailable, stop before mutation. The CLI itself checks server-derived agentAppType and app/corp identity before offering any confirmation or preparing backups. agentAppType comes from FROM_BUILDER_AI=y plus BUILDER_AI_SOURCE; source alone or FROM_AI does not qualify. Use the centralized server-derived AI application type check (including web_local). Reject non-AI or unverified apps without offering an upgrade or exposing source fields. Skill guidance and confirmation flags cannot bypass this gate. Do not check V4, appThemeEnable, organization theme gray or upgrade gray. After ask_human approval, migrate legacy navigation layout and colors before persisting CREATED_WITH_MODERN_THEME=y and APP_THEME_MODE=modern, then read all changed settings back. Already modern navigation colors stay unchanged. Report partial/unknown writes on failure. Success verifies configuration only, not page migration or runtime rendering. appUpgradeComplete remains false until the Skill verifies all page/form migrations.'],
1387
1390
  }),
1388
1391
  command('app-online', ['app-online'], 'app-online <appType> [--to-ding-app-center] [--show-app-center]', 'help.cmd_app_online', { managedRun: {'scope':'denied','reason':'scope_not_audited'} }),
1389
1392
  command('app-offline', ['app-offline'], 'app-offline <appType> [--to-ding-app-center] [--show-app-center]', 'help.cmd_app_offline', { managedRun: {'scope':'denied','reason':'scope_not_audited'} }),
@@ -1609,6 +1612,7 @@ const COMMAND_GROUPS = [
1609
1612
  },
1610
1613
  output: 'json',
1611
1614
  }),
1615
+ command(...printTemplateCommand),
1612
1616
  command('form-view', ['form-view'], 'form-view <list|inspect|save|create|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]', 'help.cmd_form_view', {
1613
1617
  managedRun: { scope: 'actions', actions: {
1614
1618
  list: { scope: 'app', appPosition: 1 },
@@ -2047,11 +2051,12 @@ const COMMAND_GROUPS = [
2047
2051
  command('export-conversation', ['export-conversation'], 'export-conversation [options]', 'help.cmd_export_conversation', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
2048
2052
  requiresLogin: false,
2049
2053
  }),
2050
- command('feedback', ['feedback'], 'feedback <report|setup|url|dismiss|status> [options]', 'help.cmd_feedback', { managedRun: { scope: 'actions', actions: { report: { scope: 'local' } } },
2054
+ command('feedback', ['feedback'], 'feedback <report|github|setup|url|dismiss|status> [options]', 'help.cmd_feedback', { managedRun: { scope: 'actions', actions: { report: { scope: 'local' } } },
2055
+ permission: actionDependentPermission({ preauthorized_actions: ['report', 'url', 'status', 'dismiss', 'github status'], ask_actions: ['setup', 'github star', 'github fork'] }),
2051
2056
  requiresLogin: false,
2052
2057
  output: 'text|json',
2053
2058
  examples: ['openyida feedback report --input summary.json --output-dir feedback-draft-001'],
2054
- notes: ['report prepares local review drafts only; no login, source checkout, update check or network submission.'],
2059
+ notes: ['report prepares local review drafts only; no login, source checkout, update check or network submission. github status [--json] checks the authenticated github.com user, Star and personal Fork through an existing gh installation; it performs no writes. github star|fork --confirm [--json] requires the user to explicitly choose that account action. Missing gh/login, unknown status and declined actions never block Issue submission. Already applied actions are reused; writes are single-attempt with readback. No auto-update/install hook, Issue submission, clone or git remote modification. Managed runs permit only offline report preparation.'],
2055
2060
  }),
2056
2061
  command('batch', ['batch'], 'batch <file>|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]', 'help.cmd_batch', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
2057
2062
  output: 'text|json',
@@ -4,6 +4,11 @@
4
4
  'use strict';
5
5
 
6
6
  module.exports = {
7
+ print_template: {
8
+ 'description': 'Manage native YiDA print templates, generate document layouts, back up, restore and request PDFs',
9
+ 'error': 'Print template operation failed: {0}',
10
+ 'usage': 'Usage: openyida print-template <list|inspect|fields|build|create|copy|save|restore|settings|status|order|render|files> <appType> <formUuid> [templateId]. Use --config <file>, build --scene order|quotation|delivery|receipt|approval|codes --bindings <file>, create --config <file>, --name <title>, --expected-hash <inspect-hash>, --backup <file>, --out <file>, --status START|STOP, --instance <id>, --page <n>, --size <n>, --allow-permission-change, --json as documented. For native record links: list|render --detail-url <url> [--template-id <id>].'
11
+ },
7
12
  custom_button: {
8
13
  'usage': 'Usage: openyida custom-button <list|inspect|options|create|update|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]',
9
14
  'invalid_config': 'Invalid configuration or argument: {0}',
@@ -15,7 +20,7 @@ module.exports = {
15
20
  'readback_mismatch': 'Saved custom button does not match readback. Keep the backup and inspect remote state.'
16
21
  },
17
22
  dws_external: {'note': 'dws is an independent external tool. OpenYida only shows installation instructions; it does not download or install it.', 'install_guide': 'Show official DingTalk CLI installation instructions'},
18
- cool_card: {'description': 'Create and read native YiDA cool cards (interactive and pinned)', 'usage': 'Usage: openyida cool-card <list|templates|create|get> <appType> [cardPageCode] [--type im|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]', 'failed': 'Native cool card operation stopped: {0}'},
23
+ cool_card: {'unsupported_environment': 'The Alibaba Group edition does not support building cool cards.', 'description': 'Create and read native YiDA cool cards (interactive and pinned)', 'usage': 'Usage: openyida cool-card <list|templates|create|get> <appType> [cardPageCode] [--type im|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]', 'failed': 'Native cool card operation stopped: {0}'},
19
24
  data_card: {'description':'Create, configure, publish and distribute native YiDA data cards','usage':'Usage: openyida data-card <list|create|get|save|publish|probe|release-get|release-save|release-check|release-test|refs> <appType> [cardCode] [--name <title>] [--description <text>] [--schema <file>|--config <file>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--published|--version <version>] [--page <n>] [--size <n>] [--search <text>] [--allow-send] [--json]','failed':'Native data card operation stopped: {0}','conflict':'The draft changed. Read it again before saving or publishing.','mismatch':'Readback does not match the expected schema or published state. Keep the backup and inspect the card before retrying.'},
20
25
  app_settings: {'description':'Read theme, frontend/backend, native data card and cool card management links','usage':'Usage: openyida app-settings <appType> [--section theme|basic|data-card|cool-card] [--json]','failed':'Application settings could not be read: {0}'},
21
26
  form_view: {
@@ -2507,3 +2512,9 @@ module.exports.task_center = {
2507
2512
  'filter_carbonCreateFrom': 'Copied from',
2508
2513
  'filter_carbonCreateTo': 'Copied to'
2509
2514
  };
2515
+
2516
+ module.exports.feedback_github = {
2517
+ usage: 'Usage: openyida feedback github <status|star|fork> [--confirm] [--json]',
2518
+ confirmation: 'Star or Fork requires the user to explicitly choose that action before --confirm. Neither is required to submit an Issue.',
2519
+ failed: 'The GitHub action could not be verified. Inspect its status before retrying; this does not block Issue submission.',
2520
+ };
@@ -4,6 +4,11 @@
4
4
  'use strict';
5
5
 
6
6
  module.exports = {
7
+ print_template: {
8
+ 'description': '管理宜搭原生打印模板、按字段生成多种单据、备份恢复与生成PDF',
9
+ 'error': '打印模板操作失败:{0}',
10
+ 'usage': 'Usage: openyida print-template <list|inspect|fields|build|create|copy|save|restore|settings|status|order|render|files> <appType> <formUuid> [templateId]. Use --config <file>, build --scene order|quotation|delivery|receipt|approval|codes --bindings <file>, create --config <file>, --name <title>, --expected-hash <inspect-hash>, --backup <file>, --out <file>, --status START|STOP, --instance <id>, --page <n>, --size <n>, --allow-permission-change, --json as documented. For native record links: list|render --detail-url <url> [--template-id <id>].'
11
+ },
7
12
  custom_button: {
8
13
  'usage': '用法:openyida custom-button <list|inspect|options|create|update|delete> <appType> <formUuid> [buttonUuid] [--config <file>] [--expected-hash <hash>] [--backup <file>] [--yes] [--json]',
9
14
  'invalid_config': '配置或参数无效:{0}',
@@ -15,7 +20,7 @@ module.exports = {
15
20
  'readback_mismatch': '保存后的配置与回读不一致,请保留备份并检查远端状态。'
16
21
  },
17
22
  dws_external: {'note': 'dws 是独立的外部工具;OpenYida 仅提供安装指引,不下载或安装它。', 'install_guide': '显示钉钉 CLI 官方安装指引'},
18
- cool_card: {'description': '创建和读取宜搭原生酷卡片(交互卡片与置顶卡片)', 'usage': '用法: openyida cool-card <list|templates|create|get> <appType> [cardPageCode] [--type im|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]', 'failed': '原生酷卡片操作已停止:{0}'},
23
+ cool_card: {'unsupported_environment': '集团版不支持酷卡片搭建。', 'description': '创建和读取宜搭原生酷卡片(交互卡片与置顶卡片)', 'usage': '用法: openyida cool-card <list|templates|create|get> <appType> [cardPageCode] [--type im|onebox] [--name <title>] [--page <n>] [--size <n>] [--search <text>] [--out <file>] [--json]', 'failed': '原生酷卡片操作已停止:{0}'},
19
24
  data_card: {'description':'创建、配置、发布原生数据卡片并管理投放播报','usage':'用法: openyida data-card <list|create|get|save|publish|probe|release-get|release-save|release-check|release-test|refs> <appType> [cardCode] [--name <title>] [--description <text>] [--schema <file>|--config <file>] [--expected-hash <hash>] [--backup <file>] [--out <file>] [--published|--version <version>] [--page <n>] [--size <n>] [--search <text>] [--allow-send] [--json]','failed':'原生数据卡片操作已停止:{0}','conflict':'卡片草稿已发生变化,请重新读取后再保存或发布。','mismatch':'回读配置或发布状态与预期不一致,请保留备份并检查卡片后再重试。'},
20
25
  app_settings: {'description':'获取主题、业务前后台、原生数据卡片和酷卡片管理入口','usage':'用法: openyida app-settings <appType> [--section theme|basic|data-card|cool-card] [--json]','failed':'无法读取应用设置:{0}'},
21
26
  form_view: {
@@ -2461,3 +2466,9 @@ module.exports.task_center = {
2461
2466
  'filter_carbonCreateFrom': '抄送时间起',
2462
2467
  'filter_carbonCreateTo': '抄送时间止'
2463
2468
  };
2469
+
2470
+ module.exports.feedback_github = {
2471
+ usage: '用法:openyida feedback github <status|star|fork> [--confirm] [--json]',
2472
+ confirmation: 'Star 或 Fork 需要用户明确选择后再传入 --confirm;提交 Issue 不要求这两项操作。',
2473
+ failed: 'GitHub 操作未能核实完成,请先查询状态;这不影响提交 Issue。',
2474
+ };
@@ -23,6 +23,7 @@ const { t } = require('./i18n');
23
23
  // ── Sample 索引表 ─────────────────────────────────────────────────────
24
24
 
25
25
  const SAMPLES = {
26
+ 'yida-print-template': Object.fromEntries(require('../print-template/scenes').names.map(name => [name, `${name}.json`])),
26
27
  'yida-chart': {
27
28
  'line-trend': 'line-trend.js',
28
29
  'multi-bar-compare': 'multi-bar-compare.js',
@@ -226,7 +226,9 @@ async function run(args) {
226
226
  const client = createYidaClient({ authRef });
227
227
  const params = auth => ({ _api: 'nattyFetch', _mock: 'false', _csrf_token: auth.csrfToken, ...request.params });
228
228
  const payload = request.method === 'POST' ? await client.postForm(request.path, params) : await client.get(request.path, params);
229
- const result = lookups.normalize(options.type, payload, { appType: positionals[1] });
229
+ const result = lookups.normalize(options.type, payload, {
230
+ appType: positionals[1], keyword: request.params.key, pageSize: request.params.pageSize,
231
+ });
230
232
  console.log(JSON.stringify(result, null, 2));
231
233
  return result;
232
234
  }
@@ -159,7 +159,7 @@ function getAutoUpdateIntervalMs(env = process.env) {
159
159
 
160
160
  function shouldSkipAutoUpdateCommand(command, args = []) {
161
161
  // Report preparation must work offline even in a global npm installation.
162
- if (command === 'feedback' && args[0] === 'report') {return true;}
162
+ if (command === 'feedback' && ['report', 'github'].includes(args[0])) {return true;}
163
163
  if (!command || ['--help', '-h', '--version', '-v'].includes(command)) {
164
164
  return true;
165
165
  }
@@ -51,6 +51,7 @@ function printUsage() {
51
51
  openyida feedback setup <appType> [--name "OpenYida 体验反馈"] [--path /o/openyida-feedback] [--open|--no-open]
52
52
  openyida feedback setup --create-app [--app-name "OpenYida 体验反馈"] [--name "OpenYida 体验反馈"]
53
53
  openyida feedback report --input <summary.json> --output-dir <new-directory>
54
+ openyida feedback github <status|star|fork> [--confirm] [--json]
54
55
  openyida feedback url [--tool Codex] [--model gpt-5] [--command publish] [--diagnostics "..."]
55
56
  openyida feedback dismiss --today|--forever|--reset
56
57
  openyida feedback status
@@ -0,0 +1,130 @@
1
+ 'use strict';
2
+
3
+ const { spawnSync } = require('child_process');
4
+ const { CliError } = require('../core/cli-error');
5
+ const { t } = require('../core/i18n');
6
+
7
+ const REPOSITORY = 'openyida/openyida';
8
+ const links = Object.freeze({ repository: `https://github.com/${REPOSITORY}`,
9
+ issue: `https://github.com/${REPOSITORY}/issues/new`, fork: `https://github.com/${REPOSITORY}/fork` });
10
+
11
+ /** Parse explicit account actions; submitting an Issue never implies Star or Fork consent. */
12
+ function parseArgs(args = []) {
13
+ const [action, ...flags] = args;
14
+ if (!['status', 'star', 'fork'].includes(action) || new Set(flags).size !== flags.length
15
+ || flags.some(flag => !['--json', '--confirm'].includes(flag))
16
+ || (action === 'status' && flags.includes('--confirm'))) {
17
+ throw new CliError(t('feedback_github.usage'), { code: 'FEEDBACK_GITHUB_USAGE' });
18
+ }
19
+ if (action !== 'status' && !flags.includes('--confirm')) {
20
+ throw new CliError(t('feedback_github.confirmation'), { code: 'FEEDBACK_GITHUB_CONFIRMATION_REQUIRED' });
21
+ }
22
+ return { action, confirm: flags.includes('--confirm') };
23
+ }
24
+
25
+ /** Run gh against github.com only. Never expose its raw output or credential diagnostics. */
26
+ function request(method, endpoint, options, fields = []) {
27
+ const spawn = options.spawnSyncFn || spawnSync;
28
+ const result = spawn('gh', ['api', '--hostname', 'github.com', '--include', '--method', method, endpoint, ...fields], {
29
+ encoding: 'utf8', timeout: 15000, maxBuffer: 1024 * 1024,
30
+ env: { ...(options.env || process.env), GH_HOST: 'github.com', GH_PROMPT_DISABLED: '1', GIT_TERMINAL_PROMPT: '0' },
31
+ stdio: ['ignore', 'pipe', 'pipe'],
32
+ });
33
+ const output = String(result.stdout || '');
34
+ const status = Number(output.match(/^HTTP\/[\d.]+\s+(\d{3})\b/)?.[1]) || null;
35
+ const separator = output.match(/\r?\n\r?\n/);
36
+ let data = null;
37
+ try { data = separator ? JSON.parse(output.slice(separator.index + separator[0].length)) : null; }
38
+ catch { /* Empty responses and invalid JSON remain unverified. */ }
39
+ return { status, data, ok: result.status === 0 && status >= 200 && status < 300,
40
+ unavailable: result.error?.code === 'ENOENT' };
41
+ }
42
+
43
+ /** Verify ownership and upstream before offering an existing personal Fork for contributions. */
44
+ function readFork(response, login) {
45
+ if (response.status === 404) { return { state: 'absent', url: null }; }
46
+ if (!response.ok) { return { state: 'unknown', url: null }; }
47
+ const repo = response.data;
48
+ if (repo?.fork === true && repo.parent?.full_name?.toLowerCase() === REPOSITORY
49
+ && repo.full_name?.toLowerCase() === `${login}/openyida`.toLowerCase()
50
+ && repo.owner?.login?.toLowerCase() === login.toLowerCase()) {
51
+ return { state: 'ready', url: `https://github.com/${login}/openyida` };
52
+ }
53
+ return { state: 'conflict', url: null };
54
+ }
55
+
56
+ /** Include renamed personal Forks; never infer absence from an incomplete or failed query. */
57
+ function inspectFork(login, options) {
58
+ const query = 'query { repository(owner:"openyida", name:"openyida") { forks(first:100, affiliations:[OWNER]) { nodes { nameWithOwner isFork owner { login } parent { nameWithOwner } } pageInfo { hasNextPage } } } }';
59
+ const response = request('POST', 'graphql', options, ['--raw-field', `query=${query}`]);
60
+ const connection = response.data?.data?.repository?.forks;
61
+ if (!response.ok || response.data?.errors || !Array.isArray(connection?.nodes)
62
+ || typeof connection.pageInfo?.hasNextPage !== 'boolean') { return { state: 'unknown', url: null }; }
63
+ for (const repo of connection.nodes) {
64
+ const [owner, name, extra] = String(repo?.nameWithOwner || '').split('/');
65
+ if (repo?.isFork === true && repo.parent?.nameWithOwner?.toLowerCase() === REPOSITORY
66
+ && owner.toLowerCase() === login.toLowerCase() && repo.owner?.login?.toLowerCase() === login.toLowerCase()
67
+ && /^[A-Za-z0-9_.-]+$/.test(name || '') && !extra) {
68
+ return { state: 'ready', url: `https://github.com/${owner}/${name}` };
69
+ }
70
+ }
71
+ if (connection.nodes.length || connection.pageInfo.hasNextPage) { return { state: 'unknown', url: null }; }
72
+ return readFork(request('GET', `repos/${login}/openyida`, options), login);
73
+ }
74
+
75
+ /** Read account, Star and personal Fork state without changing it; unknown is never treated as absent. */
76
+ function inspect(options) {
77
+ const account = request('GET', 'user', options);
78
+ const login = account.data?.login;
79
+ const base = { success: true, repository: REPOSITORY, links, mutationPerformed: false, issueRequiresStarOrFork: false };
80
+ if (!account.ok || account.data?.type !== 'User' || !/^[A-Za-z0-9][A-Za-z0-9-]{0,38}$/.test(login || '')) {
81
+ return { ...base, authenticated: false, reason: account.unavailable ? 'gh_unavailable' : 'account_unverified',
82
+ login: null, starred: null, fork: { state: 'unknown', url: null }, suggestedActions: [] };
83
+ }
84
+ const star = request('GET', `user/starred/${REPOSITORY}`, options);
85
+ const starred = star.ok && star.status === 204 ? true : star.status === 404 ? false : null;
86
+ const fork = inspectFork(login, options);
87
+ return { ...base, authenticated: true, login, starred, fork,
88
+ suggestedActions: [...(starred === false ? ['star'] : []), ...(fork.state === 'absent' ? ['fork'] : [])] };
89
+ }
90
+
91
+ /** Execute one explicitly authorized action, then read it back. No retry, clone, remote change or Issue submission. */
92
+ function execute(parsed, options = {}) {
93
+ const { action } = parseArgs([parsed.action, ...(parsed.confirm === true ? ['--confirm'] : [])]);
94
+ if ((options.env || process.env).OPENYIDA_MANAGED_RUN === '1') {
95
+ throw new CliError(t('feedback_github.failed'), { code: 'FEEDBACK_GITHUB_MANAGED_DENIED' });
96
+ }
97
+ const result = inspect(options);
98
+ if (action === 'status') { return result; }
99
+ const fail = (stage, mutationPerformed = false) => {
100
+ throw new CliError(t('feedback_github.failed'), { code: 'FEEDBACK_GITHUB_FAILED',
101
+ details: { stage, action, mutationPerformed, inspectBeforeRetry: true } });
102
+ };
103
+ if (!result.authenticated) { fail('account'); }
104
+ if (action === 'star') {
105
+ if (result.starred === true) { return { ...result, action, alreadyApplied: true }; }
106
+ if (result.starred !== false) { fail('star-status'); }
107
+ const written = request('PUT', `user/starred/${REPOSITORY}`, options);
108
+ if (!written.ok) { fail('star-write', 'unknown'); }
109
+ const verified = request('GET', `user/starred/${REPOSITORY}`, options);
110
+ if (!verified.ok || verified.status !== 204) { fail('star-readback', true); }
111
+ return { ...result, action, starred: true, mutationPerformed: true, readbackVerified: true,
112
+ suggestedActions: result.suggestedActions.filter(item => item !== 'star') };
113
+ }
114
+ if (result.fork.state === 'ready') { return { ...result, action, alreadyApplied: true }; }
115
+ if (result.fork.state !== 'absent') { fail('fork-status'); }
116
+ const created = request('POST', `repos/${REPOSITORY}/forks`, options, ['--field', 'default_branch_only=true']);
117
+ if (!created.ok) { fail('fork-write', 'unknown'); }
118
+ const fork = readFork(request('GET', `repos/${result.login}/openyida`, options), result.login);
119
+ return { ...result, action, fork, mutationPerformed: true, readbackVerified: fork.state === 'ready',
120
+ completion: fork.state === 'ready' ? 'verified' : 'pending-verification',
121
+ suggestedActions: result.suggestedActions.filter(item => item !== 'fork') };
122
+ }
123
+
124
+ /** CLI entrypoint. The confirmation flag attests to the user's explicit choice of this action. */
125
+ function run(args = []) {
126
+ const parsed = parseArgs(args);
127
+ console.log(JSON.stringify(execute(parsed), null, 2));
128
+ }
129
+
130
+ module.exports = { parseArgs, execute, run };