openyida 2026.10.12-beta.3 → 2026.10.13
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/LICENSE +20 -20
- package/README.md +16 -27
- package/bin/yida.js +7 -32
- package/lib/app/app-theme.js +289 -0
- package/lib/app/check-prd-completeness.js +4 -15
- package/lib/app/create-form/batch.js +9 -30
- package/lib/app/create-form.js +52 -10
- package/lib/app/custom-theme.js +3 -13
- package/lib/app/get-schema.js +1 -6
- package/lib/app/nav-group-create.js +69 -0
- package/lib/app/nav-group.js +8 -28
- package/lib/app/theme-from-design.js +28 -16
- package/lib/app/theme-source.js +54 -0
- package/lib/app/update-app.js +1 -13
- package/lib/app/update-form-config.js +80 -71
- package/lib/auth/token-auth.js +6 -57
- package/lib/auth/token-store.js +2 -26
- package/lib/core/command-manifest.js +168 -175
- package/lib/core/locales/en.js +23 -47
- package/lib/core/locales/zh.js +23 -47
- package/lib/core/sample.js +21 -2
- package/lib/core/theme-widget.js +115 -0
- package/lib/core/update.js +1 -3
- package/lib/core/utils.js +19 -29
- package/lib/design/document.js +8 -3
- package/lib/design/palette-contrast.js +16 -3
- package/lib/design-plan/confirmation.js +1 -1
- package/lib/design-plan/init.js +21 -6
- package/lib/design-plan/materialize.js +30 -9
- package/lib/design-plan/patch.js +4 -2
- package/lib/design-plan/themes.js +6 -1
- package/lib/design-plan/visual-policy.js +70 -8
- package/lib/page-config/save-share-config.js +5 -16
- package/package.json +1 -10
- package/scripts/postinstall.js +3 -1
- package/yida-skills/SKILL.md +3 -27
- package/yida-skills/skills/yida-app/workflow/parallel-work.md +1 -1
- package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +3 -3
- package/yida-skills/skills/yida-app/workflow/plan/workflow.md +1 -1
- package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
- package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +1 -1
- package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +6 -0
- package/yida-skills/skills/yida-create-form-page/SKILL.md +5 -1
- package/yida-skills/skills/yida-custom-page/SKILL.md +6 -0
- package/yida-skills/skills/yida-dashboard/SKILL.md +2 -2
- package/yida-skills/skills/yida-dashboard/references/theme-presets.md +20 -245
- package/yida-skills/skills/yida-design/SKILL.md +22 -10
- package/yida-skills/skills/yida-design/references/application-style-library.md +19 -6
- package/yida-skills/skills/yida-design/references/application-theme-consistency.md +13 -7
- package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +19 -9
- package/yida-skills/skills/yida-design/references/navigation-decision.md +14 -1
- package/yida-skills/skills/yida-design/references/page-quality-gates.md +16 -0
- package/yida-skills/skills/yida-design/references/responsive-layout.md +54 -0
- package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +5 -24
- package/yida-skills/skills/yida-design/references/theme-preview-images.json +19 -0
- package/yida-skills/skills/yida-design/references/theme-selection.md +80 -9
- package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +324 -303
- package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +2 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/SKILL.md +86 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/application-coverage.md +44 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/backup-and-restore.md +65 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/component-replacement.md +18 -0
- package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/confirmation.md +26 -0
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +5 -1
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +1186 -1186
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-normal.svg +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-process.svg +6 -6
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-fit.svg +3 -3
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-relations.svg +7 -7
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-in.svg +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-out.svg +4 -4
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +29 -22
- package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1166 -1166
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +71 -67
- package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +108 -69
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +229 -40
- package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +54 -44
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +107 -0
- package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +43 -1
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +16 -16
- package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +98 -17
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +23 -23
- package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +98 -25
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +15 -4
- package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +22 -10
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +21 -10
- package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +24 -20
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +2 -2
- package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +4 -2
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +160 -18
- package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +57 -36
- package/yida-skills/skills/yida-design/templates/design-themes/index.json +73 -16
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +163 -29
- package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +85 -74
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +170 -27
- package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +50 -29
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +155 -36
- package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +46 -26
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +161 -29
- package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +70 -36
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +119 -8
- package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +31 -23
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +166 -27
- package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +50 -30
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +152 -15
- package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +38 -20
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +1 -1
- package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +3 -1
- package/yida-skills/skills/yida-design/templates/theme-selector.html +83 -0
- package/yida-skills/skills/yida-design/workflow/output-design.md +7 -4
- package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +2 -0
- package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +2 -0
- package/yida-skills/skills/yida-document-markdown/SKILL.md +0 -6
- package/yida-skills/skills/yida-nav-group/SKILL.md +6 -2
- package/yida-skills/skills/yida-nav-shell/SKILL.md +2 -0
- package/yida-skills/skills/yida-prd/SKILL.md +2 -0
- package/yida-skills/skills/yida-publish-page/SKILL.md +4 -0
- package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +4 -2
- package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +2 -0
- package/yida-skills/skills/yida-tingji/SKILL.md +0 -4
- package/yida-skills/skills-index.json +11 -3
- package/lib/agent/PROTOCOL.md +0 -182
- package/lib/agent/ask-human.js +0 -151
- package/lib/agent/cmd.js +0 -405
- package/lib/agent/command-receipt.js +0 -84
- package/lib/agent/connection-store.js +0 -148
- package/lib/agent/diagnostic-log.js +0 -133
- package/lib/agent/frozen-bundle.js +0 -286
- package/lib/agent/managed-command-map.js +0 -36
- package/lib/agent/managed-context.js +0 -40
- package/lib/agent/managed-run.js +0 -85
- package/lib/agent/mutation-receipt.js +0 -82
- package/lib/agent/private-directory.js +0 -63
- package/lib/agent/provider-preflight.js +0 -257
- package/lib/agent/providers.js +0 -4
- package/lib/agent/runtime-catalog.json +0 -45
- package/lib/agent/runtime-manifest.js +0 -96
- package/lib/agent/runtime-package.js +0 -155
- package/lib/agent/stdio.js +0 -321
- package/lib/auth/profile-lock.js +0 -89
- package/lib/core/managed-policy.js +0 -81
- package/yida-skills/references/local-agent-diagnostics.md +0 -154
- package/yida-skills/references/local-agent-preflight.md +0 -56
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 Alibaba Group Holding Limited
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alibaba Group Holding Limited
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
21
|
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -118,6 +118,7 @@ The agent can then call OpenYida commands to create the application, generate so
|
|
|
118
118
|
| [Codex](https://openai.com/codex/) | Full support |
|
|
119
119
|
| [Claude Code](https://claude.ai/code) | Full support |
|
|
120
120
|
| [MuleRun](https://mulerun.com) | Full support |
|
|
121
|
+
| [OpenCode](https://opencode.ai) | Full support |
|
|
121
122
|
| [Cursor](https://cursor.com/) | Full support |
|
|
122
123
|
| [Visual Studio Code](https://code.visualstudio.com/) | Full support |
|
|
123
124
|
| QwenWork(千问办公) | Full support |
|
|
@@ -162,6 +163,18 @@ openyida/
|
|
|
162
163
|
|
|
163
164
|
For a user-facing list of supported features and matching CLI commands, see [OpenYida 功能完整列表](https://demo.aliwork.com/o/openyida/helpCenter?openyidaPath=capabilities).
|
|
164
165
|
|
|
166
|
+
### Style Selection and Optional Previews
|
|
167
|
+
|
|
168
|
+
Only show reference previews when the user explicitly requests them. Style choices use the host's supported ask_human tool on every platform.
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
openyida sample yida-design theme-preview --theme-ids app-sage,app-teal,app-terminal,app-platinum --output .cache/theme-preview.html --json
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
This creates a read-only HTML gallery of all requested themes: titles and images only, four to six columns on desktop, fewer on narrow screens, with no row limit. It has no selection controls and refuses to overwrite existing files. Open it with the host's preview tools; raw HTML is not guaranteed to render in chat. QwenWork may alternatively prepare the same read-only template with `sample yida-design theme-selector --json` and pass its returned relative `widget_path` to `qoder_show_widget`. Neither preview path submits a choice.
|
|
175
|
+
|
|
176
|
+
Ready style questions join the same ask_human call as other necessary questions; split only for dependencies or host limits. Pop art is omitted unless explicitly requested or the primary task is showcase-oriented. Previewing app-pop requires --pop-art-context explicit-request or showcase, based on that evidence.
|
|
177
|
+
|
|
165
178
|
### Application and Form Management
|
|
166
179
|
|
|
167
180
|
```bash
|
|
@@ -403,6 +416,8 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
403
416
|
| `openyida design-plan materialize <build-plan.json> [--from-preview \| --business-file <json> --visual-file <json>] [--output-dir <dir>] [--rebase-parts] [--check] [--json]` | Generate or update plan documents and theme with consistency checks |
|
|
404
417
|
| `openyida design-plan patch <build-plan.json> --set <path=value> [--set <path=value> ...] [--materialize] [--output-dir <dir>] [--json]` | Patch plan fields and optionally sync documents and theme |
|
|
405
418
|
| `openyida update-app <appType> [--name "..."] [--desc "..."] [--icon <name>] [--icon-color <color>] [--colour <key>] [--theme-color <color>] [--theme-file <css>] [--confirm-legacy-app-style] [--disable-custom-theme] [--nav-theme light\|dark\|white\|gray] [--logo-source appIcon\|customImage] [--layout side\|top\|l_shape] [--hide-app-nav\|--show-app-nav]` | Update app info |
|
|
419
|
+
| `openyida app-theme snapshot <appType> --output-dir <new-dir> [--json]` | Archive application theme settings and CSS (read-only) |
|
|
420
|
+
| `openyida app-theme restore <appType> --snapshot <before.json> --expected-current <after.json> [--check \| --confirm --output-dir <new-dir>] [--json]` | Check or restore an application theme snapshot (supported exact values only) |
|
|
406
421
|
| `openyida upgrade-app-theme <appType> --explicit-request [--confirm \| --prepare --output-dir <dir>] [--json]` | Upgrade an AI-built legacy app theme (explicit request and ask_human confirmation required) |
|
|
407
422
|
| `openyida app-online <appType> [--to-ding-app-center] [--show-app-center]` | Enable a Yida app |
|
|
408
423
|
| `openyida app-offline <appType> [--to-ding-app-center] [--show-app-center]` | Disable a Yida app |
|
|
@@ -522,11 +537,10 @@ Run `openyida --help` or `openyida <command> --help` for detailed usage.
|
|
|
522
537
|
|---------|-------------|
|
|
523
538
|
| `openyida commands [--json]` | Output machine-readable command manifest |
|
|
524
539
|
| `openyida agent-capabilities [--json] [--summary-json\|--compact]` | Output one-shot agent capability snapshot |
|
|
525
|
-
| `openyida agent <doctor\|diagnose\|status\|connect\|run\|disconnect\|logout\|logs> [options]` | Connect a local Agent (Go runtime, development preview) |
|
|
526
540
|
| `openyida a2a <serve\|agent-card> [options]` | Start local read-only A2A adapter or print Agent Card |
|
|
527
541
|
| `openyida bridge start [--token <pair-token>] [--port 6736] [--origin https://demo.aliwork.com] [--open\|--no-open]` | Start OpenYida local web bridge service |
|
|
528
542
|
| `openyida copy [--force]` | Copy project working directory |
|
|
529
|
-
| `openyida sample [--list] [<skill> <name>] [--output <file-or-directory>] [--var KEY=VALUE ...] [--design-file <design.md>] [--style-id <themeId\|free-creative>]` | Output code samples/templates |
|
|
543
|
+
| `openyida sample [--list] [<skill> <name>] [--output <file-or-directory>] [--var KEY=VALUE ...] [--design-file <design.md>] [--style-id <themeId\|free-creative>] [--theme-ids <id,...>] [--pop-art-context <explicit-request\|showcase>]` | Output code samples/templates |
|
|
530
544
|
| `openyida doctor [--fix]` | Environment diagnostics & auto-fix |
|
|
531
545
|
| `openyida db-seq-fix [--fix]` | Detect and repair PostgreSQL sequence drift |
|
|
532
546
|
| `openyida formula evaluate <formula\|file> [--schema file]` | Static-check Yida formula syntax and field refs |
|
|
@@ -591,31 +605,6 @@ Form field definitions can include `alias` or `componentAlias` to populate Yida
|
|
|
591
605
|
|
|
592
606
|
`openyida integration create` supports form events (`insert`, `update`, `delete`, `comment`) and approval events (`processFinish`, `activityTask`; aliases: `approval`, `approvalNode`). Approval events require `--approval-actions agree,disagree,terminated`; `activityTask` also requires `--approval-node-ids <nodeId,...>`. Passing `--process-code` selects a full graph replacement and now requires explicit `--replace`; it is not a safe update. `integration update` currently performs capability detection only: because full platform `processJson` + `viewJson` readback is unproven, it writes a redacted local probe artifact and returns `PLATFORM_PROBE_REQUIRED` before authentication, spec reading, or remote writes. It does not edit a flow.
|
|
593
607
|
|
|
594
|
-
### Local Agent development preview
|
|
595
|
-
|
|
596
|
-
`openyida agent` is a thin Node launcher for a separately supplied Go runtime. Node does not connect to the agent service or manage provider sessions. Platform runtime packages are **not published** in this preview; no Go executable is discovered on `PATH` and no provider is installed or invoked by Node.
|
|
597
|
-
|
|
598
|
-
The Local Agent Beta production allowlist contains macOS and Windows only. Linux remains supported by the ordinary OpenYida CLI and by the explicit development-runtime workflow below, but Linux Local Agent packages are not published and production `connect`/`run` are rejected until native acceptance is complete.
|
|
599
|
-
|
|
600
|
-
```bash
|
|
601
|
-
openyida agent doctor --json
|
|
602
|
-
openyida agent doctor --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json
|
|
603
|
-
openyida agent connect --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json --endpoint https://agent.example.test --endpoint-id configured-platform-id
|
|
604
|
-
openyida agent run --development-runtime --runtime-path /absolute/path/openyida-runtime --runtime-manifest /absolute/path/runtime-manifest.json --endpoint https://agent.example.test --endpoint-id configured-platform-id --provider qoder --provider-path /absolute/path/qodercli
|
|
605
|
-
```
|
|
606
|
-
|
|
607
|
-
Development manifest v1 specifies runtime version, protocol `1`, Node-style OS/architecture names, and `binary: {file, sha256}`; it requires the explicit `--development-runtime` flag. This is an integrity check, **not a publisher signature**. The production loader requires a strictly validated v2 manifest, exact approved platform-package version/digest, and an Ed25519 signature over the original manifest bytes using trust roots bundled in the Node host. The release catalog is currently empty: no production signing key or package is implied to exist. See the [launcher contract](lib/agent/PROTOCOL.md).
|
|
608
|
-
|
|
609
|
-
`connect` supports two equivalent enrollment entries. CLI-first opens the Yida browser approval page and returns through a state/PKCE-protected loopback callback. Web-first copies one five-minute command containing `--enroll`; the token is single-use and carries no business authority. Neither path reads or uploads an ordinary OpenYida login profile.
|
|
610
|
-
|
|
611
|
-
One installation may connect to multiple organizations. Node keeps only a shared Installation ID at the installation root; each organization authorization gets an isolated private Connection state directory, device credentials, journals, control socket, and frozen bundle. `agent connect` keeps the new Connection online in the foreground, while `agent run`, `status`, `disconnect`, and `logout` enumerate every valid Connection. Each Connection registers all locally discovered Qoder and Codex Runtimes, and the web UI selects computer, CLI, and model.
|
|
612
|
-
|
|
613
|
-
For each attempt, Go injects `OPENYIDA_MANAGED_RUN=1`, the cloud user/corp/app/run/attempt context, and a fixed-value Tianshu task grant with an initial 60-minute lease. The CLI uses that grant instead of local auth and sends scope-correlation headers. While the authenticated device connection and server-side Run remain active, yida-agent can renew the same bearer server-to-server; the CLI cannot renew it and never receives a task refresh token. Tianshu independently checks the grant against the requested app and rejects application creation, identity/environment changes, arbitrary batches, and cross-application requests. `formUuid` remains optional per-turn page-focus context, so one app-bound session can work across pages in that application. Without `OPENYIDA_MANAGED_RUN`, existing login, token refresh, and command behavior are unchanged.
|
|
614
|
-
|
|
615
|
-
Before `connect` or `run`, Node now freezes the verified runtime bytes, CLI, full production dependency closure, and packaged Skills into a private content-addressed bundle. Source changes during copying fail closed. Existing bundles are verified, never silently repaired or garbage-collected by Node, and survive replacement/removal of the original npm directory. Go owns active/session references and version activation; the external Node executable and provider installations are not copied or managed by npm. A separate parent-watch pipe and a 15-second stop grace prevent normal config EOF from being mistaken for parent death; an unconfirmed forced stop remains unknown.
|
|
616
|
-
|
|
617
|
-
This is not an OS sandbox or an authorization boundary against arbitrary shell code. Production signing/release infrastructure, full Go/provider recovery and upgrade acceptance, and OS/architecture-specific provider E2E are not claimed by these fake-process tests. Auto-update is disabled for `agent` and managed invocations; ordinary CLI commands neither spawn nor download the runtime.
|
|
618
|
-
|
|
619
608
|
## Agent Skills
|
|
620
609
|
|
|
621
610
|
The `yida-skills/` directory is the source skill library used by OpenYida during development and distributed with the npm package.
|
package/bin/yida.js
CHANGED
|
@@ -24,8 +24,6 @@ const { COMMAND_GROUPS, buildCommandManifest, findCommandSuggestion } = require(
|
|
|
24
24
|
const command = process.argv[2];
|
|
25
25
|
const args = process.argv.slice(3);
|
|
26
26
|
const rawArgs = process.argv.slice(3);
|
|
27
|
-
const { beginCommandReceipt } = require('../lib/agent/command-receipt');
|
|
28
|
-
let commandReceipt;
|
|
29
27
|
|
|
30
28
|
function shouldUseEnvManagement(argsList) {
|
|
31
29
|
const subCommand = argsList[0];
|
|
@@ -573,26 +571,6 @@ function assertNoUnsupportedLegacyLoginFlags(...argLists) {
|
|
|
573
571
|
}
|
|
574
572
|
|
|
575
573
|
async function main() {
|
|
576
|
-
const { assertManagedInvocation, resolveManagedHelp } = require('../lib/agent/managed-run');
|
|
577
|
-
const { readManagedContext } = require('../lib/agent/managed-context');
|
|
578
|
-
if (readManagedContext() && [command, ...rawArgs].some(arg => arg === '--help' || arg === '-h')) {
|
|
579
|
-
assertManagedInvocation(command, rawArgs);
|
|
580
|
-
const entries = resolveManagedHelp(command, rawArgs, { t, version: currentVersion });
|
|
581
|
-
// Managed help is static and local: no Receipt, auth, update, first-run
|
|
582
|
-
// guide or business command module may run on this path.
|
|
583
|
-
commandReceipt = { finish() {} };
|
|
584
|
-
if (rawArgs.includes('--json')) {
|
|
585
|
-
console.log(JSON.stringify({ name: 'openyida', version: currentVersion, commands: entries }, null, 2));
|
|
586
|
-
} else {
|
|
587
|
-
printCommandUsage(...entries.flatMap(entry => [entry.usage, entry.description, ...(entry.examples || [])]));
|
|
588
|
-
}
|
|
589
|
-
return;
|
|
590
|
-
}
|
|
591
|
-
commandReceipt = beginCommandReceipt({ command, args: rawArgs });
|
|
592
|
-
process.once('exit', code => {
|
|
593
|
-
try {commandReceipt.finish(code);} catch {process.exitCode = 1;}
|
|
594
|
-
});
|
|
595
|
-
assertManagedInvocation(command, rawArgs);
|
|
596
574
|
applyQuietFlag();
|
|
597
575
|
applyGlobalEnvironmentFlags();
|
|
598
576
|
|
|
@@ -641,12 +619,6 @@ async function main() {
|
|
|
641
619
|
break;
|
|
642
620
|
}
|
|
643
621
|
|
|
644
|
-
case 'agent': {
|
|
645
|
-
const { run } = require('../lib/agent/cmd');
|
|
646
|
-
await run(args);
|
|
647
|
-
break;
|
|
648
|
-
}
|
|
649
|
-
|
|
650
622
|
case 'mcp': {
|
|
651
623
|
const { runStdioServer } = require('../lib/mcp/server');
|
|
652
624
|
runStdioServer();
|
|
@@ -922,8 +894,8 @@ async function main() {
|
|
|
922
894
|
if (args.length < 4) {
|
|
923
895
|
throwCliUsage(t('cli.form_config_usage'), t('cli.form_config_example'));
|
|
924
896
|
}
|
|
925
|
-
|
|
926
|
-
|
|
897
|
+
process.argv = [process.argv[0], process.argv[1], ...args];
|
|
898
|
+
require('../lib/app/update-form-config');
|
|
927
899
|
break;
|
|
928
900
|
}
|
|
929
901
|
|
|
@@ -942,6 +914,11 @@ async function main() {
|
|
|
942
914
|
break;
|
|
943
915
|
}
|
|
944
916
|
|
|
917
|
+
case 'app-theme': {
|
|
918
|
+
await require('../lib/app/app-theme').run(args);
|
|
919
|
+
break;
|
|
920
|
+
}
|
|
921
|
+
|
|
945
922
|
case 'upgrade-app-theme': {
|
|
946
923
|
await require('../lib/app/upgrade-app-theme').run(args);
|
|
947
924
|
break;
|
|
@@ -1314,9 +1291,7 @@ async function main() {
|
|
|
1314
1291
|
}
|
|
1315
1292
|
|
|
1316
1293
|
main()
|
|
1317
|
-
.then(() => { commandReceipt.finish(process.exitCode || 0); })
|
|
1318
1294
|
.catch((err) => {
|
|
1319
|
-
try {commandReceipt?.finish(err && err.exitCode ? err.exitCode : 1);} catch { /* Intent remains recoverable. */ }
|
|
1320
1295
|
if (shouldUseStructuredErrorOutput(err, args)) {
|
|
1321
1296
|
console.error(JSON.stringify(toErrorPayload(err), null, 2));
|
|
1322
1297
|
} else if (isCliError(err)) {
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const crypto = require('crypto');
|
|
6
|
+
const https = require('https');
|
|
7
|
+
const querystring = require('querystring');
|
|
8
|
+
const { httpGet, httpPost, requestWithAutoLogin } = require('../core/utils');
|
|
9
|
+
const { createAuthRef } = require('../core/yida-client');
|
|
10
|
+
const { throwCommandError, throwUsage } = require('../core/command-errors');
|
|
11
|
+
const { t } = require('../core/i18n');
|
|
12
|
+
const { parseThemeContext } = require('./upgrade-app-theme');
|
|
13
|
+
|
|
14
|
+
// Deliberately exclude business configuration, theme migration flags and app metadata.
|
|
15
|
+
const KEYS = ['COLOUR', 'THEME_COLOR', 'NAV_THEME', 'LAY_OUT_DIRECTION', 'NAVTYPE',
|
|
16
|
+
'HIDE_APP_NAV', 'LOGO_SOURCE', 'HOMEPAGELOGO', 'CUSTOM_THEME_STYLE'];
|
|
17
|
+
const MODE_KEYS = ['APP_THEME_MODE', 'CREATED_WITH_MODERN_THEME'];
|
|
18
|
+
const MAX_BYTES = 20 * 1024 * 1024;
|
|
19
|
+
const fail = (code, details) => throwCommandError(t('app_theme.' + code.toLowerCase()), { code, details });
|
|
20
|
+
const hash = value => crypto.createHash('sha256').update(value).digest('hex');
|
|
21
|
+
function canonical(value) {
|
|
22
|
+
if (Array.isArray(value)) { return '[' + value.map(canonical).join(',') + ']'; }
|
|
23
|
+
if (value && typeof value === 'object') {
|
|
24
|
+
return '{' + Object.keys(value).sort().map(key => JSON.stringify(key) + ':' + canonical(value[key])).join(',') + '}';
|
|
25
|
+
}
|
|
26
|
+
return JSON.stringify(value);
|
|
27
|
+
}
|
|
28
|
+
const digest = value => hash(canonical(value));
|
|
29
|
+
const equal = (a, b) => canonical(a) === canonical(b);
|
|
30
|
+
const appIdValid = value => typeof value === 'string' && /^[A-Za-z0-9_-]+$/.test(value);
|
|
31
|
+
const exactKeys = (value, keys) => value && typeof value === 'object' && !Array.isArray(value) && equal(Object.keys(value).sort(), [...keys].sort());
|
|
32
|
+
|
|
33
|
+
function parseArgs(args = []) {
|
|
34
|
+
const params = { action: args[0] };
|
|
35
|
+
if (['--help', '-h'].includes(args[0])) { return { help: true }; }
|
|
36
|
+
if (!['snapshot', 'restore'].includes(params.action)) { throwUsage(t('app_theme.usage')); }
|
|
37
|
+
const values = { '--output-dir': 'outputDir', '--snapshot': 'snapshot', '--expected-current': 'expectedCurrent' };
|
|
38
|
+
const flags = { '--json': 'json', '--help': 'help', '-h': 'help', '--check': 'check', '--confirm': 'confirm' };
|
|
39
|
+
for (let i = 1; i < args.length; i++) {
|
|
40
|
+
const arg = args[i];
|
|
41
|
+
const field = values[arg] || flags[arg];
|
|
42
|
+
if (field && params[field] !== undefined) { throwUsage(t('cli_argument.invalid', arg)); }
|
|
43
|
+
if (values[arg] && args[i + 1] && !args[i + 1].startsWith('-')) { params[field] = args[++i]; }
|
|
44
|
+
else if (flags[arg]) { params[field] = true; }
|
|
45
|
+
else if (!arg.startsWith('-') && !params.appType) { params.appType = arg; }
|
|
46
|
+
else { throwUsage(t('cli_argument.invalid', arg)); }
|
|
47
|
+
}
|
|
48
|
+
if (params.help) { return params; }
|
|
49
|
+
validateParams(params);
|
|
50
|
+
return params;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function validateParams(p) {
|
|
54
|
+
if (!appIdValid(p.appType) || !['snapshot', 'restore'].includes(p.action) ||
|
|
55
|
+
(p.action === 'snapshot' && (!p.outputDir || p.snapshot || p.expectedCurrent || p.check || p.confirm)) ||
|
|
56
|
+
(p.action === 'restore' && (!p.snapshot || !p.expectedCurrent || (p.check && p.confirm) || (p.confirm && !p.outputDir)))) {
|
|
57
|
+
throwUsage(t('app_theme.usage'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function identity(appType, auth) {
|
|
62
|
+
let url;
|
|
63
|
+
try { url = new URL(auth.baseUrl); } catch (_) { fail('APP_THEME_IDENTITY_INVALID'); }
|
|
64
|
+
if (!auth.corpId || !['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) {
|
|
65
|
+
fail('APP_THEME_IDENTITY_INVALID');
|
|
66
|
+
}
|
|
67
|
+
return { appType, corpId: auth.corpId, baseUrl: url.href.replace(/\/$/, '') };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function configRequest(appType, auth, endpoint, fields, target = identity(appType, auth)) {
|
|
71
|
+
return requestWithAutoLogin(current => {
|
|
72
|
+
if (!equal(target, identity(appType, current))) { fail('APP_THEME_IDENTITY_INVALID'); }
|
|
73
|
+
return httpPost(current.baseUrl, `/${encodeURIComponent(appType)}/query/app/${endpoint}.json`,
|
|
74
|
+
querystring.stringify({ appType, ...fields, _csrf_token: current.csrfToken }));
|
|
75
|
+
}, auth);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function readState(appType, auth) {
|
|
79
|
+
const target = identity(appType, auth);
|
|
80
|
+
const html = await requestWithAutoLogin(current => httpGet(current.baseUrl,
|
|
81
|
+
`/${appType}/admin`, { _stamp: Date.now() }, { responseType: 'text' }), auth);
|
|
82
|
+
const context = parseThemeContext(html, ['appType', 'corpId', 'appThemeEnable', 'appThemeMode']);
|
|
83
|
+
if (context.appType !== target.appType || context.corpId !== target.corpId) { fail('APP_THEME_IDENTITY_INVALID'); }
|
|
84
|
+
const response = await requestWithAutoLogin(current => httpGet(current.baseUrl,
|
|
85
|
+
`/${appType}/query/app/getAppIncludingAecpInfo.json`, { appKey: appType, _stamp: Date.now() }), auth);
|
|
86
|
+
const app = response?.content;
|
|
87
|
+
if (response?.success !== true || !app || typeof app !== 'object' || typeof app.icon !== 'string') {
|
|
88
|
+
fail('APP_THEME_SNAPSHOT_INVALID');
|
|
89
|
+
}
|
|
90
|
+
const config = {};
|
|
91
|
+
const modes = {};
|
|
92
|
+
for (const key of [...KEYS, ...MODE_KEYS]) {
|
|
93
|
+
const result = await configRequest(appType, auth, 'getSingleConfig', { key });
|
|
94
|
+
const empty = exactKeys(result?.content, []);
|
|
95
|
+
if (result?.success !== true || (result.content !== null && typeof result.content !== 'string' && !empty)) {
|
|
96
|
+
fail('APP_THEME_READ_FAILED', { key });
|
|
97
|
+
}
|
|
98
|
+
(KEYS.includes(key) ? config : modes)[key] = empty ? null : result.content;
|
|
99
|
+
}
|
|
100
|
+
if (!equal(target, identity(appType, auth))) { fail('APP_THEME_IDENTITY_INVALID'); }
|
|
101
|
+
return { identity: target, config, protected: { modes, appThemeEnable: context.appThemeEnable,
|
|
102
|
+
appThemeMode: context.appThemeMode, icon: app.icon } };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function styleOf(state) {
|
|
106
|
+
const raw = state.config.CUSTOM_THEME_STYLE;
|
|
107
|
+
if (raw === null || raw === '') { return null; }
|
|
108
|
+
let style;
|
|
109
|
+
try { style = JSON.parse(raw); } catch (_) { fail('APP_THEME_SNAPSHOT_INVALID'); }
|
|
110
|
+
if (!style || Array.isArray(style) || typeof style !== 'object' ||
|
|
111
|
+
Object.keys(style).some(key => !['enabled', 'iframePropagation', 'cssUrl', 'cssFileName'].includes(key)) ||
|
|
112
|
+
['enabled', 'iframePropagation'].some(key => Object.hasOwn(style, key) && typeof style[key] !== 'boolean') ||
|
|
113
|
+
['cssUrl', 'cssFileName'].some(key => Object.hasOwn(style, key) && typeof style[key] !== 'string') ||
|
|
114
|
+
(style.enabled === true && !style.cssUrl)) { fail('APP_THEME_SNAPSHOT_INVALID'); }
|
|
115
|
+
return style;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// CSS is fetched without Yida credentials. Redirects, oversized files and HTML/login bodies fail closed.
|
|
119
|
+
function downloadCss(url) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
let parsed;
|
|
122
|
+
try { parsed = new URL(url); } catch (_) { reject(new Error(t('app_theme.app_theme_css_unavailable'))); return; }
|
|
123
|
+
if (parsed.protocol !== 'https:' || parsed.username || parsed.password || parsed.hash) {
|
|
124
|
+
reject(new Error(t('app_theme.app_theme_css_unavailable'))); return;
|
|
125
|
+
}
|
|
126
|
+
const request = https.get(parsed, { headers: { Accept: 'text/css' } }, response => {
|
|
127
|
+
if (response.statusCode !== 200 || !/^text\/css(?:;|$)/i.test(response.headers['content-type'] || '')) {
|
|
128
|
+
response.resume(); reject(new Error(t('app_theme.app_theme_css_unavailable'))); return;
|
|
129
|
+
}
|
|
130
|
+
const chunks = [];
|
|
131
|
+
let length = 0;
|
|
132
|
+
response.on('data', chunk => {
|
|
133
|
+
length += chunk.length;
|
|
134
|
+
if (length > MAX_BYTES) { request.destroy(new Error(t('app_theme.app_theme_css_unavailable'))); }
|
|
135
|
+
else { chunks.push(chunk); }
|
|
136
|
+
});
|
|
137
|
+
response.on('error', reject);
|
|
138
|
+
response.on('end', () => {
|
|
139
|
+
const bytes = Buffer.concat(chunks);
|
|
140
|
+
if (!bytes.length || /<\s*(?:!doctype|html|script)\b/i.test(bytes.toString('utf8'))) {
|
|
141
|
+
reject(new Error(t('app_theme.app_theme_css_unavailable')));
|
|
142
|
+
} else { resolve(bytes); }
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
request.setTimeout(15000, () => request.destroy(new Error(t('app_theme.app_theme_css_unavailable'))));
|
|
146
|
+
request.on('error', reject);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function newDirectory(dir) {
|
|
151
|
+
const absolute = path.resolve(dir);
|
|
152
|
+
fs.mkdirSync(path.dirname(absolute), { recursive: true });
|
|
153
|
+
try { fs.mkdirSync(absolute, { mode: 0o700 }); }
|
|
154
|
+
catch (_) { fail('APP_THEME_OUTPUT_EXISTS'); }
|
|
155
|
+
return absolute;
|
|
156
|
+
}
|
|
157
|
+
function writeJson(file, value, exclusive = false) {
|
|
158
|
+
fs.writeFileSync(file, JSON.stringify(value, null, 2) + '\n', { mode: 0o600, flag: exclusive ? 'wx' : 'w' });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function capabilities(state) {
|
|
162
|
+
return { restorableKeys: KEYS.filter(key => typeof state.config[key] === 'string' && state.config[key].trim()),
|
|
163
|
+
cannotClearKeys: KEYS.filter(key => !state.config[key]?.trim()),
|
|
164
|
+
protectedFields: ['icon', 'appThemeEnable', 'appThemeMode', ...MODE_KEYS],
|
|
165
|
+
scope: 'application-theme-config', supportsPageRestore: false, supportsIconRestore: false };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
async function snapshot(p, auth, fetchCss = downloadCss) {
|
|
169
|
+
validateParams({ ...p, action: 'snapshot' });
|
|
170
|
+
const dir = newDirectory(p.outputDir);
|
|
171
|
+
const state = await readState(p.appType, auth);
|
|
172
|
+
const style = styleOf(state);
|
|
173
|
+
let css = null;
|
|
174
|
+
if (style?.cssUrl) {
|
|
175
|
+
const bytes = await fetchCss(style.cssUrl);
|
|
176
|
+
fs.writeFileSync(path.join(dir, 'theme.css'), bytes, { mode: 0o600, flag: 'wx' });
|
|
177
|
+
css = { file: 'theme.css', sha256: hash(bytes), bytes: bytes.length };
|
|
178
|
+
}
|
|
179
|
+
if (!equal(state, await readState(p.appType, auth))) { fail('APP_THEME_CONFLICT'); }
|
|
180
|
+
const data = { version: 1, createdAt: new Date().toISOString(), state, css };
|
|
181
|
+
const document = { ...data, sha256: digest(data) };
|
|
182
|
+
const file = path.join(dir, 'snapshot.json');
|
|
183
|
+
writeJson(file, document, true);
|
|
184
|
+
loadSnapshot(file); // Re-read actual local bytes before claiming a usable backup.
|
|
185
|
+
return { success: true, snapshot: file, fingerprint: digest(state), ...capabilities(state), runtimeVerified: false };
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function loadSnapshot(file) {
|
|
189
|
+
try {
|
|
190
|
+
if (!fs.lstatSync(file).isFile() || fs.statSync(file).size > MAX_BYTES) { throw new Error(); }
|
|
191
|
+
const doc = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
192
|
+
const { sha256, ...data } = doc;
|
|
193
|
+
const state = data.state;
|
|
194
|
+
if (!exactKeys(doc, ['version', 'createdAt', 'state', 'css', 'sha256']) || data.version !== 1 ||
|
|
195
|
+
typeof data.createdAt !== 'string' || sha256 !== digest(data) ||
|
|
196
|
+
!exactKeys(state, ['identity', 'config', 'protected']) ||
|
|
197
|
+
!exactKeys(state.identity, ['appType', 'corpId', 'baseUrl']) || !appIdValid(state.identity.appType) ||
|
|
198
|
+
!equal(state.identity, identity(state.identity.appType, state.identity)) ||
|
|
199
|
+
!exactKeys(state.config, KEYS) || !exactKeys(state.protected, ['modes', 'appThemeEnable', 'appThemeMode', 'icon']) ||
|
|
200
|
+
!exactKeys(state.protected.modes, MODE_KEYS) ||
|
|
201
|
+
Object.values({ ...state.config, ...state.protected.modes }).some(v => v !== null && typeof v !== 'string') ||
|
|
202
|
+
['icon', 'appThemeEnable', 'appThemeMode'].some(k => typeof state.protected[k] !== 'string')) { throw new Error(); }
|
|
203
|
+
const style = styleOf(state);
|
|
204
|
+
if (style?.cssUrl) {
|
|
205
|
+
if (!exactKeys(data.css, ['file', 'sha256', 'bytes']) || data.css.file !== 'theme.css') { throw new Error(); }
|
|
206
|
+
const cssFile = path.join(path.dirname(file), 'theme.css');
|
|
207
|
+
if (!fs.lstatSync(cssFile).isFile() || fs.statSync(cssFile).size > MAX_BYTES) { throw new Error(); }
|
|
208
|
+
const bytes = fs.readFileSync(cssFile);
|
|
209
|
+
if (!bytes.length || hash(bytes) !== data.css.sha256 || bytes.length !== data.css.bytes) { throw new Error(); }
|
|
210
|
+
} else if (data.css !== null) { throw new Error(); }
|
|
211
|
+
return doc;
|
|
212
|
+
} catch (_) { fail('APP_THEME_SNAPSHOT_INVALID'); }
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function planRestore(before, after) {
|
|
216
|
+
const blockers = [];
|
|
217
|
+
if (!equal(before.identity, after.identity)) { blockers.push({ reason: 'identity_mismatch' }); }
|
|
218
|
+
if (!equal(before.protected, after.protected)) { blockers.push({ reason: 'protected_fields_changed' }); }
|
|
219
|
+
const changes = KEYS.filter(key => before.config[key] !== after.config[key]).map(key => ({ key, value: before.config[key] }));
|
|
220
|
+
for (const { key, value } of changes) {
|
|
221
|
+
if (value === null || !value.trim()) { blockers.push({ key, reason: 'cannot_restore_absent_or_empty_value' }); }
|
|
222
|
+
}
|
|
223
|
+
return { changes, blockers };
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function restore(p, auth, fetchCss = downloadCss) {
|
|
227
|
+
validateParams({ ...p, action: 'restore' });
|
|
228
|
+
const before = loadSnapshot(p.snapshot);
|
|
229
|
+
const after = loadSnapshot(p.expectedCurrent);
|
|
230
|
+
if (!equal(before.state.identity, identity(p.appType, auth)) || !equal(after.state.identity, identity(p.appType, auth))) {
|
|
231
|
+
fail('APP_THEME_IDENTITY_INVALID');
|
|
232
|
+
}
|
|
233
|
+
const plan = planRestore(before.state, after.state);
|
|
234
|
+
const current = await readState(p.appType, auth);
|
|
235
|
+
if (!equal(current, after.state)) { fail('APP_THEME_CONFLICT'); }
|
|
236
|
+
if (plan.changes.some(({ key }) => key === 'CUSTOM_THEME_STYLE') && before.css) {
|
|
237
|
+
try {
|
|
238
|
+
if (hash(await fetchCss(styleOf(before.state).cssUrl)) !== before.css.sha256) { throw new Error(); }
|
|
239
|
+
} catch (_) { plan.blockers.push({ reason: 'original_css_unavailable_or_changed' }); }
|
|
240
|
+
}
|
|
241
|
+
const report = { success: true, canRestore: !plan.blockers.length, changes: plan.changes.map(({ key }) => key),
|
|
242
|
+
blockers: plan.blockers, verificationScope: 'application-theme-config', runtimeVerified: false };
|
|
243
|
+
if (p.confirm !== true) { return { ...report, checkOnly: true }; }
|
|
244
|
+
if (plan.blockers.length) { fail('APP_THEME_RESTORE_BLOCKED', report); }
|
|
245
|
+
const dir = newDirectory(p.outputDir);
|
|
246
|
+
const journalFile = path.join(dir, 'restore.json');
|
|
247
|
+
const journal = { version: 1, identity: current.identity, targetFingerprint: digest(before.state),
|
|
248
|
+
expectedFingerprint: digest(after.state), status: 'prepared', attemptedKey: null, verifiedWrites: [] };
|
|
249
|
+
writeJson(journalFile, journal, true);
|
|
250
|
+
let expected = current;
|
|
251
|
+
try {
|
|
252
|
+
for (const change of plan.changes) {
|
|
253
|
+
// No server-side compare-and-swap exists. Check the complete tracked state before every write.
|
|
254
|
+
if (!equal(expected, await readState(p.appType, auth))) { fail('APP_THEME_CONFLICT'); }
|
|
255
|
+
journal.status = 'writing';
|
|
256
|
+
journal.attemptedKey = change.key;
|
|
257
|
+
writeJson(journalFile, journal);
|
|
258
|
+
const response = await configRequest(p.appType, auth, 'updateSingleConfig', change, before.state.identity);
|
|
259
|
+
if (response?.success !== true || response.content !== true) { fail('APP_THEME_WRITE_FAILED'); }
|
|
260
|
+
expected = { ...expected, config: { ...expected.config, [change.key]: change.value } };
|
|
261
|
+
if (!equal(expected, await readState(p.appType, auth))) { fail('APP_THEME_VERIFY_FAILED'); }
|
|
262
|
+
journal.verifiedWrites.push(change.key);
|
|
263
|
+
journal.attemptedKey = null;
|
|
264
|
+
writeJson(journalFile, journal);
|
|
265
|
+
}
|
|
266
|
+
if (!equal(before.state, await readState(p.appType, auth))) { fail('APP_THEME_VERIFY_FAILED'); }
|
|
267
|
+
journal.status = 'restored';
|
|
268
|
+
writeJson(journalFile, journal);
|
|
269
|
+
return { ...report, changed: plan.changes.length > 0, verified: true, journal: journalFile };
|
|
270
|
+
} catch (error) {
|
|
271
|
+
journal.status = 'failed_or_unknown';
|
|
272
|
+
journal.errorCode = error.code || 'APP_THEME_WRITE_FAILED';
|
|
273
|
+
try { journal.observedState = await readState(p.appType, auth); } catch (_) { journal.readbackFailed = true; }
|
|
274
|
+
try { writeJson(journalFile, journal); } catch (_) { /* The pre-write journal remains available. */ }
|
|
275
|
+
fail('APP_THEME_RESTORE_INCOMPLETE', { journal: journalFile, attemptedKey: journal.attemptedKey,
|
|
276
|
+
verifiedWrites: journal.verifiedWrites, cause: journal.errorCode });
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
async function run(args) {
|
|
281
|
+
const params = parseArgs(args);
|
|
282
|
+
if (params.help) { console.log(t('app_theme.usage')); return; }
|
|
283
|
+
const auth = createAuthRef();
|
|
284
|
+
const result = await (params.action === 'snapshot' ? snapshot(params, auth) : restore(params, auth));
|
|
285
|
+
console.log(JSON.stringify(result, null, 2));
|
|
286
|
+
return result;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
module.exports = { run, parseArgs, snapshot, restore, loadSnapshot, planRestore, downloadCss, KEYS, MODE_KEYS };
|
|
@@ -9,9 +9,8 @@ const { auditAssetExecution } = require('../asset/asset-execution');
|
|
|
9
9
|
const { fetchFormPageList } = require('./form-navigation');
|
|
10
10
|
|
|
11
11
|
const CHECKABLE_RESOURCE_KINDS = new Set(['display-page', 'normal-form', 'process-form']);
|
|
12
|
-
const APP_TYPE_OPTIONS = Object.freeze(['--app-type', '--appType', '--app_type']);
|
|
13
12
|
|
|
14
|
-
function parseArgs(args = []
|
|
13
|
+
function parseArgs(args = []) {
|
|
15
14
|
const parsed = {
|
|
16
15
|
prdPath: '',
|
|
17
16
|
appType: '',
|
|
@@ -20,29 +19,20 @@ function parseArgs(args = [], options = {}) {
|
|
|
20
19
|
help: false,
|
|
21
20
|
};
|
|
22
21
|
|
|
23
|
-
const seen = new Set();
|
|
24
22
|
for (let index = 0; index < args.length; index++) {
|
|
25
23
|
const arg = args[index];
|
|
26
|
-
const key = APP_TYPE_OPTIONS.includes(arg) ? 'appType'
|
|
27
|
-
: ['--build-manifest', '--manifest'].includes(arg) ? 'buildManifestPath' : arg;
|
|
28
|
-
if (options.strict && arg.startsWith('-') && seen.has(key)) {
|
|
29
|
-
throwUsage(t('check_prd_completeness.usage'), t('check_prd_completeness.example'));
|
|
30
|
-
}
|
|
31
|
-
if (options.strict && arg.startsWith('-')) {seen.add(key);}
|
|
32
24
|
if (arg === '--help' || arg === '-h') {
|
|
33
25
|
parsed.help = true;
|
|
34
26
|
} else if (arg === '--json') {
|
|
35
27
|
parsed.json = true;
|
|
36
|
-
} else if (
|
|
28
|
+
} else if ((arg === '--app-type' || arg === '--appType' || arg === '--app_type') && args[index + 1]) {
|
|
37
29
|
parsed.appType = args[index + 1];
|
|
38
30
|
index++;
|
|
39
|
-
} else if ((arg === '--build-manifest' || arg === '--manifest') && args[index + 1]
|
|
31
|
+
} else if ((arg === '--build-manifest' || arg === '--manifest') && args[index + 1]) {
|
|
40
32
|
parsed.buildManifestPath = args[index + 1];
|
|
41
33
|
index++;
|
|
42
|
-
} else if (!arg.startsWith(
|
|
34
|
+
} else if (!arg.startsWith('--') && !parsed.prdPath) {
|
|
43
35
|
parsed.prdPath = arg;
|
|
44
|
-
} else if (options.strict) {
|
|
45
|
-
throwUsage(t('check_prd_completeness.usage'), t('check_prd_completeness.example'));
|
|
46
36
|
}
|
|
47
37
|
}
|
|
48
38
|
|
|
@@ -637,7 +627,6 @@ async function run(args = []) {
|
|
|
637
627
|
}
|
|
638
628
|
|
|
639
629
|
module.exports = {
|
|
640
|
-
APP_TYPE_OPTIONS,
|
|
641
630
|
evaluatePrdCompleteness,
|
|
642
631
|
extractResources,
|
|
643
632
|
loadBuildManifest,
|
|
@@ -6,7 +6,6 @@ const crypto = require('crypto');
|
|
|
6
6
|
const { execFile } = require('child_process');
|
|
7
7
|
const { CliError } = require('../../core/cli-error');
|
|
8
8
|
const { t } = require('../../core/i18n');
|
|
9
|
-
const { redactSensitive } = require('../../core/redact');
|
|
10
9
|
const { usage, hint } = require('../../core/chalk');
|
|
11
10
|
const { assertNoEmojiInText, assertNoEmojiInValue } = require('../../core/no-emoji-guard');
|
|
12
11
|
const { normalizeYidaLocale } = require('../../core/yida-i18n');
|
|
@@ -195,6 +194,11 @@ function mergeCommandOutput(stdout, stderr, failed) {
|
|
|
195
194
|
return { ...stdoutOutput, ...stderrOutput };
|
|
196
195
|
}
|
|
197
196
|
|
|
197
|
+
function boundedDiagnostic(value) {
|
|
198
|
+
const text = String(value || '').trim();
|
|
199
|
+
return text.length > 4000 ? text.slice(-4000) : text;
|
|
200
|
+
}
|
|
201
|
+
|
|
198
202
|
function expectedReadbackFields(fields) {
|
|
199
203
|
const expected = [];
|
|
200
204
|
for (const field of Array.isArray(fields) ? fields : []) {
|
|
@@ -261,38 +265,14 @@ function buildDeliveryUrls(baseUrl, appType, formUuid) {
|
|
|
261
265
|
|
|
262
266
|
function execute(args, { execFile: execFileImpl = execFile } = {}) {
|
|
263
267
|
return new Promise((resolve, reject) => {
|
|
264
|
-
|
|
265
|
-
execFileImpl(process.execPath, [path.resolve(__dirname, '../../../bin/yida.js'), ...args, ...flags], {
|
|
268
|
+
execFileImpl(process.execPath, [path.resolve(__dirname, '../../../bin/yida.js'), ...args, '--quiet'], {
|
|
266
269
|
env: { ...process.env, YIDA_QUIET: '1' }, timeout: 180000, maxBuffer: 8 * 1024 * 1024,
|
|
267
270
|
}, (error, stdout, stderr) => {
|
|
268
271
|
const output = mergeCommandOutput(stdout, stderr, Boolean(error));
|
|
269
272
|
if (error || !output || typeof output !== 'object' || output.success === false) {
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// diagnostics and a known created ID, never Node's argv-bearing error.
|
|
274
|
-
let message = String(source?.errorMsg || source?.error || source?.message || stderr || 'Missing command result');
|
|
275
|
-
const grant = process.env.OPENYIDA_AGENT_TASK_GRANT;
|
|
276
|
-
if (grant) { message = message.split(grant).join('***'); }
|
|
277
|
-
// Diagnostics may contain embedded or incomplete JSON, not a plain
|
|
278
|
-
// object. Reuse the shared sensitive-key rules, then discard the tail
|
|
279
|
-
// rather than attempting to recover a possibly nested secret value.
|
|
280
|
-
for (const match of message.matchAll(/["']([^"'\r\n]{1,128})["']\s*:/g)) {
|
|
281
|
-
let key;
|
|
282
|
-
try { key = JSON.parse(`"${match[1]}"`); } catch { key = match[1].replace(/\\/g, ''); }
|
|
283
|
-
if (redactSensitive({ [key]: 'sensitive-value' })[key] !== 'sensitive-value') {
|
|
284
|
-
message = `${message.slice(0, match.index)}[REDACTED]`;
|
|
285
|
-
break;
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
message = redactSensitive(message).slice(0, 2048);
|
|
289
|
-
const errorCode = /^[A-Z][A-Z0-9_]{0,127}$/.test(source?.errorCode || error?.code || '')
|
|
290
|
-
? source?.errorCode || error.code : undefined;
|
|
291
|
-
const formUuid = [source?.formUuid, source?.details?.formUuid, output?.formUuid, output?.details?.formUuid]
|
|
292
|
-
.find(value => typeof value === 'string' && /^FORM[a-zA-Z0-9_-]{1,128}$/.test(value));
|
|
293
|
-
const safeOutput = { success: false, errorMsg: message,
|
|
294
|
-
...(errorCode ? { errorCode } : {}), ...(formUuid ? { formUuid } : {}) };
|
|
295
|
-
reject(Object.assign(new Error(message), { output: safeOutput }));
|
|
273
|
+
reject(Object.assign(new Error(
|
|
274
|
+
output?.errorMsg || output?.error || output?.message || boundedDiagnostic(stderr) || error?.message || 'Missing command result'
|
|
275
|
+
), { output }));
|
|
296
276
|
} else { resolve(output); }
|
|
297
277
|
});
|
|
298
278
|
});
|
|
@@ -326,7 +306,6 @@ async function schedule(forms, concurrency, results, worker, save) {
|
|
|
326
306
|
results[form.key] = { ...output, status: 'success' };
|
|
327
307
|
}, error => {
|
|
328
308
|
results[form.key] = { ...results[form.key], status: 'failed', error: error.message,
|
|
329
|
-
...(error.output?.errorCode ? { errorCode: error.output.errorCode } : {}),
|
|
330
309
|
...((error.output?.formUuid || error.output?.details?.formUuid) ? { formUuid: error.output.formUuid || error.output.details.formUuid } : {}) };
|
|
331
310
|
// A create may persist the form before a later schema/readback stage fails.
|
|
332
311
|
// Retry that known UUID once inside the same authoritative batch so its
|