nubos-pilot 0.7.2 → 0.8.0

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 (241) hide show
  1. package/agents/np-executor.md +1 -0
  2. package/bin/install.js +2 -0
  3. package/bin/np-tools/_commands.cjs +98 -64
  4. package/bin/np-tools/dashboard.cjs +11 -5
  5. package/bin/np-tools/dashboard.test.cjs +80 -0
  6. package/bin/np-tools/help.cjs +16 -7
  7. package/bin/np-tools/help.test.cjs +63 -0
  8. package/bin/np-tools/stats.cjs +99 -3
  9. package/bin/np-tools/stats.test.cjs +65 -0
  10. package/lib/dashboard.cjs +41 -10
  11. package/lib/dashboard.test.cjs +83 -0
  12. package/lib/install/runtime-assets.cjs +50 -1
  13. package/lib/install/runtime-assets.test.cjs +190 -0
  14. package/lib/install/runtimes-registry.cjs +1 -0
  15. package/lib/runtime/_readline.cjs +49 -11
  16. package/lib/runtime/_readline.test.cjs +59 -0
  17. package/lib/runtime/claude.cjs +8 -1
  18. package/package.json +2 -1
  19. package/skills/np-composition-patterns/AGENTS.md +946 -0
  20. package/skills/np-composition-patterns/README.md +60 -0
  21. package/skills/np-composition-patterns/SKILL.md +89 -0
  22. package/skills/np-composition-patterns/metadata.json +11 -0
  23. package/skills/np-composition-patterns/rules/_sections.md +29 -0
  24. package/skills/np-composition-patterns/rules/_template.md +24 -0
  25. package/skills/np-composition-patterns/rules/architecture-avoid-boolean-props.md +100 -0
  26. package/skills/np-composition-patterns/rules/architecture-compound-components.md +112 -0
  27. package/skills/np-composition-patterns/rules/patterns-children-over-render-props.md +87 -0
  28. package/skills/np-composition-patterns/rules/patterns-explicit-variants.md +100 -0
  29. package/skills/np-composition-patterns/rules/react19-no-forwardref.md +42 -0
  30. package/skills/np-composition-patterns/rules/state-context-interface.md +191 -0
  31. package/skills/np-composition-patterns/rules/state-decouple-implementation.md +113 -0
  32. package/skills/np-composition-patterns/rules/state-lift-state.md +125 -0
  33. package/skills/np-council/SKILL.md +300 -0
  34. package/skills/np-design/SKILL.md +679 -0
  35. package/skills/np-frontend-design/LICENSE.txt +177 -0
  36. package/skills/np-frontend-design/SKILL.md +42 -0
  37. package/skills/np-high-end-visual-design/SKILL.md +98 -0
  38. package/skills/np-impeccable/SKILL.md +152 -0
  39. package/skills/np-impeccable/agents/openai.yaml +4 -0
  40. package/skills/np-impeccable/reference/adapt.md +190 -0
  41. package/skills/np-impeccable/reference/animate.md +173 -0
  42. package/skills/np-impeccable/reference/audit.md +134 -0
  43. package/skills/np-impeccable/reference/bolder.md +113 -0
  44. package/skills/np-impeccable/reference/brand.md +104 -0
  45. package/skills/np-impeccable/reference/clarify.md +174 -0
  46. package/skills/np-impeccable/reference/cognitive-load.md +106 -0
  47. package/skills/np-impeccable/reference/color-and-contrast.md +105 -0
  48. package/skills/np-impeccable/reference/colorize.md +154 -0
  49. package/skills/np-impeccable/reference/craft.md +138 -0
  50. package/skills/np-impeccable/reference/critique.md +213 -0
  51. package/skills/np-impeccable/reference/delight.md +302 -0
  52. package/skills/np-impeccable/reference/distill.md +111 -0
  53. package/skills/np-impeccable/reference/document.md +427 -0
  54. package/skills/np-impeccable/reference/extract.md +70 -0
  55. package/skills/np-impeccable/reference/harden.md +347 -0
  56. package/skills/np-impeccable/reference/heuristics-scoring.md +234 -0
  57. package/skills/np-impeccable/reference/interaction-design.md +195 -0
  58. package/skills/np-impeccable/reference/layout.md +141 -0
  59. package/skills/np-impeccable/reference/live.md +513 -0
  60. package/skills/np-impeccable/reference/motion-design.md +99 -0
  61. package/skills/np-impeccable/reference/onboard.md +234 -0
  62. package/skills/np-impeccable/reference/optimize.md +258 -0
  63. package/skills/np-impeccable/reference/overdrive.md +130 -0
  64. package/skills/np-impeccable/reference/personas.md +178 -0
  65. package/skills/np-impeccable/reference/polish.md +232 -0
  66. package/skills/np-impeccable/reference/product.md +62 -0
  67. package/skills/np-impeccable/reference/quieter.md +99 -0
  68. package/skills/np-impeccable/reference/responsive-design.md +114 -0
  69. package/skills/np-impeccable/reference/shape.md +136 -0
  70. package/skills/np-impeccable/reference/spatial-design.md +100 -0
  71. package/skills/np-impeccable/reference/teach.md +137 -0
  72. package/skills/np-impeccable/reference/typeset.md +124 -0
  73. package/skills/np-impeccable/reference/typography.md +159 -0
  74. package/skills/np-impeccable/reference/ux-writing.md +107 -0
  75. package/skills/np-impeccable/scripts/cleanup-deprecated.mjs +284 -0
  76. package/skills/np-impeccable/scripts/command-metadata.json +94 -0
  77. package/skills/np-impeccable/scripts/design-parser.mjs +820 -0
  78. package/skills/np-impeccable/scripts/detect-csp.mjs +198 -0
  79. package/skills/np-impeccable/scripts/is-generated.mjs +69 -0
  80. package/skills/np-impeccable/scripts/live-accept.mjs +465 -0
  81. package/skills/np-impeccable/scripts/live-browser.js +4684 -0
  82. package/skills/np-impeccable/scripts/live-inject.mjs +436 -0
  83. package/skills/np-impeccable/scripts/live-poll.mjs +187 -0
  84. package/skills/np-impeccable/scripts/live-server.mjs +679 -0
  85. package/skills/np-impeccable/scripts/live-wrap.mjs +395 -0
  86. package/skills/np-impeccable/scripts/live.mjs +247 -0
  87. package/skills/np-impeccable/scripts/load-context.mjs +93 -0
  88. package/skills/np-impeccable/scripts/modern-screenshot.umd.js +14 -0
  89. package/skills/np-impeccable/scripts/pin.mjs +214 -0
  90. package/skills/np-industrial-brutalist-ui/SKILL.md +92 -0
  91. package/skills/np-minimalist-ui/SKILL.md +85 -0
  92. package/skills/np-react-best-practices/AGENTS.md +3810 -0
  93. package/skills/np-react-best-practices/README.md +123 -0
  94. package/skills/np-react-best-practices/SKILL.md +149 -0
  95. package/skills/np-react-best-practices/metadata.json +15 -0
  96. package/skills/np-react-best-practices/rules/_sections.md +46 -0
  97. package/skills/np-react-best-practices/rules/_template.md +28 -0
  98. package/skills/np-react-best-practices/rules/advanced-effect-event-deps.md +56 -0
  99. package/skills/np-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  100. package/skills/np-react-best-practices/rules/advanced-init-once.md +42 -0
  101. package/skills/np-react-best-practices/rules/advanced-use-latest.md +39 -0
  102. package/skills/np-react-best-practices/rules/async-api-routes.md +38 -0
  103. package/skills/np-react-best-practices/rules/async-cheap-condition-before-await.md +37 -0
  104. package/skills/np-react-best-practices/rules/async-defer-await.md +82 -0
  105. package/skills/np-react-best-practices/rules/async-dependencies.md +51 -0
  106. package/skills/np-react-best-practices/rules/async-parallel.md +28 -0
  107. package/skills/np-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  108. package/skills/np-react-best-practices/rules/bundle-analyzable-paths.md +63 -0
  109. package/skills/np-react-best-practices/rules/bundle-barrel-imports.md +60 -0
  110. package/skills/np-react-best-practices/rules/bundle-conditional.md +31 -0
  111. package/skills/np-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  112. package/skills/np-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  113. package/skills/np-react-best-practices/rules/bundle-preload.md +50 -0
  114. package/skills/np-react-best-practices/rules/client-event-listeners.md +74 -0
  115. package/skills/np-react-best-practices/rules/client-localstorage-schema.md +71 -0
  116. package/skills/np-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  117. package/skills/np-react-best-practices/rules/client-swr-dedup.md +56 -0
  118. package/skills/np-react-best-practices/rules/js-batch-dom-css.md +107 -0
  119. package/skills/np-react-best-practices/rules/js-cache-function-results.md +80 -0
  120. package/skills/np-react-best-practices/rules/js-cache-property-access.md +28 -0
  121. package/skills/np-react-best-practices/rules/js-cache-storage.md +70 -0
  122. package/skills/np-react-best-practices/rules/js-combine-iterations.md +32 -0
  123. package/skills/np-react-best-practices/rules/js-early-exit.md +50 -0
  124. package/skills/np-react-best-practices/rules/js-flatmap-filter.md +60 -0
  125. package/skills/np-react-best-practices/rules/js-hoist-regexp.md +45 -0
  126. package/skills/np-react-best-practices/rules/js-index-maps.md +37 -0
  127. package/skills/np-react-best-practices/rules/js-length-check-first.md +49 -0
  128. package/skills/np-react-best-practices/rules/js-min-max-loop.md +82 -0
  129. package/skills/np-react-best-practices/rules/js-request-idle-callback.md +105 -0
  130. package/skills/np-react-best-practices/rules/js-set-map-lookups.md +24 -0
  131. package/skills/np-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  132. package/skills/np-react-best-practices/rules/rendering-activity.md +26 -0
  133. package/skills/np-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  134. package/skills/np-react-best-practices/rules/rendering-conditional-render.md +40 -0
  135. package/skills/np-react-best-practices/rules/rendering-content-visibility.md +38 -0
  136. package/skills/np-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  137. package/skills/np-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  138. package/skills/np-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  139. package/skills/np-react-best-practices/rules/rendering-resource-hints.md +85 -0
  140. package/skills/np-react-best-practices/rules/rendering-script-defer-async.md +68 -0
  141. package/skills/np-react-best-practices/rules/rendering-svg-precision.md +28 -0
  142. package/skills/np-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  143. package/skills/np-react-best-practices/rules/rerender-defer-reads.md +39 -0
  144. package/skills/np-react-best-practices/rules/rerender-dependencies.md +45 -0
  145. package/skills/np-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  146. package/skills/np-react-best-practices/rules/rerender-derived-state.md +29 -0
  147. package/skills/np-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  148. package/skills/np-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  149. package/skills/np-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  150. package/skills/np-react-best-practices/rules/rerender-memo.md +44 -0
  151. package/skills/np-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  152. package/skills/np-react-best-practices/rules/rerender-no-inline-components.md +82 -0
  153. package/skills/np-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  154. package/skills/np-react-best-practices/rules/rerender-split-combined-hooks.md +64 -0
  155. package/skills/np-react-best-practices/rules/rerender-transitions.md +40 -0
  156. package/skills/np-react-best-practices/rules/rerender-use-deferred-value.md +59 -0
  157. package/skills/np-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  158. package/skills/np-react-best-practices/rules/server-after-nonblocking.md +73 -0
  159. package/skills/np-react-best-practices/rules/server-auth-actions.md +96 -0
  160. package/skills/np-react-best-practices/rules/server-cache-lru.md +41 -0
  161. package/skills/np-react-best-practices/rules/server-cache-react.md +76 -0
  162. package/skills/np-react-best-practices/rules/server-dedup-props.md +65 -0
  163. package/skills/np-react-best-practices/rules/server-hoist-static-io.md +149 -0
  164. package/skills/np-react-best-practices/rules/server-no-shared-module-state.md +50 -0
  165. package/skills/np-react-best-practices/rules/server-parallel-fetching.md +83 -0
  166. package/skills/np-react-best-practices/rules/server-parallel-nested-fetching.md +34 -0
  167. package/skills/np-react-best-practices/rules/server-serialization.md +38 -0
  168. package/skills/np-react-native-skills/AGENTS.md +2897 -0
  169. package/skills/np-react-native-skills/README.md +165 -0
  170. package/skills/np-react-native-skills/SKILL.md +121 -0
  171. package/skills/np-react-native-skills/metadata.json +16 -0
  172. package/skills/np-react-native-skills/rules/_sections.md +86 -0
  173. package/skills/np-react-native-skills/rules/_template.md +28 -0
  174. package/skills/np-react-native-skills/rules/animation-derived-value.md +53 -0
  175. package/skills/np-react-native-skills/rules/animation-gesture-detector-press.md +95 -0
  176. package/skills/np-react-native-skills/rules/animation-gpu-properties.md +65 -0
  177. package/skills/np-react-native-skills/rules/design-system-compound-components.md +66 -0
  178. package/skills/np-react-native-skills/rules/fonts-config-plugin.md +71 -0
  179. package/skills/np-react-native-skills/rules/imports-design-system-folder.md +68 -0
  180. package/skills/np-react-native-skills/rules/js-hoist-intl.md +61 -0
  181. package/skills/np-react-native-skills/rules/list-performance-callbacks.md +44 -0
  182. package/skills/np-react-native-skills/rules/list-performance-function-references.md +132 -0
  183. package/skills/np-react-native-skills/rules/list-performance-images.md +53 -0
  184. package/skills/np-react-native-skills/rules/list-performance-inline-objects.md +97 -0
  185. package/skills/np-react-native-skills/rules/list-performance-item-expensive.md +94 -0
  186. package/skills/np-react-native-skills/rules/list-performance-item-memo.md +82 -0
  187. package/skills/np-react-native-skills/rules/list-performance-item-types.md +104 -0
  188. package/skills/np-react-native-skills/rules/list-performance-virtualize.md +67 -0
  189. package/skills/np-react-native-skills/rules/monorepo-native-deps-in-app.md +46 -0
  190. package/skills/np-react-native-skills/rules/monorepo-single-dependency-versions.md +63 -0
  191. package/skills/np-react-native-skills/rules/navigation-native-navigators.md +188 -0
  192. package/skills/np-react-native-skills/rules/react-compiler-destructure-functions.md +50 -0
  193. package/skills/np-react-native-skills/rules/react-compiler-reanimated-shared-values.md +48 -0
  194. package/skills/np-react-native-skills/rules/react-state-dispatcher.md +91 -0
  195. package/skills/np-react-native-skills/rules/react-state-fallback.md +56 -0
  196. package/skills/np-react-native-skills/rules/react-state-minimize.md +65 -0
  197. package/skills/np-react-native-skills/rules/rendering-no-falsy-and.md +74 -0
  198. package/skills/np-react-native-skills/rules/rendering-text-in-text-component.md +36 -0
  199. package/skills/np-react-native-skills/rules/scroll-position-no-state.md +82 -0
  200. package/skills/np-react-native-skills/rules/state-ground-truth.md +80 -0
  201. package/skills/np-react-native-skills/rules/ui-expo-image.md +66 -0
  202. package/skills/np-react-native-skills/rules/ui-image-gallery.md +104 -0
  203. package/skills/np-react-native-skills/rules/ui-measure-views.md +78 -0
  204. package/skills/np-react-native-skills/rules/ui-menus.md +174 -0
  205. package/skills/np-react-native-skills/rules/ui-native-modals.md +77 -0
  206. package/skills/np-react-native-skills/rules/ui-pressable.md +61 -0
  207. package/skills/np-react-native-skills/rules/ui-safe-area-scroll.md +65 -0
  208. package/skills/np-react-native-skills/rules/ui-scrollview-content-inset.md +45 -0
  209. package/skills/np-react-native-skills/rules/ui-styling.md +87 -0
  210. package/skills/np-react-view-transitions/AGENTS.md +955 -0
  211. package/skills/np-react-view-transitions/README.md +42 -0
  212. package/skills/np-react-view-transitions/SKILL.md +320 -0
  213. package/skills/np-react-view-transitions/metadata.json +12 -0
  214. package/skills/np-react-view-transitions/references/css-recipes.md +242 -0
  215. package/skills/np-react-view-transitions/references/implementation.md +182 -0
  216. package/skills/np-react-view-transitions/references/nextjs.md +176 -0
  217. package/skills/np-react-view-transitions/references/patterns.md +262 -0
  218. package/skills/np-redesign-existing-projects/SKILL.md +178 -0
  219. package/skills/np-shadcn/SKILL.md +250 -0
  220. package/skills/np-shadcn/agents/openai.yml +5 -0
  221. package/skills/np-shadcn/assets/shadcn-small.png +0 -0
  222. package/skills/np-shadcn/assets/shadcn.png +0 -0
  223. package/skills/np-shadcn/cli.md +276 -0
  224. package/skills/np-shadcn/customization.md +209 -0
  225. package/skills/np-shadcn/evals/evals.json +47 -0
  226. package/skills/np-shadcn/mcp.md +94 -0
  227. package/skills/np-shadcn/rules/base-vs-radix.md +306 -0
  228. package/skills/np-shadcn/rules/composition.md +195 -0
  229. package/skills/np-shadcn/rules/forms.md +192 -0
  230. package/skills/np-shadcn/rules/icons.md +101 -0
  231. package/skills/np-shadcn/rules/styling.md +162 -0
  232. package/skills/np-stitch-design-taste/DESIGN.md +121 -0
  233. package/skills/np-stitch-design-taste/SKILL.md +184 -0
  234. package/skills/np-web-design-guidelines/SKILL.md +39 -0
  235. package/workflows/add-todo.md +5 -0
  236. package/workflows/discuss-phase.md +2 -0
  237. package/workflows/execute-phase.md +27 -0
  238. package/workflows/note.md +5 -0
  239. package/workflows/plan-phase.md +12 -0
  240. package/workflows/stats.md +27 -90
  241. package/workflows/verify-work.md +12 -0
