openyida 2026.10.12-beta.2 → 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.
Files changed (167) hide show
  1. package/LICENSE +20 -20
  2. package/README.md +16 -27
  3. package/bin/yida.js +7 -32
  4. package/lib/app/app-theme.js +289 -0
  5. package/lib/app/check-prd-completeness.js +4 -15
  6. package/lib/app/create-form/batch.js +9 -30
  7. package/lib/app/create-form.js +50 -3
  8. package/lib/app/custom-theme.js +3 -13
  9. package/lib/app/get-schema.js +1 -6
  10. package/lib/app/nav-group-create.js +69 -0
  11. package/lib/app/nav-group.js +8 -28
  12. package/lib/app/theme-from-design.js +28 -16
  13. package/lib/app/theme-source.js +54 -0
  14. package/lib/app/update-app.js +1 -13
  15. package/lib/app/update-form-config.js +80 -71
  16. package/lib/auth/token-auth.js +6 -57
  17. package/lib/auth/token-store.js +2 -26
  18. package/lib/core/command-manifest.js +168 -175
  19. package/lib/core/locales/en.js +23 -47
  20. package/lib/core/locales/zh.js +23 -47
  21. package/lib/core/sample.js +21 -2
  22. package/lib/core/theme-widget.js +115 -0
  23. package/lib/core/update.js +1 -3
  24. package/lib/core/utils.js +19 -29
  25. package/lib/design/document.js +8 -3
  26. package/lib/design/palette-contrast.js +16 -3
  27. package/lib/design-plan/confirmation.js +1 -1
  28. package/lib/design-plan/init.js +21 -6
  29. package/lib/design-plan/materialize.js +30 -9
  30. package/lib/design-plan/patch.js +4 -2
  31. package/lib/design-plan/themes.js +6 -1
  32. package/lib/design-plan/visual-policy.js +70 -8
  33. package/lib/page-config/save-share-config.js +5 -16
  34. package/package.json +1 -10
  35. package/scripts/postinstall.js +3 -1
  36. package/yida-skills/SKILL.md +3 -27
  37. package/yida-skills/skills/yida-app/workflow/parallel-work.md +1 -1
  38. package/yida-skills/skills/yida-app/workflow/plan/step-4-deliver.md +3 -3
  39. package/yida-skills/skills/yida-app/workflow/plan/workflow.md +1 -1
  40. package/yida-skills/skills/yida-app/workflow/step-2-design.md +3 -1
  41. package/yida-skills/skills/yida-app/workflow/step-3-create-or-reuse-app.md +1 -1
  42. package/yida-skills/skills/yida-canvas-custom-page/SKILL.md +6 -0
  43. package/yida-skills/skills/yida-create-form-page/SKILL.md +5 -1
  44. package/yida-skills/skills/yida-custom-page/SKILL.md +6 -0
  45. package/yida-skills/skills/yida-dashboard/SKILL.md +2 -2
  46. package/yida-skills/skills/yida-dashboard/references/theme-presets.md +20 -245
  47. package/yida-skills/skills/yida-design/SKILL.md +22 -10
  48. package/yida-skills/skills/yida-design/references/application-style-library.md +19 -6
  49. package/yida-skills/skills/yida-design/references/application-theme-consistency.md +13 -7
  50. package/yida-skills/skills/yida-design/references/ask-human-interaction-contract.md +19 -9
  51. package/yida-skills/skills/yida-design/references/navigation-decision.md +14 -1
  52. package/yida-skills/skills/yida-design/references/page-quality-gates.md +16 -0
  53. package/yida-skills/skills/yida-design/references/responsive-layout.md +54 -0
  54. package/yida-skills/skills/yida-design/references/theme/theme-token-presets.md +5 -24
  55. package/yida-skills/skills/yida-design/references/theme-preview-images.json +19 -0
  56. package/yida-skills/skills/yida-design/references/theme-selection.md +80 -9
  57. package/yida-skills/skills/yida-design/scripts/validate_design_themes.py +324 -303
  58. package/yida-skills/skills/yida-design/sub_skill/page-design/SKILL.md +2 -0
  59. package/yida-skills/skills/yida-design/sub_skill/ui-improve/SKILL.md +86 -0
  60. package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/application-coverage.md +44 -0
  61. package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/backup-and-restore.md +65 -0
  62. package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/component-replacement.md +18 -0
  63. package/yida-skills/skills/yida-design/sub_skill/ui-improve/references/confirmation.md +26 -0
  64. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/SKILL.md +5 -1
  65. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/build-plan-template.html +1186 -1186
  66. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-normal.svg +4 -4
  67. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/form-process.svg +6 -6
  68. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-fit.svg +3 -3
  69. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-relations.svg +7 -7
  70. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-in.svg +4 -4
  71. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/assets/icons/graph-zoom-out.svg +4 -4
  72. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/references/visual-theme-selection.md +29 -22
  73. package/yida-skills/skills/yida-design/sub_skill/yida-design-plan/scripts/render_build_plan.py +1166 -1166
  74. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/app_theme.css +2 -2
  75. package/yida-skills/skills/yida-design/templates/design-themes/app-editorial/design.md +4 -2
  76. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/app_theme.css +2 -2
  77. package/yida-skills/skills/yida-design/templates/design-themes/app-executive/design.md +4 -2
  78. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/app_theme.css +2 -2
  79. package/yida-skills/skills/yida-design/templates/design-themes/app-finance/design.md +4 -2
  80. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/app_theme.css +2 -2
  81. package/yida-skills/skills/yida-design/templates/design-themes/app-glass/design.md +4 -2
  82. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/app_theme.css +2 -2
  83. package/yida-skills/skills/yida-design/templates/design-themes/app-graphite/design.md +4 -2
  84. package/yida-skills/skills/yida-design/templates/design-themes/app-line/app_theme.css +2 -2
  85. package/yida-skills/skills/yida-design/templates/design-themes/app-line/design.md +4 -2
  86. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/app_theme.css +2 -2
  87. package/yida-skills/skills/yida-design/templates/design-themes/app-neon/design.md +4 -2
  88. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/app_theme.css +2 -2
  89. package/yida-skills/skills/yida-design/templates/design-themes/app-nordic/design.md +4 -2
  90. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/app_theme.css +2 -2
  91. package/yida-skills/skills/yida-design/templates/design-themes/app-paper/design.md +4 -2
  92. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/app_theme.css +71 -67
  93. package/yida-skills/skills/yida-design/templates/design-themes/app-platinum/design.md +108 -69
  94. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/app_theme.css +2 -2
  95. package/yida-skills/skills/yida-design/templates/design-themes/app-plum/design.md +4 -2
  96. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/app_theme.css +229 -40
  97. package/yida-skills/skills/yida-design/templates/design-themes/app-pop/design.md +54 -44
  98. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/app_theme.css +107 -0
  99. package/yida-skills/skills/yida-design/templates/design-themes/app-sage/design.md +43 -1
  100. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/app_theme.css +16 -16
  101. package/yida-skills/skills/yida-design/templates/design-themes/app-teal/design.md +98 -17
  102. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/app_theme.css +23 -23
  103. package/yida-skills/skills/yida-design/templates/design-themes/app-terminal/design.md +98 -25
  104. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/app_theme.css +2 -2
  105. package/yida-skills/skills/yida-design/templates/design-themes/app-ticket/design.md +4 -2
  106. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/app_theme.css +2 -2
  107. package/yida-skills/skills/yida-design/templates/design-themes/app-wire/design.md +4 -2
  108. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/app_theme.css +15 -4
  109. package/yida-skills/skills/yida-design/templates/design-themes/dark-inset-hairline/design.md +22 -10
  110. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/app_theme.css +21 -10
  111. package/yida-skills/skills/yida-design/templates/design-themes/dark-rail-fine-lines/design.md +24 -20
  112. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/app_theme.css +2 -2
  113. package/yida-skills/skills/yida-design/templates/design-themes/free-creative/design.md +4 -2
  114. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/app_theme.css +160 -18
  115. package/yida-skills/skills/yida-design/templates/design-themes/hairline-soft-blocks/design.md +57 -36
  116. package/yida-skills/skills/yida-design/templates/design-themes/index.json +73 -16
  117. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/app_theme.css +163 -29
  118. package/yida-skills/skills/yida-design/templates/design-themes/ink-glow-soft-panels/design.md +85 -74
  119. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/app_theme.css +170 -27
  120. package/yida-skills/skills/yida-design/templates/design-themes/inset-frame-pixel-rhythm/design.md +50 -29
  121. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/app_theme.css +155 -36
  122. package/yida-skills/skills/yida-design/templates/design-themes/outlined-texture-duotone/design.md +46 -26
  123. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/app_theme.css +161 -29
  124. package/yida-skills/skills/yida-design/templates/design-themes/soft-inset-surfaces/design.md +70 -36
  125. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/app_theme.css +119 -8
  126. package/yida-skills/skills/yida-design/templates/design-themes/soft-outline-rhythm/design.md +31 -23
  127. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/app_theme.css +166 -27
  128. package/yida-skills/skills/yida-design/templates/design-themes/soft-rail-bold-band/design.md +50 -30
  129. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/app_theme.css +152 -15
  130. package/yida-skills/skills/yida-design/templates/design-themes/warm-canvas-contrast-panels/design.md +38 -20
  131. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/app_theme.css +1 -1
  132. package/yida-skills/skills/yida-design/templates/design-themes/warm-rail-muted-panels/design.md +3 -1
  133. package/yida-skills/skills/yida-design/templates/theme-selector.html +83 -0
  134. package/yida-skills/skills/yida-design/workflow/output-design.md +7 -4
  135. package/yida-skills/skills/yida-design/workflow/step-4-wireframe-interaction.md +2 -0
  136. package/yida-skills/skills/yida-design/workflow/step-5-visual-states.md +2 -0
  137. package/yida-skills/skills/yida-document-markdown/SKILL.md +0 -6
  138. package/yida-skills/skills/yida-nav-group/SKILL.md +6 -2
  139. package/yida-skills/skills/yida-nav-shell/SKILL.md +2 -0
  140. package/yida-skills/skills/yida-prd/SKILL.md +2 -0
  141. package/yida-skills/skills/yida-publish-page/SKILL.md +4 -0
  142. package/yida-skills/skills/yida-requirement-analysis/references/handoff.md +4 -2
  143. package/yida-skills/skills/yida-requirement-analysis/workflow/prepare-brief.md +2 -0
  144. package/yida-skills/skills/yida-tingji/SKILL.md +0 -4
  145. package/yida-skills/skills-index.json +11 -3
  146. package/lib/agent/PROTOCOL.md +0 -182
  147. package/lib/agent/ask-human.js +0 -151
  148. package/lib/agent/cmd.js +0 -405
  149. package/lib/agent/command-receipt.js +0 -84
  150. package/lib/agent/connection-store.js +0 -148
  151. package/lib/agent/diagnostic-log.js +0 -133
  152. package/lib/agent/frozen-bundle.js +0 -286
  153. package/lib/agent/managed-command-map.js +0 -36
  154. package/lib/agent/managed-context.js +0 -40
  155. package/lib/agent/managed-run.js +0 -85
  156. package/lib/agent/mutation-receipt.js +0 -82
  157. package/lib/agent/private-directory.js +0 -63
  158. package/lib/agent/provider-preflight.js +0 -257
  159. package/lib/agent/providers.js +0 -4
  160. package/lib/agent/runtime-catalog.json +0 -45
  161. package/lib/agent/runtime-manifest.js +0 -96
  162. package/lib/agent/runtime-package.js +0 -155
  163. package/lib/agent/stdio.js +0 -321
  164. package/lib/auth/profile-lock.js +0 -89
  165. package/lib/core/managed-policy.js +0 -81
  166. package/yida-skills/references/local-agent-diagnostics.md +0 -154
  167. 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
- const { run: runUpdateFormConfig } = require('../lib/app/update-form-config');
926
- await runUpdateFormConfig(args);
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 = [], options = {}) {
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 (APP_TYPE_OPTIONS.includes(arg) && args[index + 1] && (!options.strict || !args[index + 1].startsWith('-'))) {
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] && (!options.strict || !args[index + 1].startsWith('-'))) {
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(options.strict ? '-' : '--') && !parsed.prdPath) {
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
- const flags = ['--json', '--quiet'].filter(flag => !args.includes(flag));
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
- const failure = parseOutput(stderr);
271
- const source = failure?.success === false ? failure : output;
272
- // Child errors are written to stderr. Keep only bounded, redacted
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