openyida 2026.10.18-2 → 2026.10.19
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 +12 -1
- package/bin/yida.js +5 -0
- package/lib/app/canvas-compile.js +4 -4
- package/lib/app/form-view.js +332 -0
- package/lib/core/command-manifest.js +29 -3
- package/lib/core/locales/en.js +30 -1
- package/lib/core/locales/zh.js +30 -1
- package/lib/core/update.js +2 -0
- package/lib/feedback/feedback.js +13 -1
- package/lib/feedback/report.js +239 -0
- package/package.json +1 -1
- package/scripts/postinstall.js +5 -1
- package/yida-skills/SKILL.md +10 -2
- package/yida-skills/skills/yida-app/workflow/step-4-forms-processes.md +1 -0
- package/yida-skills/skills/yida-data-manage-ui/SKILL.md +2 -0
- package/yida-skills/skills/yida-feedback/SKILL.md +45 -0
- package/yida-skills/skills/yida-feedback/references/github-contribution.md +26 -0
- package/yida-skills/skills/yida-feedback/references/report-input.md +52 -0
- package/yida-skills/skills/yida-feedback/references/user-interaction.md +50 -0
- package/yida-skills/skills/yida-form-view/SKILL.md +58 -0
- package/yida-skills/skills/yida-voc/SKILL.md +2 -0
- package/yida-skills/skills-index.json +82 -2
package/README.md
CHANGED
|
@@ -420,6 +420,16 @@ The opt-in aggregate real-E2E runner additionally requires `OPENYIDA_AGGREGATE_E
|
|
|
420
420
|
|
|
421
421
|
Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
422
422
|
|
|
423
|
+
### Report a problem or contribute a fix
|
|
424
|
+
|
|
425
|
+
Tell your AI: “OpenYida keeps failing. Analyze the cause and prepare feedback.” You can prepare a redacted report and reproduction steps without a GitHub account or a local source checkout. Preparing a report does not submit it.
|
|
426
|
+
|
|
427
|
+
```bash
|
|
428
|
+
openyida feedback report --input summary.json --output-dir feedback-draft-001
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
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).
|
|
432
|
+
|
|
423
433
|
<!-- OPENYIDA_COMMANDS_START -->
|
|
424
434
|
<!-- This section is generated by `npm run docs:commands`. Do not edit command rows by hand. -->
|
|
425
435
|
|
|
@@ -480,6 +490,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
480
490
|
| `openyida create-form add-option <appType> <formUuid> <fieldLabel> <option1> [option2] ...` | Update a form page |
|
|
481
491
|
| `openyida list-forms <appType> [--keyword <text>]` | List forms/pages in an app |
|
|
482
492
|
| `openyida aggregate-table <list\|create-empty\|inspect\|preview\|save\|publish\|status> <appType> ...` | Manage aggregate tables (virtualView) |
|
|
493
|
+
| `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 |
|
|
483
494
|
| `openyida get-schema <appType> <formUuid\|--all> [--summary-json\|--field-map-json\|--analysis-json]` | Get one form Schema or all form Schemas |
|
|
484
495
|
| `openyida check-prd-completeness <prd.md> --app-type <appType> [--build-manifest <file>] [--json]` | Check resources, frontend menu hiding and application theme delivery |
|
|
485
496
|
| `openyida check-design <design.md> [--prd <prd.md>] [--base-dir <dir>] [--json]` | Validate design documents, theme tokens and PRD handoff |
|
|
@@ -590,7 +601,7 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
590
601
|
| `openyida formula evaluate <formula\|file> [--schema file]` | Static-check Yida formula syntax and field refs |
|
|
591
602
|
| `openyida update` | Check and update to latest version |
|
|
592
603
|
| `openyida export-conversation [options]` | Export AI conversation records |
|
|
593
|
-
| `openyida feedback <setup\|url\|dismiss\|status> [options]` |
|
|
604
|
+
| `openyida feedback <report\|setup\|url\|dismiss\|status> [options]` | Prepare local issue reports, configure feedback forms and reminders |
|
|
594
605
|
| `openyida batch <file>\|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]` | Run OpenYida commands in batch |
|
|
595
606
|
| `openyida flash-to-prd --file <path> --name "<project>"` | Convert flash notes or meeting notes to a PRD prompt |
|
|
596
607
|
| `openyida ai <text\|image> [options]` | Call Yida AI text and image recognition APIs |
|
package/bin/yida.js
CHANGED
|
@@ -815,6 +815,11 @@ async function main() {
|
|
|
815
815
|
break;
|
|
816
816
|
}
|
|
817
817
|
|
|
818
|
+
case 'form-view': {
|
|
819
|
+
const { run } = require('../lib/app/form-view');
|
|
820
|
+
await run(args);
|
|
821
|
+
break;
|
|
822
|
+
}
|
|
818
823
|
case 'aggregate-table': {
|
|
819
824
|
const { run } = require('../lib/aggregate-table/aggregate-table');
|
|
820
825
|
await run(args);
|
|
@@ -513,19 +513,19 @@ function findUnavailableCanvasInstanceApiIssues(source) {
|
|
|
513
513
|
const guardPlugin = ({ types: types }) => ({
|
|
514
514
|
name: 'yida-canvas-instance-api-guard',
|
|
515
515
|
visitor: {
|
|
516
|
-
MemberExpression(path) {
|
|
516
|
+
'MemberExpression|OptionalMemberExpression'(path) {
|
|
517
517
|
const object = path.node.object;
|
|
518
518
|
const property = path.node.property;
|
|
519
519
|
const propertyName = path.node.computed && types.isStringLiteral(property)
|
|
520
520
|
? property.value
|
|
521
|
-
: (types.isIdentifier(property) ? property.name : '');
|
|
521
|
+
: (!path.node.computed && types.isIdentifier(property) ? property.name : '');
|
|
522
522
|
|
|
523
|
-
if (types.isMemberExpression(object)) {
|
|
523
|
+
if (types.isMemberExpression(object) || types.isOptionalMemberExpression(object)) {
|
|
524
524
|
const owner = object.object;
|
|
525
525
|
const ownerProperty = object.property;
|
|
526
526
|
const ownerPropertyName = object.computed && types.isStringLiteral(ownerProperty)
|
|
527
527
|
? ownerProperty.value
|
|
528
|
-
: (types.isIdentifier(ownerProperty) ? ownerProperty.name : '');
|
|
528
|
+
: (!object.computed && types.isIdentifier(ownerProperty) ? ownerProperty.name : '');
|
|
529
529
|
if (
|
|
530
530
|
types.isIdentifier(owner, { name: 'props' }) &&
|
|
531
531
|
ownerPropertyName === 'utils' &&
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const { isDeepStrictEqual } = require('util');
|
|
7
|
+
const { CliError } = require('../core/cli-error');
|
|
8
|
+
const { t } = require('../core/i18n');
|
|
9
|
+
const { resolveProjectRoot } = require('../core/utils');
|
|
10
|
+
const { createAuthRef, createYidaClient } = require('../core/yida-client');
|
|
11
|
+
const { buildYidaTitleI18n } = require('../core/yida-i18n');
|
|
12
|
+
|
|
13
|
+
const ACTIONS = ['list', 'inspect', 'save', 'create', 'order'];
|
|
14
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
15
|
+
|
|
16
|
+
function fail(code, key, detail, details) {
|
|
17
|
+
throw new CliError(t(`form_view.${key}`, detail || ''), { code: `FORM_VIEW_${code}`, details });
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function parseJson(value, label) {
|
|
21
|
+
try { return typeof value === 'string' ? JSON.parse(value) : value; }
|
|
22
|
+
catch (_) { return fail('INVALID_CONFIG', 'invalid_config', label); }
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertObject(value, label) {
|
|
26
|
+
if (!object(value)) { fail('INVALID_CONFIG', 'invalid_config', label); }
|
|
27
|
+
return value;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function assertSafeKeys(value) {
|
|
31
|
+
if (!value || typeof value !== 'object') { return; }
|
|
32
|
+
for (const key of Object.keys(value)) {
|
|
33
|
+
if (['__proto__', 'prototype', 'constructor'].includes(key)) {
|
|
34
|
+
fail('INVALID_CONFIG', 'invalid_config', key);
|
|
35
|
+
}
|
|
36
|
+
assertSafeKeys(value[key]);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function normalizeConfig(raw, viewUuid) {
|
|
41
|
+
assertObject(raw, 'viewConfig');
|
|
42
|
+
if (raw.viewUuid !== viewUuid) { fail('TARGET_MISMATCH', 'target_mismatch', viewUuid); }
|
|
43
|
+
return {
|
|
44
|
+
...raw,
|
|
45
|
+
viewUuid,
|
|
46
|
+
staticConfig: assertObject(parseJson(raw.staticConfig || {}, 'staticConfig'), 'staticConfig'),
|
|
47
|
+
defaultConfig: assertObject(parseJson(raw.defaultConfig || {}, 'defaultConfig'), 'defaultConfig'),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function stable(value) {
|
|
52
|
+
if (Array.isArray(value)) { return value.map(stable); }
|
|
53
|
+
if (!object(value)) { return value; }
|
|
54
|
+
return Object.fromEntries(Object.keys(value).sort().map(key => [key, stable(value[key])]));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function configHash(value) {
|
|
58
|
+
return crypto.createHash('sha256').update(JSON.stringify(stable({
|
|
59
|
+
staticConfig: value.staticConfig, defaultConfig: value.defaultConfig,
|
|
60
|
+
}))).digest('hex');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function writableConfig(value) {
|
|
64
|
+
// updateConfig generates this timestamp; it is not a user setting.
|
|
65
|
+
const defaultConfig = { ...value.defaultConfig };
|
|
66
|
+
delete defaultConfig.latestUpdateTime;
|
|
67
|
+
return { staticConfig: value.staticConfig, defaultConfig };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Objects merge recursively; arrays and complete condition expressions replace.
|
|
71
|
+
function merge(base, patch) {
|
|
72
|
+
const result = { ...base };
|
|
73
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
74
|
+
result[key] = object(value) && !['filter', 'dataRange'].includes(key)
|
|
75
|
+
? merge(object(base[key]) ? base[key] : {}, value) : value;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function validatePatch(patch) {
|
|
81
|
+
assertObject(patch, 'config');
|
|
82
|
+
assertSafeKeys(patch);
|
|
83
|
+
if (!Object.keys(patch).length || Object.keys(patch).some(key => !['staticConfig', 'defaultConfig'].includes(key))) {
|
|
84
|
+
fail('INVALID_CONFIG', 'invalid_config', 'staticConfig / defaultConfig');
|
|
85
|
+
}
|
|
86
|
+
for (const key of Object.keys(patch)) { assertObject(patch[key], key); }
|
|
87
|
+
const def = patch.defaultConfig || {};
|
|
88
|
+
const stat = patch.staticConfig || {};
|
|
89
|
+
for (const [key, value] of [['showFields', def.showFields], ['lockFieldIds', def.lockFieldIds], ['columnFilter', stat.columnFilter]]) {
|
|
90
|
+
if (value === undefined || (key === 'showFields' && value === 'all')) { continue; }
|
|
91
|
+
if (!Array.isArray(value) || value.some(id => typeof id !== 'string' || !id.trim()) || new Set(value).size !== value.length) {
|
|
92
|
+
fail('INVALID_CONFIG', 'invalid_config', key);
|
|
93
|
+
}
|
|
94
|
+
if (key === 'showFields' && !value.length) { fail('INVALID_CONFIG', 'invalid_config', key); }
|
|
95
|
+
}
|
|
96
|
+
if (def.widths !== undefined && def.widths !== null) {
|
|
97
|
+
assertObject(def.widths, 'widths');
|
|
98
|
+
for (const width of Object.values(def.widths)) {
|
|
99
|
+
if (typeof width !== 'number' || !Number.isFinite(width) || width <= 0) { fail('INVALID_CONFIG', 'invalid_config', 'widths'); }
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (def.filter !== undefined) { assertObject(def.filter, 'filter'); }
|
|
103
|
+
for (const key of ['values', 'operationValues']) {
|
|
104
|
+
if (def.filter?.[key] !== undefined) { assertObject(def.filter[key], `filter.${key}`); }
|
|
105
|
+
}
|
|
106
|
+
if (def.filter?.conditionType !== undefined && !['AND', 'OR'].includes(def.filter.conditionType)) {
|
|
107
|
+
fail('INVALID_CONFIG', 'invalid_config', 'filter.conditionType');
|
|
108
|
+
}
|
|
109
|
+
if (def.sort !== undefined && !Array.isArray(def.sort)) { fail('INVALID_CONFIG', 'invalid_config', 'sort'); }
|
|
110
|
+
if (def.sort?.some(rule => !object(rule) || typeof rule.id !== 'string' || !['y', 'n'].includes(rule.isAsc))) {
|
|
111
|
+
fail('INVALID_CONFIG', 'invalid_config', 'sort');
|
|
112
|
+
}
|
|
113
|
+
if (stat.dataRange !== undefined) { assertObject(stat.dataRange, 'dataRange'); }
|
|
114
|
+
return patch;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function panelFields(panel) {
|
|
118
|
+
if (!object(panel) || !Array.isArray(panel.componentValueVoList)) { fail('UNSUPPORTED', 'unsupported'); }
|
|
119
|
+
const fields = [];
|
|
120
|
+
const visit = (items, parentId) => items.forEach(item => {
|
|
121
|
+
const id = item.componentId || item.id;
|
|
122
|
+
if (id) { fields.push({ id, parentId, name: item.varName, componentName: item.componentName,
|
|
123
|
+
supportVisible: item.supportVisible, showTerm: item.showTerm, operatorMap: item.operatorMap }); }
|
|
124
|
+
if (Array.isArray(item.children)) { visit(item.children, id); }
|
|
125
|
+
});
|
|
126
|
+
visit(panel.componentValueVoList);
|
|
127
|
+
return fields;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function validateFields(patch, fields) {
|
|
131
|
+
const known = new Map(fields.map(field => [field.id, field]));
|
|
132
|
+
const def = patch.defaultConfig || {};
|
|
133
|
+
const stat = patch.staticConfig || {};
|
|
134
|
+
const refs = [
|
|
135
|
+
...(Array.isArray(def.showFields) ? def.showFields : []), ...(def.lockFieldIds || []),
|
|
136
|
+
...Object.keys(def.widths || {}), ...(stat.columnFilter || []).filter(id => id !== 'all'),
|
|
137
|
+
...Object.keys(def.filter?.values || {}), ...Object.keys(def.filter?.operationValues || {}),
|
|
138
|
+
...(def.sort || []).map(rule => rule.id),
|
|
139
|
+
];
|
|
140
|
+
for (const field of fields) {
|
|
141
|
+
if (['CascadeDateField', 'CascadeTimeZoneDateField', 'AddressField'].includes(field.componentName)) {
|
|
142
|
+
for (const suffix of ['_0', '_1']) { known.set(field.id + suffix, field); }
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
for (const id of refs) {
|
|
146
|
+
if (!known.has(id)) { fail('INVALID_CONFIG', 'invalid_config', `fieldId: ${id}`); }
|
|
147
|
+
}
|
|
148
|
+
for (const [id, op] of Object.entries(def.filter?.operationValues || {})) {
|
|
149
|
+
const operators = known.get(id)?.operatorMap;
|
|
150
|
+
if (Array.isArray(operators) && !operators.some(item => item.value === op)) {
|
|
151
|
+
fail('INVALID_CONFIG', 'invalid_config', `filter.operationValues.${id}: ${op}`);
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function parseArgs(args) {
|
|
157
|
+
const positional = [];
|
|
158
|
+
const flags = {};
|
|
159
|
+
for (let i = 0; i < args.length; i++) {
|
|
160
|
+
const arg = args[i];
|
|
161
|
+
if (['--json', '--with-config', '--replace', '--help', '-h'].includes(arg)) { flags[arg] = true; continue; }
|
|
162
|
+
if (['--config', '--name', '--expected-hash', '--backup'].includes(arg)) {
|
|
163
|
+
if (!args[i + 1] || args[i + 1].startsWith('--')) { fail('USAGE', 'invalid_config', arg); }
|
|
164
|
+
flags[arg] = args[++i];
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
if (arg.startsWith('-')) { fail('USAGE', 'invalid_config', arg); }
|
|
168
|
+
positional.push(arg);
|
|
169
|
+
}
|
|
170
|
+
if (flags['--help'] || flags['-h']) { return { help: true }; }
|
|
171
|
+
const [action, appType, formUuid, viewUuid] = positional;
|
|
172
|
+
if (!ACTIONS.includes(action) || !appType || !formUuid || positional.length > (['inspect', 'save'].includes(action) ? 4 : 3)
|
|
173
|
+
|| (['inspect', 'save'].includes(action) && !viewUuid)
|
|
174
|
+
|| (['save', 'order'].includes(action) && !flags['--config'])
|
|
175
|
+
|| (action === 'create' && !flags['--name']?.trim())) {
|
|
176
|
+
fail('USAGE', 'usage');
|
|
177
|
+
}
|
|
178
|
+
const allowed = { list: ['--with-config'], inspect: [], save: ['--config', '--expected-hash', '--backup', '--replace'], create: ['--name', '--backup'], order: ['--config', '--backup'] };
|
|
179
|
+
if (Object.keys(flags).some(flag => flag !== '--json' && !allowed[action].includes(flag))) { fail('USAGE', 'usage'); }
|
|
180
|
+
// IDs are path segments, never URLs or traversal components.
|
|
181
|
+
for (const id of [appType, formUuid, viewUuid].filter(Boolean)) {
|
|
182
|
+
if (!/^[A-Za-z0-9_-]+$/.test(id)) { fail('USAGE', 'invalid_config', id); }
|
|
183
|
+
}
|
|
184
|
+
return { action, appType, formUuid, viewUuid, flags };
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
function unwrap(response, endpoint) {
|
|
188
|
+
if (!response || response.success !== true || response.__needLogin || response.__csrfExpired) {
|
|
189
|
+
fail('API_ERROR', 'api_error', response?.errorMsg || response?.message || endpoint, {
|
|
190
|
+
endpoint, platformErrorCode: response?.errorCode,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
return response.content;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function backup(value, filename) {
|
|
197
|
+
const output = filename ? path.resolve(filename) : path.join(resolveProjectRoot().projectRoot,
|
|
198
|
+
'.cache', 'openyida', 'form-view', `${Date.now()}-${crypto.randomUUID()}.json`);
|
|
199
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
200
|
+
fs.writeFileSync(output, JSON.stringify(value, null, 2), { flag: 'wx', mode: 0o600 });
|
|
201
|
+
return output;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function execute(parsed, client = createYidaClient({ authRef: createAuthRef() })) {
|
|
205
|
+
const { action, appType, formUuid, viewUuid, flags } = parsed;
|
|
206
|
+
const prefix = `/dingtalk/web/${appType}/query/`;
|
|
207
|
+
const target = { appType, formUuid, ...(viewUuid ? { viewUuid } : {}) };
|
|
208
|
+
const read = async (endpoint, params) => unwrap(await client.postForm(prefix + endpoint, params), endpoint);
|
|
209
|
+
const list = async () => {
|
|
210
|
+
const form = await read('formdesign/getFormSchemaInfo.json', { formUuid });
|
|
211
|
+
if (!object(form) || !Array.isArray(form.multiViewInfos)) { fail('UNSUPPORTED', 'unsupported'); }
|
|
212
|
+
if (form.multiViewInfos.some(view => !object(view) || typeof view.viewUuid !== 'string' || !/^[A-Za-z0-9_-]+$/.test(view.viewUuid))
|
|
213
|
+
|| new Set(form.multiViewInfos.map(view => view.viewUuid)).size !== form.multiViewInfos.length) { fail('UNSUPPORTED', 'unsupported'); }
|
|
214
|
+
return form.multiViewInfos.map(view => ({
|
|
215
|
+
...view, name: parseJson(view.name, 'name'), status: parseJson(view.status, 'status'),
|
|
216
|
+
}));
|
|
217
|
+
};
|
|
218
|
+
const get = async (id) => normalizeConfig(await read('dataview/getViewConfig.json', { formUuid, viewUuid: id }), id);
|
|
219
|
+
const fields = async (id) => panelFields(unwrap(await client.get(prefix + 'dataview/getDataViewPanel.json', {
|
|
220
|
+
formUuid, viewUuid: id, pageType: 'manage', ccMode: 'tile',
|
|
221
|
+
}), 'dataview/getDataViewPanel.json'));
|
|
222
|
+
const views = await list();
|
|
223
|
+
if (viewUuid && !views.some(view => view.viewUuid === viewUuid)) { fail('TARGET_MISMATCH', 'target_mismatch', viewUuid); }
|
|
224
|
+
const output = { success: true, action, ...target };
|
|
225
|
+
if (action === 'list') {
|
|
226
|
+
const configs = [];
|
|
227
|
+
for (const view of views) {
|
|
228
|
+
configs.push(flags['--with-config'] ? { ...view, config: await get(view.viewUuid) } : view);
|
|
229
|
+
}
|
|
230
|
+
return { ...output, views: configs, totalCount: views.length };
|
|
231
|
+
}
|
|
232
|
+
if (action === 'inspect') {
|
|
233
|
+
const config = await get(viewUuid);
|
|
234
|
+
const view = views.find(item => item.viewUuid === viewUuid);
|
|
235
|
+
return { ...output, view, config, configHash: configHash(config), ...(view.viewType === 'table' ? { fields: await fields(viewUuid) } : {}) };
|
|
236
|
+
}
|
|
237
|
+
let params;
|
|
238
|
+
let endpoint;
|
|
239
|
+
let before;
|
|
240
|
+
let expected;
|
|
241
|
+
if (action === 'save') {
|
|
242
|
+
if (views.find(view => view.viewUuid === viewUuid).viewType !== 'table') { fail('UNSUPPORTED', 'unsupported'); }
|
|
243
|
+
const patch = validatePatch(parseJson(fs.readFileSync(flags['--config'], 'utf8'), 'config'));
|
|
244
|
+
if (flags['--replace'] && (!patch.staticConfig || !patch.defaultConfig || !flags['--expected-hash'])) {
|
|
245
|
+
fail('INVALID_CONFIG', 'invalid_config', '--replace requires staticConfig, defaultConfig, --expected-hash');
|
|
246
|
+
}
|
|
247
|
+
validateFields(patch, await fields(viewUuid));
|
|
248
|
+
before = await get(viewUuid);
|
|
249
|
+
if (flags['--expected-hash'] && flags['--expected-hash'] !== configHash(before)) { fail('CONFLICT', 'conflict'); }
|
|
250
|
+
expected = {
|
|
251
|
+
staticConfig: flags['--replace'] ? patch.staticConfig : merge(before.staticConfig, patch.staticConfig || {}),
|
|
252
|
+
defaultConfig: flags['--replace'] ? { ...patch.defaultConfig } : merge(before.defaultConfig, patch.defaultConfig || {}),
|
|
253
|
+
};
|
|
254
|
+
delete expected.defaultConfig.latestUpdateTime;
|
|
255
|
+
// Validate the patch, while preserving platform-owned or legacy settings as read.
|
|
256
|
+
if (Array.isArray(expected.defaultConfig.showFields) && expected.defaultConfig.lockFieldIds?.some(id => !expected.defaultConfig.showFields.includes(id))) {
|
|
257
|
+
fail('INVALID_CONFIG', 'invalid_config', 'lockFieldIds / showFields');
|
|
258
|
+
}
|
|
259
|
+
if (Array.isArray(expected.staticConfig.columnFilter) && !expected.staticConfig.columnFilter.includes('all')
|
|
260
|
+
&& Array.isArray(expected.defaultConfig.showFields)
|
|
261
|
+
&& expected.defaultConfig.showFields.some(id => !expected.staticConfig.columnFilter.includes(id))) {
|
|
262
|
+
fail('INVALID_CONFIG', 'invalid_config', 'columnFilter / showFields');
|
|
263
|
+
}
|
|
264
|
+
if (isDeepStrictEqual(writableConfig(expected), writableConfig(before))) {
|
|
265
|
+
return { ...output, changed: false, readbackVerified: true, config: before, configHash: configHash(before) };
|
|
266
|
+
}
|
|
267
|
+
// Re-read immediately before writing; the platform has no compare-and-swap API.
|
|
268
|
+
if (configHash(await get(viewUuid)) !== configHash(before)) { fail('CONFLICT', 'conflict'); }
|
|
269
|
+
params = { formUuid, viewUuid, staticConfig: JSON.stringify(expected.staticConfig), defaultConfig: JSON.stringify(expected.defaultConfig) };
|
|
270
|
+
endpoint = 'dataview/updateConfig.json';
|
|
271
|
+
} else if (action === 'order') {
|
|
272
|
+
expected = parseJson(fs.readFileSync(flags['--config'], 'utf8'), 'order');
|
|
273
|
+
const ids = views.map(view => view.viewUuid);
|
|
274
|
+
if (!Array.isArray(expected) || expected.length !== ids.length || new Set(expected).size !== ids.length || expected.some(id => !ids.includes(id))) {
|
|
275
|
+
fail('INVALID_CONFIG', 'invalid_config', 'order');
|
|
276
|
+
}
|
|
277
|
+
before = ids;
|
|
278
|
+
if (isDeepStrictEqual(before, expected)) { return { ...output, changed: false, views, readbackVerified: true }; }
|
|
279
|
+
if (!isDeepStrictEqual((await list()).map(view => view.viewUuid), before)) { fail('CONFLICT', 'conflict'); }
|
|
280
|
+
params = { formUuid, viewConfig: JSON.stringify(expected) };
|
|
281
|
+
endpoint = 'dataview/updateViewPageConfig.json';
|
|
282
|
+
} else {
|
|
283
|
+
expected = flags['--name'].trim();
|
|
284
|
+
before = views;
|
|
285
|
+
params = { formUuid, dataFormUuid: formUuid, viewType: 'table', name: JSON.stringify(buildYidaTitleI18n(expected)),
|
|
286
|
+
status: JSON.stringify({ PC: 'ACTIVATED', MOBILE: 'ACTIVATED' }) };
|
|
287
|
+
endpoint = 'dataview/createView.json';
|
|
288
|
+
}
|
|
289
|
+
const backupPath = backup({ ...target, action, config: before }, flags['--backup']);
|
|
290
|
+
const mutationDetails = { target, backupPath, retrySafe: false, sideEffectState: 'unknown', readbackAllowed: true };
|
|
291
|
+
try {
|
|
292
|
+
// Never replay a mutation automatically after transport failure or auth refresh.
|
|
293
|
+
unwrap(await client.postFormOnce(prefix + endpoint, params), endpoint);
|
|
294
|
+
} catch (error) {
|
|
295
|
+
throw new CliError(error.message, { code: error.code || 'FORM_VIEW_WRITE_FAILED', details: mutationDetails });
|
|
296
|
+
}
|
|
297
|
+
try {
|
|
298
|
+
if (action === 'save') {
|
|
299
|
+
const config = await get(viewUuid);
|
|
300
|
+
if (!isDeepStrictEqual(stable(writableConfig(expected)), stable(writableConfig(config)))) {
|
|
301
|
+
fail('READBACK_MISMATCH', 'readback_mismatch');
|
|
302
|
+
}
|
|
303
|
+
return { ...output, changed: true, backupPath, readbackVerified: true, config, configHash: configHash(config) };
|
|
304
|
+
}
|
|
305
|
+
const after = await list();
|
|
306
|
+
if (action === 'order' && !isDeepStrictEqual(after.map(view => view.viewUuid), expected)) { fail('READBACK_MISMATCH', 'readback_mismatch'); }
|
|
307
|
+
if (action === 'create') {
|
|
308
|
+
const added = after.filter(view => !before.some(old => old.viewUuid === view.viewUuid));
|
|
309
|
+
if (added.length !== 1 || !Object.values(added[0].name || {}).includes(expected)) { fail('READBACK_MISMATCH', 'readback_mismatch'); }
|
|
310
|
+
return { ...output, viewUuid: added[0].viewUuid, view: added[0], backupPath, readbackVerified: true };
|
|
311
|
+
}
|
|
312
|
+
return { ...output, views: after, backupPath, readbackVerified: true };
|
|
313
|
+
} catch (error) {
|
|
314
|
+
throw new CliError(error.message, { code: error.code || 'FORM_VIEW_READBACK_FAILED',
|
|
315
|
+
details: { ...mutationDetails, mutationAccepted: true, sideEffectState: 'accepted_unverified' } });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
async function run(args, options = {}) {
|
|
320
|
+
const parsed = parseArgs(args);
|
|
321
|
+
if (parsed.help) { process.stdout.write(t('form_view.usage') + '\n'); return; }
|
|
322
|
+
let result;
|
|
323
|
+
try { result = await execute(parsed, options.client); }
|
|
324
|
+
catch (error) {
|
|
325
|
+
if (error.isCliError) { throw error; }
|
|
326
|
+
throw new CliError(error.message, { code: 'FORM_VIEW_FAILED' });
|
|
327
|
+
}
|
|
328
|
+
process.stdout.write(JSON.stringify(result, null, 2) + '\n');
|
|
329
|
+
return result;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = { run, execute, parseArgs, normalizeConfig, configHash, merge, validatePatch };
|
|
@@ -526,6 +526,12 @@ const COMMAND_SIDE_EFFECTS = new Map([
|
|
|
526
526
|
mutating_actions: ['connect', 'run', 'disconnect', 'logout'],
|
|
527
527
|
note: 'Explicit local-agent control. Remote runs use only the current task grant and never read a personal OpenYida profile.',
|
|
528
528
|
})],
|
|
529
|
+
['form-view', sideEffect('mixed', {
|
|
530
|
+
mutates_yida: true,
|
|
531
|
+
mutates_local: true,
|
|
532
|
+
read_actions: ['list', 'inspect'],
|
|
533
|
+
mutating_actions: ['save', 'create', 'order'],
|
|
534
|
+
})],
|
|
529
535
|
['aggregate-table', sideEffect('mixed', {
|
|
530
536
|
mutates_yida: true,
|
|
531
537
|
mutates_local: false,
|
|
@@ -616,7 +622,7 @@ const COMMAND_SIDE_EFFECTS = new Map([
|
|
|
616
622
|
mutates_yida: true,
|
|
617
623
|
mutates_local: true,
|
|
618
624
|
read_actions: ['url', 'status'],
|
|
619
|
-
mutating_actions: ['setup', 'dismiss'],
|
|
625
|
+
mutating_actions: ['setup', 'dismiss', 'report'],
|
|
620
626
|
})],
|
|
621
627
|
['i18n', sideEffect('mixed', {
|
|
622
628
|
mutates_yida: true,
|
|
@@ -808,6 +814,12 @@ const COMMAND_PERMISSIONS = new Map([
|
|
|
808
814
|
ask_actions: ['connect', 'run', 'disconnect', 'logout'],
|
|
809
815
|
})),
|
|
810
816
|
|
|
817
|
+
...permissionEntries([
|
|
818
|
+
'form-view',
|
|
819
|
+
], actionDependentPermission({
|
|
820
|
+
preauthorized_actions: ['save', 'create', 'order'],
|
|
821
|
+
})),
|
|
822
|
+
|
|
811
823
|
...permissionEntries([
|
|
812
824
|
'aggregate-table',
|
|
813
825
|
], actionDependentPermission({
|
|
@@ -934,7 +946,7 @@ const COMMAND_PERMISSIONS = new Map([
|
|
|
934
946
|
...permissionEntries([
|
|
935
947
|
'feedback',
|
|
936
948
|
], actionDependentPermission({
|
|
937
|
-
preauthorized_actions: ['setup', 'dismiss'],
|
|
949
|
+
preauthorized_actions: ['setup', 'dismiss', 'report'],
|
|
938
950
|
})),
|
|
939
951
|
|
|
940
952
|
...permissionEntries([
|
|
@@ -1496,6 +1508,18 @@ const COMMAND_GROUPS = [
|
|
|
1496
1508
|
},
|
|
1497
1509
|
output: 'json',
|
|
1498
1510
|
}),
|
|
1511
|
+
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', {
|
|
1512
|
+
managedRun: { scope: 'actions', actions: {
|
|
1513
|
+
list: { scope: 'app', appPosition: 1 },
|
|
1514
|
+
inspect: { scope: 'app', appPosition: 1 },
|
|
1515
|
+
save: { scope: 'app', appPosition: 1, mutation: { operation: 'update_form', formUuidPosition: 2 } },
|
|
1516
|
+
create: { scope: 'app', appPosition: 1, mutation: { operation: 'update_form', formUuidPosition: 2 } },
|
|
1517
|
+
order: { scope: 'app', appPosition: 1, mutation: { operation: 'update_form', formUuidPosition: 2 } },
|
|
1518
|
+
} },
|
|
1519
|
+
output: 'json',
|
|
1520
|
+
examples: ['openyida form-view list APP_XXX FORM_XXX --with-config --json', 'openyida form-view save APP_XXX FORM_XXX VIEW_XXX --config view-patch.json --json'],
|
|
1521
|
+
notes: ['Reads native multiViewInfos and per-view staticConfig/defaultConfig. Saves a recursive object patch; arrays, filter and dataRange replace. --replace restores full staticConfig/defaultConfig and requires --expected-hash. showFields controls visibility and order, widths controls pixel widths, lockFieldIds controls frozen columns, filter controls default filtering, staticConfig.columnFilter controls available fields. Backups precede mutations and success requires readback. --expected-hash detects stale edits; the platform has no atomic compare-and-swap. Personal browser view settings can override published defaults. create adds a table view; order requires every existing view UUID exactly once. No delete operation.'],
|
|
1522
|
+
}),
|
|
1499
1523
|
command('get-schema', ['get-schema'], 'get-schema <appType> <formUuid|--all> [--summary-json|--field-map-json|--analysis-json]', 'help.cmd_get_schema', { managedRun: { scope: 'app', appPosition: 0 },
|
|
1500
1524
|
examples: ['openyida get-schema APP_XXX FORM_XXX --field-map-json'],
|
|
1501
1525
|
}),
|
|
@@ -1864,9 +1888,11 @@ const COMMAND_GROUPS = [
|
|
|
1864
1888
|
command('export-conversation', ['export-conversation'], 'export-conversation [options]', 'help.cmd_export_conversation', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
|
|
1865
1889
|
requiresLogin: false,
|
|
1866
1890
|
}),
|
|
1867
|
-
command('feedback', ['feedback'], 'feedback <setup|url|dismiss|status> [options]', 'help.cmd_feedback', { managedRun: { scope: '
|
|
1891
|
+
command('feedback', ['feedback'], 'feedback <report|setup|url|dismiss|status> [options]', 'help.cmd_feedback', { managedRun: { scope: 'actions', actions: { report: { scope: 'local' } } },
|
|
1868
1892
|
requiresLogin: false,
|
|
1869
1893
|
output: 'text|json',
|
|
1894
|
+
examples: ['openyida feedback report --input summary.json --output-dir feedback-draft-001'],
|
|
1895
|
+
notes: ['report prepares local review drafts only; no login, source checkout, update check or network submission.'],
|
|
1870
1896
|
}),
|
|
1871
1897
|
command('batch', ['batch'], 'batch <file>|--commands "cmd1 ; cmd2" [--stop-on-error] [--json]', 'help.cmd_batch', { managedRun: { scope: 'denied', reason: 'scope_not_audited' },
|
|
1872
1898
|
output: 'text|json',
|
package/lib/core/locales/en.js
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
+
form_view: {
|
|
8
|
+
usage: 'Usage: openyida form-view <list|inspect|save|create|order> <appType> <formUuid> [viewUuid] [--config <file>] [--name <title>] [--with-config] [--expected-hash <hash>] [--backup <file>] [--replace] [--json]',
|
|
9
|
+
invalid_config: 'Invalid native view configuration or argument: %s',
|
|
10
|
+
target_mismatch: 'The view does not belong to the target form: %s',
|
|
11
|
+
api_error: 'Native view API failed: %s. Check platform permissions and edition availability; do not retry writes blindly.',
|
|
12
|
+
unsupported: 'The platform did not return a native multi-view list. Check the form type and platform support.',
|
|
13
|
+
conflict: 'The view changed since it was read. Inspect it again before saving.',
|
|
14
|
+
readback_mismatch: 'The saved view does not match readback. Keep the backup and inspect the remote view before retrying.',
|
|
15
|
+
},
|
|
7
16
|
integration_manage: {'failed': 'Automation operation failed: {0}', 'usage': 'integration <monitor|copy|rename|delete> <appType> ...', 'desc_monitor': 'Read or save automation error monitors', 'desc_copy': 'Copy an automation as disabled', 'desc_rename': 'Rename an automation', 'desc_delete': 'Delete a disabled automation'},
|
|
8
17
|
permission_manage: {'failed': 'Permission group operation failed: {0}', 'usage': 'permission-manage <copy|rename|delete> <appType> <formUuid> <packageUuid> [--name name] [--dry-run] [--confirm]', 'description': 'Copy, rename or delete a permission group'},
|
|
9
18
|
matrix_manage: {'failed': 'Matrix operation failed: {0}', 'usage': 'matrix <list|detail|references|rows|create|update|delete|upsert-rows|delete-rows> [matrixId] [--file config.json] [--dry-run] [--confirm]', 'description': 'Manage permission matrix definitions, references and rows'},
|
|
@@ -148,6 +157,7 @@ module.exports = {
|
|
|
148
157
|
cmd_validate_form: 'Validate form field JSON locally',
|
|
149
158
|
cmd_update_form: 'Update a form page',
|
|
150
159
|
cmd_list_forms: 'List forms/pages in an app',
|
|
160
|
+
cmd_form_view: 'Native form views: list, inspect and configure columns, widths, frozen columns and filters',
|
|
151
161
|
cmd_aggregate_table: 'Manage aggregate tables (virtualView)',
|
|
152
162
|
cmd_get_schema: 'Get one form Schema or all form Schemas',
|
|
153
163
|
cmd_check_prd_completeness: 'Check resources, frontend menu hiding and application theme delivery',
|
|
@@ -224,7 +234,7 @@ module.exports = {
|
|
|
224
234
|
cmd_formula_evaluate: 'Static-check Yida formula syntax and field refs',
|
|
225
235
|
cmd_update: 'Check and update to latest version',
|
|
226
236
|
cmd_export_conversation: 'Export AI conversation records',
|
|
227
|
-
cmd_feedback: '
|
|
237
|
+
cmd_feedback: 'Prepare local issue reports, configure feedback forms and reminders',
|
|
228
238
|
cmd_batch: 'Run OpenYida commands in batch',
|
|
229
239
|
cmd_flash_to_prd: 'Convert flash notes or meeting notes to a PRD prompt',
|
|
230
240
|
cmd_ai: 'Call Yida AI text and image recognition APIs',
|
|
@@ -2341,3 +2351,22 @@ Object.assign(module.exports.integration, {
|
|
|
2341
2351
|
batch_readback_mismatch: 'Persisted batch node type, source or quantity differs from the submitted configuration.',
|
|
2342
2352
|
create_opt_spec: ' --spec <file.json> Configure batch retrieve/create, updates and branches with a structured spec',
|
|
2343
2353
|
});
|
|
2354
|
+
|
|
2355
|
+
module.exports.feedback_report = {
|
|
2356
|
+
comparison: 'Test pass/fail requires a command, actual result and evidence.',
|
|
2357
|
+
screenshots: 'Choose at most five PNG/JPEG/WebP screenshots of at most 10 MiB each.',
|
|
2358
|
+
text: 'Feedback text fields must be strings of at most 12000 characters.',
|
|
2359
|
+
list: 'Feedback lists must contain at most 40 text items.',
|
|
2360
|
+
object: 'Feedback input must be a JSON object.',
|
|
2361
|
+
required: 'Feedback input requires title and actual.',
|
|
2362
|
+
count: 'occurrenceCount must be a positive integer when known.',
|
|
2363
|
+
cause: 'cause must be an object.',
|
|
2364
|
+
confirmed: 'A confirmed cause requires a summary and evidence.',
|
|
2365
|
+
usage: 'Usage: openyida feedback report --input <summary.json> --output-dir <new-directory>',
|
|
2366
|
+
duplicate: 'Duplicate feedback report option.',
|
|
2367
|
+
options: 'Both --input and --output-dir are required.',
|
|
2368
|
+
file: 'Feedback input must be a JSON file of at most 1 MiB.',
|
|
2369
|
+
json: 'Feedback input is not valid JSON.',
|
|
2370
|
+
help: 'report prepares local review drafts without login, a source checkout or network submission.',
|
|
2371
|
+
login_hint: 'Ask your AI to prepare local feedback with yida-feedback; no GitHub account or source checkout is required.',
|
|
2372
|
+
};
|
package/lib/core/locales/zh.js
CHANGED
|
@@ -4,6 +4,15 @@
|
|
|
4
4
|
'use strict';
|
|
5
5
|
|
|
6
6
|
module.exports = {
|
|
7
|
+
form_view: {
|
|
8
|
+
usage: '用法:openyida form-view <list|inspect|save|create|order> <appType> <formUuid> [viewUuid] [--config <文件>] [--name <名称>] [--with-config] [--expected-hash <哈希>] [--backup <文件>] [--replace] [--json]',
|
|
9
|
+
invalid_config: '原生视图配置或参数无效:%s',
|
|
10
|
+
target_mismatch: '视图不属于目标表单:%s',
|
|
11
|
+
api_error: '原生视图接口失败:%s。请检查平台权限和版本能力,不要盲目重试写入。',
|
|
12
|
+
unsupported: '平台未返回原生多视图列表,请检查表单类型及平台支持情况。',
|
|
13
|
+
conflict: '视图在读取后发生变化,请重新检查配置再保存。',
|
|
14
|
+
readback_mismatch: '保存配置与回读结果不一致,请保留备份并核对远程视图后再重试。',
|
|
15
|
+
},
|
|
7
16
|
integration_manage: {'failed': '自动化操作失败:{0}', 'usage': 'integration <monitor|copy|rename|delete> <appType> ...', 'desc_monitor': '查询或保存自动化异常监听', 'desc_copy': '复制自动化,默认停用', 'desc_rename': '重命名自动化', 'desc_delete': '删除已停用的自动化'},
|
|
8
17
|
permission_manage: {'failed': '权限组操作失败:{0}', 'usage': 'permission-manage <copy|rename|delete> <appType> <formUuid> <packageUuid> [--name name] [--dry-run] [--confirm]', 'description': '复制、重命名或删除权限组'},
|
|
9
18
|
matrix_manage: {'failed': '矩阵操作失败:{0}', 'usage': 'matrix <list|detail|references|rows|create|update|delete|upsert-rows|delete-rows> [matrixId] [--file config.json] [--dry-run] [--confirm]', 'description': '管理权限矩阵定义、引用及数据行'},
|
|
@@ -148,6 +157,7 @@ module.exports = {
|
|
|
148
157
|
cmd_validate_form: '本地校验表单字段 JSON',
|
|
149
158
|
cmd_update_form: '更新表单页面',
|
|
150
159
|
cmd_list_forms: '列出应用下的表单/页面',
|
|
160
|
+
cmd_form_view: '原生表单视图:查询、检查并配置显示列、列宽、冻结列和筛选',
|
|
151
161
|
cmd_aggregate_table: '管理聚合表(virtualView)',
|
|
152
162
|
cmd_get_schema: '获取单个或全部表单 Schema',
|
|
153
163
|
cmd_check_prd_completeness: '检查资源数量、前台菜单隐藏及应用主题同步',
|
|
@@ -224,7 +234,7 @@ module.exports = {
|
|
|
224
234
|
cmd_formula_evaluate: '静态检查宜搭公式语法和字段引用',
|
|
225
235
|
cmd_update: '检查并更新到最新版本',
|
|
226
236
|
cmd_export_conversation: '导出 AI 对话记录',
|
|
227
|
-
cmd_feedback: '
|
|
237
|
+
cmd_feedback: '整理本地问题报告、配置反馈表单与提醒',
|
|
228
238
|
cmd_batch: '批量执行 OpenYida 命令',
|
|
229
239
|
cmd_flash_to_prd: '闪记 / 会议纪要转 PRD prompt',
|
|
230
240
|
cmd_ai: '调用 AI 文生文和识图能力',
|
|
@@ -2295,3 +2305,22 @@ Object.assign(module.exports.integration, {
|
|
|
2295
2305
|
batch_readback_mismatch: '平台回读的批量节点类型、来源或条数与提交不一致。',
|
|
2296
2306
|
create_opt_spec: ' --spec <file.json> 使用结构化编排配置批量取数、批量新增、更新和分支',
|
|
2297
2307
|
});
|
|
2308
|
+
|
|
2309
|
+
module.exports.feedback_report = {
|
|
2310
|
+
comparison: '测试通过或失败须提供真实命令、实际结果及证据。',
|
|
2311
|
+
screenshots: '截图须为明确选择的 PNG/JPEG/WebP,每张至多 10 MiB,最多 5 张。',
|
|
2312
|
+
text: '反馈文本字段须为不超过 12000 字符的字符串。',
|
|
2313
|
+
list: '反馈列表须为至多 40 个文本项的数组。',
|
|
2314
|
+
object: '反馈输入须为 JSON 对象。',
|
|
2315
|
+
required: '反馈输入须包含 title 和 actual。',
|
|
2316
|
+
count: '已知发生次数 occurrenceCount 须为正整数。',
|
|
2317
|
+
cause: 'cause 须为对象。',
|
|
2318
|
+
confirmed: '已确认的根因须包含说明 summary 和证据 evidence。',
|
|
2319
|
+
usage: '用法:openyida feedback report --input <summary.json> --output-dir <新目录>',
|
|
2320
|
+
duplicate: '反馈报告选项重复。',
|
|
2321
|
+
options: '必须提供 --input 和 --output-dir。',
|
|
2322
|
+
file: '反馈输入须为至多 1 MiB 的 JSON 文件。',
|
|
2323
|
+
json: '反馈输入不是有效 JSON。',
|
|
2324
|
+
help: 'report 仅生成本地待审阅材料,无需登录或源码,不联网提交。',
|
|
2325
|
+
login_hint: '可请 AI 使用 yida-feedback 整理本地反馈材料,无需 GitHub 账号或源码。',
|
|
2326
|
+
};
|
package/lib/core/update.js
CHANGED
|
@@ -158,6 +158,8 @@ function getAutoUpdateIntervalMs(env = process.env) {
|
|
|
158
158
|
}
|
|
159
159
|
|
|
160
160
|
function shouldSkipAutoUpdateCommand(command, args = []) {
|
|
161
|
+
// Report preparation must work offline even in a global npm installation.
|
|
162
|
+
if (command === 'feedback' && args[0] === 'report') {return true;}
|
|
161
163
|
if (!command || ['--help', '-h', '--version', '-v'].includes(command)) {
|
|
162
164
|
return true;
|
|
163
165
|
}
|