@@ -189,6 +189,71 @@ test('STATS-2: unknown subcommand prints usage', async () => {
189
189
  assert.match(stderr.toString(), /Usage:/);
190
190
  });
191
191
 
192
+ test('STATS-MD-1: stats markdown emits English title + headers when no config', async () => {
193
+ const sb = makeSandbox(DEMO_YAML, DEMO_STATE);
194
+ writeTaskPlan(sb, 1, 1, 1, 'done');
195
+ const stdout = makeSink();
196
+ const stderr = makeSink();
197
+ const code = await statsCli.run(['markdown'], { cwd: sb, stdout, stderr });
198
+ assert.equal(code, 0, 'stderr=' + stderr.toString());
199
+ const out = stdout.toString();
200
+ assert.match(out, /^## Project Stats/m);
201
+ assert.match(out, /^\*\*Milestone:\*\*/m);
202
+ assert.match(out, /^\*\*Progress:\*\*/m);
203
+ assert.match(out, /^### Phases/m);
204
+ assert.match(out, /^### Metrics by Phase/m);
205
+ });
206
+
207
+ test('STATS-MD-2: stats markdown emits German labels when config.response_language=de', async () => {
208
+ const sb = makeSandbox(DEMO_YAML, DEMO_STATE);
209
+ fs.writeFileSync(
210
+ path.join(sb, '.nubos-pilot', 'config.json'),
211
+ JSON.stringify({ response_language: 'de' }),
212
+ );
213
+ writeTaskPlan(sb, 1, 1, 1, 'done');
214
+ const stdout = makeSink();
215
+ const stderr = makeSink();
216
+ const code = await statsCli.run(['markdown'], { cwd: sb, stdout, stderr });
217
+ assert.equal(code, 0, 'stderr=' + stderr.toString());
218
+ const out = stdout.toString();
219
+ assert.match(out, /^## Projekt-Stats/m);
220
+ assert.match(out, /^\*\*Fortschritt:\*\*/m);
221
+ assert.match(out, /^\*\*Letzte Aktivität:\*\*/m);
222
+ assert.match(out, /^\*\*Projekt-Start:\*\*/m);
223
+ assert.match(out, /^### Phasen/m);
224
+ assert.match(out, /^### Metriken pro Phase/m);
225
+ assert.match(out, /Pläne/);
226
+ assert.equal(/^## Project Stats/m.test(out), false, 'no English title');
227
+ });
228
+
229
+ test('STATS-MD-3: --lang flag overrides config language', async () => {
230
+ const sb = makeSandbox(DEMO_YAML, DEMO_STATE);
231
+ fs.writeFileSync(
232
+ path.join(sb, '.nubos-pilot', 'config.json'),
233
+ JSON.stringify({ response_language: 'de' }),
234
+ );
235
+ writeTaskPlan(sb, 1, 1, 1, 'done');
236
+ const stdout = makeSink();
237
+ const stderr = makeSink();
238
+ const code = await statsCli.run(['markdown', '--lang', 'en'], { cwd: sb, stdout, stderr });
239
+ assert.equal(code, 0, 'stderr=' + stderr.toString());
240
+ assert.match(stdout.toString(), /^## Project Stats/m);
241
+ });
242
+
243
+ test('STATS-MD-4: _renderMarkdown is a pure function callable without project', () => {
244
+ const md = statsCli._renderMarkdown({
245
+ schema_version: 2,
246
+ milestone: { version: 'v1', name: 'Auth' },
247
+ phases: [{ number: '1', name: 'F', plans_total: 2, plans_complete: 1, status: 'in-progress' }],
248
+ plans_total: 2, plans_complete: 1, percent: 50,
249
+ git: { commits: 5, first_commit_at: '2026-01-01' },
250
+ last_activity: '2026-04-01T00:00:00Z',
251
+ metrics_by_phase: {},
252
+ }, 'de');
253
+ assert.match(md, /Projekt-Stats/);
254
+ assert.match(md, /Pläne/);
255
+ });
256
+
192
257
  test('STATS-3: outside project emits NubosPilotError envelope', async () => {
193
258
  const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'np-stats-outside-'));
194
259
  _sandboxes.push(tmp);
package/lib/dashboard.cjs CHANGED
@@ -5,6 +5,35 @@ const path = require('node:path');
5
5
 
6
6
  const { extractFrontmatter } = require('./frontmatter.cjs');
7
7
  const { listMilestones, listSlices, listTasks, mId } = require('./layout.cjs');
8
+ const { normalizeLanguage, DEFAULT_LANGUAGE } = require('./language.cjs');
9
+
10
+ const LABELS = Object.freeze({
11
+ en: {
12
+ done: 'done',
13
+ 'in-progress': 'in-progress',
14
+ pending: 'pending',
15
+ skipped: 'skipped',
16
+ parked: 'parked',
17
+ no_tasks: 'no tasks',
18
+ no_slices: 'no slices planned',
19
+ no_milestones: 'No milestones yet. Run /np:new-project or /np:new-milestone.',
20
+ },
21
+ de: {
22
+ done: 'erledigt',
23
+ 'in-progress': 'in Arbeit',
24
+ pending: 'offen',
25
+ skipped: 'übersprungen',
26
+ parked: 'geparkt',
27
+ no_tasks: 'keine Tasks',
28
+ no_slices: 'keine Slices geplant',
29
+ no_milestones: 'Noch keine Milestones. Führe /np:new-project oder /np:new-milestone aus.',
30
+ },
31
+ });
32
+
33
+ function _labelsFor(language) {
34
+ const lang = normalizeLanguage(language || DEFAULT_LANGUAGE);
35
+ return LABELS[lang] || LABELS[DEFAULT_LANGUAGE];
36
+ }
8
37
 
9
38
  const ANSI = Object.freeze({
10
39
  reset: '\x1b[0m',
@@ -83,15 +112,15 @@ function collectSnapshot(projectRoot) {
83
112
  return { milestones: _collectMilestones(cwd) };
84
113
  }
85
114
 
86
- function _summarizeCounts(c, useColor) {
115
+ function _summarizeCounts(c, useColor, labels) {
87
116
  const paint = (code, text) => useColor ? code + text + ANSI.reset : text;
88
117
  const bits = [];
89
- if (c.done) bits.push(paint(ANSI.green, c.done + ' done'));
90
- if (c['in-progress']) bits.push(paint(ANSI.yellow, c['in-progress'] + ' in-progress'));
91
- if (c.pending) bits.push(paint(ANSI.gray, c.pending + ' pending'));
92
- if (c.skipped) bits.push(paint(ANSI.dim, c.skipped + ' skipped'));
93
- if (c.parked) bits.push(paint(ANSI.red, c.parked + ' parked'));
94
- return bits.join(' · ') || paint(ANSI.dim, 'no tasks');
118
+ if (c.done) bits.push(paint(ANSI.green, c.done + ' ' + labels.done));
119
+ if (c['in-progress']) bits.push(paint(ANSI.yellow, c['in-progress'] + ' ' + labels['in-progress']));
120
+ if (c.pending) bits.push(paint(ANSI.gray, c.pending + ' ' + labels.pending));
121
+ if (c.skipped) bits.push(paint(ANSI.dim, c.skipped + ' ' + labels.skipped));
122
+ if (c.parked) bits.push(paint(ANSI.red, c.parked + ' ' + labels.parked));
123
+ return bits.join(' · ') || paint(ANSI.dim, labels.no_tasks);
95
124
  }
96
125
 
97
126
  function _checkboxRow(statuses, useColor) {
@@ -106,6 +135,7 @@ function _checkboxRow(statuses, useColor) {
106
135
  function renderSnapshot(snap, opts) {
107
136
  const o = opts || {};
108
137
  const useColor = o.color !== false;
138
+ const labels = _labelsFor(o.language);
109
139
  const c = (code, text) => useColor ? code + text + ANSI.reset : text;
110
140
  const lines = [];
111
141
 
@@ -113,7 +143,7 @@ function renderSnapshot(snap, opts) {
113
143
  lines.push('');
114
144
 
115
145
  if (!snap.milestones || snap.milestones.length === 0) {
116
- lines.push(c(ANSI.dim, 'No milestones yet. Run /np:new-project or /np:new-milestone.'));
146
+ lines.push(c(ANSI.dim, labels.no_milestones));
117
147
  lines.push('');
118
148
  return lines.join('\n');
119
149
  }
@@ -123,10 +153,10 @@ function renderSnapshot(snap, opts) {
123
153
  const status = m.status ? ' ' + c(ANSI.dim, '[' + m.status + ']') : '';
124
154
  lines.push(c(ANSI.bold, m.id) + name + status);
125
155
  if (m.slices.length === 0) {
126
- lines.push(' ' + c(ANSI.dim, 'no slices planned'));
156
+ lines.push(' ' + c(ANSI.dim, labels.no_slices));
127
157
  }
128
158
  for (const s of m.slices) {
129
- lines.push(' ' + c(ANSI.bold, s.full_id) + ' ' + _summarizeCounts(s.counts, useColor));
159
+ lines.push(' ' + c(ANSI.bold, s.full_id) + ' ' + _summarizeCounts(s.counts, useColor, labels));
130
160
  if (s.task_statuses.length > 0) {
131
161
  lines.push(' ' + _checkboxRow(s.task_statuses, useColor));
132
162
  }
@@ -142,4 +172,5 @@ module.exports = {
142
172
  renderSnapshot,
143
173
  ANSI,
144
174
  STATUS_GLYPHS,
175
+ LABELS,
145
176
  };
@@ -162,6 +162,89 @@ test('DB-8: empty slice (no tasks) renders "no tasks" indicator', () => {
162
162
  }
163
163
  });
164
164
 
165
+ test('DB-L1: renderSnapshot uses German labels when language=de', () => {
166
+ const root = _sandbox();
167
+ try {
168
+ _writeMeta(root, 1, { name: 'Auth', status: 'active' });
169
+ _writeTask(root, 1, 1, 1, 'done', 'Login');
170
+ _writeTask(root, 1, 1, 2, 'in-progress', 'Logout');
171
+ _writeTask(root, 1, 1, 3, 'pending', 'Reset');
172
+ const snap = dashboard.collectSnapshot(root);
173
+ const out = dashboard.renderSnapshot(snap, { color: false, language: 'de' });
174
+ assert.match(out, /1 erledigt/);
175
+ assert.match(out, /1 in Arbeit/);
176
+ assert.match(out, /1 offen/);
177
+ assert.equal(/\bdone\b/.test(out), false, 'must not leak English label');
178
+ assert.equal(/in-progress/.test(out), false, 'must not leak English label');
179
+ } finally {
180
+ fs.rmSync(root, { recursive: true, force: true });
181
+ }
182
+ });
183
+
184
+ test('DB-L2: renderSnapshot uses German "no milestones" line for de', () => {
185
+ const root = _sandbox();
186
+ try {
187
+ const snap = dashboard.collectSnapshot(root);
188
+ const out = dashboard.renderSnapshot(snap, { color: false, language: 'de' });
189
+ assert.match(out, /Noch keine Milestones/);
190
+ assert.equal(/No milestones yet/.test(out), false);
191
+ } finally {
192
+ fs.rmSync(root, { recursive: true, force: true });
193
+ }
194
+ });
195
+
196
+ test('DB-L3: empty slice renders German "keine Tasks" for de', () => {
197
+ const root = _sandbox();
198
+ try {
199
+ _writeMeta(root, 1, { name: 'Empty' });
200
+ fs.mkdirSync(path.join(root, '.nubos-pilot', 'milestones', 'M001', 'slices', 'S001', 'tasks'), { recursive: true });
201
+ const snap = dashboard.collectSnapshot(root);
202
+ const out = dashboard.renderSnapshot(snap, { color: false, language: 'de' });
203
+ assert.match(out, /keine Tasks/);
204
+ assert.equal(/no tasks/.test(out), false);
205
+ } finally {
206
+ fs.rmSync(root, { recursive: true, force: true });
207
+ }
208
+ });
209
+
210
+ test('DB-L4: milestone without slices renders German "keine Slices geplant"', () => {
211
+ const root = _sandbox();
212
+ try {
213
+ _writeMeta(root, 1, { name: 'NoSlices' });
214
+ const snap = dashboard.collectSnapshot(root);
215
+ const out = dashboard.renderSnapshot(snap, { color: false, language: 'de' });
216
+ assert.match(out, /keine Slices geplant/);
217
+ } finally {
218
+ fs.rmSync(root, { recursive: true, force: true });
219
+ }
220
+ });
221
+
222
+ test('DB-L5: unknown language falls back to English labels', () => {
223
+ const root = _sandbox();
224
+ try {
225
+ _writeMeta(root, 1, { name: 'Auth' });
226
+ _writeTask(root, 1, 1, 1, 'done', 'X');
227
+ const snap = dashboard.collectSnapshot(root);
228
+ const out = dashboard.renderSnapshot(snap, { color: false, language: 'fr' });
229
+ assert.match(out, /1 done/);
230
+ } finally {
231
+ fs.rmSync(root, { recursive: true, force: true });
232
+ }
233
+ });
234
+
235
+ test('DB-L6: omitted language defaults to English (lib stays pure)', () => {
236
+ const root = _sandbox();
237
+ try {
238
+ _writeMeta(root, 1, { name: 'Auth' });
239
+ _writeTask(root, 1, 1, 1, 'done', 'X');
240
+ const snap = dashboard.collectSnapshot(root);
241
+ const out = dashboard.renderSnapshot(snap, { color: false });
242
+ assert.match(out, /1 done/);
243
+ } finally {
244
+ fs.rmSync(root, { recursive: true, force: true });
245
+ }
246
+ });
247
+
165
248
  test('DB-9: multiple milestones render in numeric order', () => {
166
249
  const root = _sandbox();
167
250
  try {
@@ -19,6 +19,33 @@ function _listMarkdown(dir) {
19
19
  .sort();
20
20
  }
21
21
 
22
+ function _listSkillDirs(skillsRoot) {
23
+ if (!skillsRoot || !fs.existsSync(skillsRoot)) return [];
24
+ return fs.readdirSync(skillsRoot, { withFileTypes: true })
25
+ .filter((e) => e.isDirectory() && !e.name.startsWith('.'))
26
+ .map((e) => e.name)
27
+ .filter((name) => fs.existsSync(path.join(skillsRoot, name, 'SKILL.md')))
28
+ .sort();
29
+ }
30
+
31
+ function _walkFiles(dir) {
32
+ const out = [];
33
+ const stack = [''];
34
+ while (stack.length) {
35
+ const rel = stack.pop();
36
+ const abs = rel ? path.join(dir, rel) : dir;
37
+ let entries;
38
+ try { entries = fs.readdirSync(abs, { withFileTypes: true }); }
39
+ catch { continue; }
40
+ for (const e of entries) {
41
+ const childRel = rel ? path.join(rel, e.name) : e.name;
42
+ if (e.isDirectory()) stack.push(childRel);
43
+ else if (e.isFile()) out.push(childRel);
44
+ }
45
+ }
46
+ return out.sort();
47
+ }
48
+
22
49
  function _payloadBase(scope, projectRoot) {
23
50
  return scope === 'global' ? os.homedir() : projectRoot;
24
51
  }
@@ -27,10 +54,11 @@ function _toPosix(p) {
27
54
  return p.split(path.sep).join('/');
28
55
  }
29
56
 
30
- function planRuntimeAssets({ selectedRuntimes, scope, projectRoot, workflowsDir, agentsDir }) {
57
+ function planRuntimeAssets({ selectedRuntimes, scope, projectRoot, workflowsDir, agentsDir, skillsDir }) {
31
58
  const base = _payloadBase(scope, projectRoot);
32
59
  const workflows = _listMarkdown(workflowsDir);
33
60
  const agents = _listMarkdown(agentsDir);
61
+ const skills = _listSkillDirs(skillsDir);
34
62
  const plans = [];
35
63
  for (const id of selectedRuntimes || []) {
36
64
  const meta = registryMod.getRuntimeMeta(id);
@@ -60,6 +88,23 @@ function planRuntimeAssets({ selectedRuntimes, scope, projectRoot, workflowsDir,
60
88
  });
61
89
  }
62
90
  }
91
+ if (meta.skillsSubdir && skillsDir) {
92
+ for (const skill of skills) {
93
+ const skillSrcDir = path.join(skillsDir, skill);
94
+ for (const rel of _walkFiles(skillSrcDir)) {
95
+ const sourceFile = path.join(skillSrcDir, rel);
96
+ const targetFile = path.join(configDir, meta.skillsSubdir, skill, rel);
97
+ plans.push({
98
+ runtime: id,
99
+ kind: 'skill',
100
+ skillName: skill,
101
+ sourceFile,
102
+ targetFile,
103
+ manifestKey: _toPosix(path.relative(base, targetFile)),
104
+ });
105
+ }
106
+ }
107
+ }
63
108
  }
64
109
  return plans;
65
110
  }
@@ -105,6 +150,7 @@ function _isAssetKey(key) {
105
150
  if (key.startsWith('.')) {
106
151
  if (key.startsWith('.claude/commands/')) return true;
107
152
  if (key.startsWith('.claude/agents/')) return true;
153
+ if (key.startsWith('.claude/skills/')) return true;
108
154
  for (const meta of registryMod.RUNTIMES) {
109
155
  if (meta.commandsSubdir) {
110
156
  if (key.startsWith(meta.localDir + '/' + meta.commandsSubdir + '/')) return true;
@@ -112,6 +158,9 @@ function _isAssetKey(key) {
112
158
  if (meta.agentsSubdir) {
113
159
  if (key.startsWith(meta.localDir + '/' + meta.agentsSubdir + '/')) return true;
114
160
  }
161
+ if (meta.skillsSubdir) {
162
+ if (key.startsWith(meta.localDir + '/' + meta.skillsSubdir + '/')) return true;
163
+ }
115
164
  }
116
165
  }
117
166
  return false;
@@ -0,0 +1,190 @@
1
+ 'use strict';
2
+
3
+ const test = require('node:test');
4
+ const assert = require('node:assert');
5
+ const fs = require('node:fs');
6
+ const os = require('node:os');
7
+ const path = require('node:path');
8
+
9
+ const runtimeAssets = require('./runtime-assets.cjs');
10
+
11
+ function _mkTmp(prefix) {
12
+ return fs.mkdtempSync(path.join(os.tmpdir(), prefix));
13
+ }
14
+
15
+ function _seedSource(root) {
16
+ const workflowsDir = path.join(root, 'workflows');
17
+ const agentsDir = path.join(root, 'agents');
18
+ const skillsDir = path.join(root, 'skills');
19
+ fs.mkdirSync(workflowsDir, { recursive: true });
20
+ fs.mkdirSync(agentsDir, { recursive: true });
21
+ fs.mkdirSync(path.join(skillsDir, 'np-council'), { recursive: true });
22
+ fs.mkdirSync(path.join(skillsDir, 'np-shadcn', 'rules'), { recursive: true });
23
+ fs.mkdirSync(path.join(skillsDir, 'np-shadcn', 'assets'), { recursive: true });
24
+ fs.mkdirSync(path.join(skillsDir, '.draft'), { recursive: true });
25
+ fs.mkdirSync(path.join(skillsDir, 'np-incomplete'), { recursive: true });
26
+
27
+ fs.writeFileSync(path.join(workflowsDir, 'execute-phase.md'), '# wf');
28
+ fs.writeFileSync(path.join(agentsDir, 'np-executor.md'), '# agent');
29
+ fs.writeFileSync(path.join(skillsDir, 'np-council', 'SKILL.md'), '# council');
30
+ fs.writeFileSync(path.join(skillsDir, 'np-shadcn', 'SKILL.md'), '# shadcn');
31
+ fs.writeFileSync(path.join(skillsDir, 'np-shadcn', 'rules', 'react.md'), '# rules');
32
+ fs.writeFileSync(path.join(skillsDir, 'np-shadcn', 'assets', 'preset.json'), '{}');
33
+ fs.writeFileSync(path.join(skillsDir, '.draft', 'SKILL.md'), '# hidden');
34
+ fs.writeFileSync(path.join(skillsDir, 'np-incomplete', 'README.md'), '# no skill md');
35
+
36
+ return { workflowsDir, agentsDir, skillsDir };
37
+ }
38
+
39
+ test('planRuntimeAssets: discovers skills automatically and skips invalid dirs', () => {
40
+ const root = _mkTmp('np-skills-plan-');
41
+ try {
42
+ const { workflowsDir, agentsDir, skillsDir } = _seedSource(root);
43
+ const projectRoot = path.join(root, 'proj');
44
+ fs.mkdirSync(projectRoot, { recursive: true });
45
+
46
+ const plans = runtimeAssets.planRuntimeAssets({
47
+ selectedRuntimes: ['claude'],
48
+ scope: 'local',
49
+ projectRoot,
50
+ workflowsDir,
51
+ agentsDir,
52
+ skillsDir,
53
+ });
54
+
55
+ const skills = plans.filter((p) => p.kind === 'skill');
56
+ const skillNames = new Set(skills.map((p) => p.skillName));
57
+ assert.deepStrictEqual([...skillNames].sort(), ['np-council', 'np-shadcn'],
58
+ 'only directories with SKILL.md and no leading dot are taken');
59
+
60
+ const shadcnFiles = skills
61
+ .filter((p) => p.skillName === 'np-shadcn')
62
+ .map((p) => p.manifestKey)
63
+ .sort();
64
+ assert.deepStrictEqual(shadcnFiles, [
65
+ '.claude/skills/np-shadcn/SKILL.md',
66
+ '.claude/skills/np-shadcn/assets/preset.json',
67
+ '.claude/skills/np-shadcn/rules/react.md',
68
+ ], 'walk yields nested files with posix manifest keys');
69
+ } finally {
70
+ fs.rmSync(root, { recursive: true, force: true });
71
+ }
72
+ });
73
+
74
+ test('planRuntimeAssets: skips skills for runtimes without skillsSubdir', () => {
75
+ const root = _mkTmp('np-skills-skip-');
76
+ try {
77
+ const { workflowsDir, agentsDir, skillsDir } = _seedSource(root);
78
+ const projectRoot = path.join(root, 'proj');
79
+ fs.mkdirSync(projectRoot, { recursive: true });
80
+
81
+ const plans = runtimeAssets.planRuntimeAssets({
82
+ selectedRuntimes: ['codex', 'gemini', 'cursor'],
83
+ scope: 'local',
84
+ projectRoot,
85
+ workflowsDir,
86
+ agentsDir,
87
+ skillsDir,
88
+ });
89
+
90
+ assert.strictEqual(plans.filter((p) => p.kind === 'skill').length, 0,
91
+ 'no skill plans for runtimes without skillsSubdir');
92
+ } finally {
93
+ fs.rmSync(root, { recursive: true, force: true });
94
+ }
95
+ });
96
+
97
+ test('writeRuntimeAssets: copies nested skill files into runtime config dir', () => {
98
+ const root = _mkTmp('np-skills-write-');
99
+ try {
100
+ const { workflowsDir, agentsDir, skillsDir } = _seedSource(root);
101
+ const projectRoot = path.join(root, 'proj');
102
+ fs.mkdirSync(projectRoot, { recursive: true });
103
+
104
+ const plans = runtimeAssets.planRuntimeAssets({
105
+ selectedRuntimes: ['claude'],
106
+ scope: 'local',
107
+ projectRoot,
108
+ workflowsDir,
109
+ agentsDir,
110
+ skillsDir,
111
+ });
112
+ runtimeAssets.writeRuntimeAssets(plans);
113
+
114
+ assert.ok(fs.existsSync(path.join(projectRoot, '.claude/skills/np-council/SKILL.md')));
115
+ assert.ok(fs.existsSync(path.join(projectRoot, '.claude/skills/np-shadcn/SKILL.md')));
116
+ assert.ok(fs.existsSync(path.join(projectRoot, '.claude/skills/np-shadcn/rules/react.md')));
117
+ assert.ok(fs.existsSync(path.join(projectRoot, '.claude/skills/np-shadcn/assets/preset.json')));
118
+ } finally {
119
+ fs.rmSync(root, { recursive: true, force: true });
120
+ }
121
+ });
122
+
123
+ test('manifestEntriesForPlans: hashes every skill file', () => {
124
+ const root = _mkTmp('np-skills-hash-');
125
+ try {
126
+ const { workflowsDir, agentsDir, skillsDir } = _seedSource(root);
127
+ const projectRoot = path.join(root, 'proj');
128
+ fs.mkdirSync(projectRoot, { recursive: true });
129
+
130
+ const plans = runtimeAssets.planRuntimeAssets({
131
+ selectedRuntimes: ['claude'],
132
+ scope: 'local',
133
+ projectRoot,
134
+ workflowsDir,
135
+ agentsDir,
136
+ skillsDir,
137
+ });
138
+ const entries = runtimeAssets.manifestEntriesForPlans(plans);
139
+ const skillKeys = Object.keys(entries).filter((k) => k.includes('/skills/'));
140
+
141
+ assert.ok(skillKeys.length >= 4, 'each nested skill file gets its own manifest key');
142
+ for (const k of skillKeys) {
143
+ assert.match(entries[k], /^[a-f0-9]{64}$/, k + ' has sha256 hash');
144
+ }
145
+ } finally {
146
+ fs.rmSync(root, { recursive: true, force: true });
147
+ }
148
+ });
149
+
150
+ test('isAssetManifestKey: recognises skill paths for all runtimes that opted in', () => {
151
+ assert.strictEqual(runtimeAssets.isAssetManifestKey('.claude/skills/np-council/SKILL.md'), true);
152
+ assert.strictEqual(runtimeAssets.isAssetManifestKey('.claude/skills/np-shadcn/rules/react.md'), true);
153
+ assert.strictEqual(runtimeAssets.isAssetManifestKey('~/.claude/skills/np-council/SKILL.md'), true);
154
+ assert.strictEqual(runtimeAssets.isAssetManifestKey('.codex/skills/np-council/SKILL.md'), false,
155
+ 'codex did not opt into skills');
156
+ assert.strictEqual(runtimeAssets.isAssetManifestKey('.claude/nubos-pilot/state.json'), false);
157
+ });
158
+
159
+ test('removeStaleAssets: deletes nested skill files and prunes empty dirs', () => {
160
+ const root = _mkTmp('np-skills-stale-');
161
+ try {
162
+ const { workflowsDir, agentsDir, skillsDir } = _seedSource(root);
163
+ const projectRoot = path.join(root, 'proj');
164
+ fs.mkdirSync(projectRoot, { recursive: true });
165
+
166
+ const plans = runtimeAssets.planRuntimeAssets({
167
+ selectedRuntimes: ['claude'],
168
+ scope: 'local',
169
+ projectRoot,
170
+ workflowsDir,
171
+ agentsDir,
172
+ skillsDir,
173
+ });
174
+ runtimeAssets.writeRuntimeAssets(plans);
175
+
176
+ const stale = [
177
+ '.claude/skills/np-shadcn/SKILL.md',
178
+ '.claude/skills/np-shadcn/rules/react.md',
179
+ '.claude/skills/np-shadcn/assets/preset.json',
180
+ ];
181
+ runtimeAssets.removeStaleAssets(stale, 'local', projectRoot);
182
+
183
+ assert.strictEqual(fs.existsSync(path.join(projectRoot, '.claude/skills/np-shadcn')), false,
184
+ 'empty skill directory tree pruned');
185
+ assert.strictEqual(fs.existsSync(path.join(projectRoot, '.claude/skills/np-council/SKILL.md')), true,
186
+ 'untouched skills remain');
187
+ } finally {
188
+ fs.rmSync(root, { recursive: true, force: true });
189
+ }
190
+ });
@@ -15,6 +15,7 @@ const RUNTIMES = [
15
15
  payloadSubdir: 'nubos-pilot',
16
16
  commandsSubdir: 'commands/np',
17
17
  agentsSubdir: 'agents',
18
+ skillsSubdir: 'skills',
18
19
  },
19
20
  {
20
21
  id: 'antigravity',
@@ -1,5 +1,27 @@
1
1
  const readline = require('node:readline');
2
2
  const { NubosPilotError } = require('../core.cjs');
3
+ const { resolveLanguage, normalizeLanguage } = require('../language.cjs');
4
+
5
+ const LABELS = Object.freeze({
6
+ en: {
7
+ choice: 'Choice',
8
+ multiselect_hint: 'Select multiple: 1,2,6 or 1 2 6',
9
+ },
10
+ de: {
11
+ choice: 'Auswahl',
12
+ multiselect_hint: 'Mehrfachauswahl: 1,2,6 oder 1 2 6',
13
+ },
14
+ });
15
+
16
+ function _labelsFor(language) {
17
+ const lang = normalizeLanguage(language || 'en');
18
+ return LABELS[lang] || LABELS.en;
19
+ }
20
+
21
+ function _resolveLangForCwd() {
22
+ try { return resolveLanguage(process.cwd()); }
23
+ catch { return 'en'; }
24
+ }
3
25
 
4
26
  let _readlineImpl = null;
5
27
 
@@ -39,7 +61,7 @@ function _readOneLine() {
39
61
  });
40
62
  }
41
63
 
42
- function _parseAnswer(type, rawLine, options, def) {
64
+ function _parseAnswer(type, rawLine, options, def, language) {
43
65
  const line = (rawLine == null ? '' : String(rawLine)).trim();
44
66
  if (type === 'select') {
45
67
  if (line === '' && def != null) return def;
@@ -74,6 +96,10 @@ function _parseAnswer(type, rawLine, options, def) {
74
96
  if (line === '' && def != null) return def;
75
97
  if (/^y(es)?$/i.test(line)) return true;
76
98
  if (/^n(o)?$/i.test(line)) return false;
99
+ if (normalizeLanguage(language || 'en') === 'de') {
100
+ if (/^j(a)?$/i.test(line)) return true;
101
+ if (/^nein$/i.test(line)) return false;
102
+ }
77
103
  if (def != null) return def;
78
104
  throw new NubosPilotError(
79
105
  'askuser-invalid-response',
@@ -100,15 +126,25 @@ function _stripAnsi(s) {
100
126
  return String(s).replace(/\x1b\[[0-9;]*m/g, '');
101
127
  }
102
128
 
103
- function _defaultDisplay(type, options, def) {
129
+ function _confirmGlyphs(language) {
130
+ return normalizeLanguage(language || 'en') === 'de'
131
+ ? { yes: 'j', no: 'n' }
132
+ : { yes: 'y', no: 'n' };
133
+ }
134
+
135
+ function _defaultDisplay(type, options, def, language) {
104
136
  if (def == null) {
105
- if (type === 'confirm') return '[y/n]';
137
+ if (type === 'confirm') {
138
+ const g = _confirmGlyphs(language);
139
+ return '[' + g.yes + '/' + g.no + ']';
140
+ }
106
141
  return '';
107
142
  }
108
143
  if (type === 'confirm') {
109
- if (def === true) return '[Y/n]';
110
- if (def === false) return '[y/N]';
111
- return '[y/n]';
144
+ const g = _confirmGlyphs(language);
145
+ if (def === true) return '[' + g.yes.toUpperCase() + '/' + g.no + ']';
146
+ if (def === false) return '[' + g.yes + '/' + g.no.toUpperCase() + ']';
147
+ return '[' + g.yes + '/' + g.no + ']';
112
148
  }
113
149
  if (type === 'select') {
114
150
  if (options) {
@@ -127,7 +163,7 @@ function _defaultDisplay(type, options, def) {
127
163
  return '[' + String(def) + ']';
128
164
  }
129
165
 
130
- async function askUserReadline({ type, question, options, def }) {
166
+ async function askUserReadline({ type, question, options, def, language }) {
131
167
  const hasTTY = !!process.stdin.isTTY;
132
168
  if (!hasTTY && !_readlineImpl) {
133
169
  if (def != null) return { value: def, source: 'default' };
@@ -137,6 +173,8 @@ async function askUserReadline({ type, question, options, def }) {
137
173
  { question },
138
174
  );
139
175
  }
176
+ const lang = language || _resolveLangForCwd();
177
+ const labels = _labelsFor(lang);
140
178
  process.stderr.write('\n');
141
179
  process.stderr.write(' ' + ANSI_YELLOW + _stripAnsi(question) + ANSI_RESET + '\n');
142
180
  process.stderr.write('\n');
@@ -150,14 +188,14 @@ async function askUserReadline({ type, question, options, def }) {
150
188
  }
151
189
  process.stderr.write('\n');
152
190
  if (type === 'multiselect') {
153
- process.stderr.write(' Select multiple: 1,2,6 or 1 2 6\n');
191
+ process.stderr.write(' ' + labels.multiselect_hint + '\n');
154
192
  process.stderr.write('\n');
155
193
  }
156
194
  }
157
- const marker = _defaultDisplay(type, options, def);
158
- process.stderr.write(' Choice' + (marker ? ' ' + marker : '') + ': ');
195
+ const marker = _defaultDisplay(type, options, def, lang);
196
+ process.stderr.write(' ' + labels.choice + (marker ? ' ' + marker : '') + ': ');
159
197
  const line = await _readOneLine();
160
- return { value: _parseAnswer(type, line, options, def), source: 'readline' };
198
+ return { value: _parseAnswer(type, line, options, def, lang), source: 'readline' };
161
199
  }
162
200
 
163
201
  module.exports = { askUserReadline, _readOneLine, _parseAnswer, _setReadlineImplForTests, _hasReadlineImplForTests };