@nan0web/ui 1.11.0 → 1.12.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nan0web/ui",
3
- "version": "1.11.0",
3
+ "version": "1.12.1",
4
4
  "description": "NaN•Web UI. One application logic (algorithm) and many UI.",
5
5
  "main": "src/index.js",
6
6
  "types": "types/index.d.ts",
@@ -24,6 +24,10 @@
24
24
  "import": "./src/core/index.js",
25
25
  "types": "./types/core/index.d.ts"
26
26
  },
27
+ "./src/core/Intent.js": {
28
+ "import": "./src/core/Intent.js",
29
+ "types": "./types/core/Intent.d.ts"
30
+ },
27
31
  "./domain": {
28
32
  "import": "./src/domain/index.js",
29
33
  "types": "./types/domain/index.d.ts"
@@ -70,23 +74,23 @@
70
74
  "lit": "^3.3.2",
71
75
  "vite": "^6.4.1",
72
76
  "vitest": "^3.2.4",
73
- "@nan0web/db-fs": "1.2.1",
77
+ "@nan0web/db-fs": "1.2.2",
74
78
  "@nan0web/event": "1.0.1",
75
79
  "@nan0web/i18n": "1.5.0",
76
- "@nan0web/inspect": "1.0.0",
77
- "@nan0web/ui-cli": "2.12.3",
78
- "@nan0web/test": "1.1.4",
79
80
  "@nan0web/icons": "1.1.0",
80
- "@nan0web/release": "1.0.3",
81
+ "@nan0web/inspect": "1.0.0",
82
+ "@nan0web/ui-cli": "2.13.1",
81
83
  "@nan0web/nan0web.app": "0.1.0",
82
- "@nan0web/ui-lit": "1.1.0"
84
+ "@nan0web/ui-lit": "1.1.0",
85
+ "@nan0web/test": "1.1.4",
86
+ "@nan0web/release": "1.0.3"
83
87
  },
84
88
  "dependencies": {
85
89
  "string-width": "^7.2.0",
86
90
  "@nan0web/co": "2.0.1",
91
+ "@nan0web/core": "1.1.3",
87
92
  "@nan0web/log": "1.1.1",
88
- "@nan0web/types": "1.7.2",
89
- "@nan0web/core": "1.1.3"
93
+ "@nan0web/types": "1.7.3"
90
94
  },
91
95
  "scripts": {
92
96
  "prebuild": "rm -rf types/",
@@ -15,7 +15,7 @@ export default class CoreApp {
15
15
  /** @type {Map<string, CommandFn>} Registered command handlers */
16
16
  commands
17
17
 
18
- /** @type {object} App state */
18
+ /** @type {Record<string, any>} App state */
19
19
  state
20
20
 
21
21
  /** @type {Message} Starting command parsed from argv */
package/src/View/View.js CHANGED
@@ -28,7 +28,7 @@ export default class View {
28
28
  frame
29
29
  /** @type {Locale} */
30
30
  locale
31
- /** @type {Map} */
31
+ /** @type {Map<string, string>} */
32
32
  vocab
33
33
  /** @type {number[]} */
34
34
  windowSize
@@ -43,7 +43,7 @@ export default class View {
43
43
  * @param {number} [input.startedAt]
44
44
  * @param {Frame} [input.frame]
45
45
  * @param {Locale} [input.locale]
46
- * @param {Map} [input.vocab]
46
+ * @param {Map<string, string>} [input.vocab]
47
47
  * @param {number[]} [input.windowSize]
48
48
  * @param {Map<string, ComponentFn>} [input.components]
49
49
  * @param {string} [input.renderMethod]
@@ -66,7 +66,7 @@ export default class View {
66
66
  this.frame = frame
67
67
  this.locale = locale
68
68
  this.vocab = vocab
69
- this.windowSize = null === windowSize ? this.stdout.getWindowSize() : windowSize
69
+ this.windowSize = /** @type {number[]} */ (null === windowSize ? this.stdout.getWindowSize() : windowSize)
70
70
  this.components = components
71
71
  this.renderMethod = renderMethod
72
72
  if (!empty(frame)) {
@@ -85,6 +85,10 @@ export default class View {
85
85
  getWindowSize() {
86
86
  return equal(this.windowSize, [0, 0]) ? this.stdout.getWindowSize() : this.windowSize
87
87
  }
88
+ /**
89
+ * @param {number} width
90
+ * @param {number} height
91
+ */
88
92
  setWindowSize(width, height) {
89
93
  this.windowSize = [width, height]
90
94
  }
@@ -181,6 +185,7 @@ export default class View {
181
185
  }
182
186
  }
183
187
 
188
+ /** @param {any} value */
184
189
  t(value) {
185
190
  if (typeOf(Array)(value)) {
186
191
  value = value.map((row) => {
@@ -196,16 +201,22 @@ export default class View {
196
201
  return this.vocab.has(value) ? this.vocab.get(value) : value
197
202
  }
198
203
 
204
+ /** @param {any[]} args */
199
205
  debug(...args) {
206
+ // @ts-ignore
200
207
  return this.render(1)([StdOut.STYLES.dim, 'Debug: ', args.join(' '), Frame.EOL, StdOut.RESET])
201
208
  }
202
209
 
210
+ /** @param {any[]} args */
203
211
  info(...args) {
212
+ // @ts-ignore
204
213
  return this.render(1)([StdOut.COLORS.green, 'Info : ', args.join(' '), Frame.EOL, StdOut.RESET])
205
214
  }
206
215
 
216
+ /** @param {any[]} args */
207
217
  warn(...args) {
208
218
  return this.render(1)([
219
+ // @ts-ignore
209
220
  StdOut.COLORS.yellow,
210
221
  'Warn : ',
211
222
  args.join(' '),
@@ -214,8 +225,10 @@ export default class View {
214
225
  ])
215
226
  }
216
227
 
228
+ /** @param {any[]} args */
217
229
  error(...args) {
218
230
  return this.render(1)([
231
+ // @ts-ignore
219
232
  StdOut.COLORS.red,
220
233
  StdOut.STYLES.bold,
221
234
  'Error: ',
@@ -2,6 +2,18 @@ import Event from '@nan0web/event/oop'
2
2
  import CancelError from './Error/CancelError.js'
3
3
  import UiMessage from './Message/Message.js'
4
4
 
5
+ /**
6
+ * @typedef {Object} AskOptions
7
+ * @property {boolean} [silent] - Suppress logs or output.
8
+ * @property {string} [title] - Custom title for the prompt.
9
+ * @property {string} [hint] - Presentation hint (e.g., 'password', 'tree', 'markdown').
10
+ * @property {any} [default] - Default value if no input is provided.
11
+ * @property {Array<string|Object>} [options] - Array of options for select inputs.
12
+ * @property {Record<string, any>} [UI] - Localization dictionary/overrides.
13
+ * @property {string} [component] - Target specific component override.
14
+ */
15
+ /** @typedef {import('./index.js').AskResponse} AskResponse */
16
+
5
17
  /**
6
18
  * Abstract input adapter for UI implementations.
7
19
  *
@@ -41,19 +53,22 @@ export class InputAdapter extends Event {
41
53
  return true
42
54
  }
43
55
 
56
+
57
+
44
58
  /**
45
59
  * Helper to ask a question.
46
- * @param {string} question - Question to ask.
47
- * @returns {Promise<string>}
60
+ * @param {string|import('./Message/Message.js').default|any} question - Question to ask, Form instance, or AskIntent.
61
+ * @param {AskOptions} [options] - Additional options.
62
+ * @returns {Promise<AskResponse>}
48
63
  */
49
- async ask(question) {
64
+ async ask(question, options) {
50
65
  throw new Error('ask() method must be implemented in subclass')
51
66
  }
52
67
 
53
68
  /**
54
69
  * Generic selection prompt.
55
70
  * @param {Object} config - Selection configuration.
56
- * @returns {Promise<{ index: number, value: string | null }>}
71
+ * @returns {Promise<{ index?: number, value: string | null }>}
57
72
  */
58
73
  async select(config) {
59
74
  throw new Error('select() method must be implemented in subclass')
@@ -41,6 +41,7 @@ import { IntentErrorModel } from './IntentErrorModel.js'
41
41
  * @property {number} [total] - Absolute total (if value is absolute).
42
42
  * @property {string} [id] - Progress ID for tracking by Adapter to calculate speed/eta.
43
43
  * @property {string} message - Status message from Model (i18n static field value).
44
+ * @property {ProgressOptions} [options] - Additional options for progress intent.
44
45
  */
45
46
 
46
47
  /**
@@ -71,6 +72,7 @@ import { IntentErrorModel } from './IntentErrorModel.js'
71
72
  * @typedef {Object} ResultIntent
72
73
  * @property {'result'} type
73
74
  * @property {*} data - The raw result data (JSON-serializable).
75
+ * @property {boolean} [raw] - If true, Adapter MUST output data raw (no UI decorations).
74
76
  */
75
77
 
76
78
  /**
@@ -118,7 +120,11 @@ import { IntentErrorModel } from './IntentErrorModel.js'
118
120
  * The value MUST conform to the type described in the requested FieldSchema.
119
121
  * @typedef {Object} AskResponse
120
122
  * @property {*} value - The value matching schema.type (collected from user / LLM / test fixture).
121
- * @property {boolean} [cancelled] - Whether the user cancelled this interaction (e.g. pressed ESC).
123
+ * @property {boolean} cancelled - Whether the user cancelled this interaction (e.g. pressed ESC).
124
+ * @property {string} [action] - The action identifier (e.g., 'submit', 'exit', 'back').
125
+ * @property {any} [body] - Additional payload or form data.
126
+ * @property {any} [form] - The form model instance (if applicable).
127
+ * @property {number} [index] - The selected index for lists/tables.
122
128
  */
123
129
 
124
130
  /**
@@ -244,26 +250,50 @@ export function ask(field, schema) {
244
250
  return { type: 'ask', field, schema }
245
251
  }
246
252
 
253
+ /**
254
+ * @typedef {Object} ProgressOptions
255
+ * @property {number} [total] - Absolute total steps.
256
+ * @property {string} [id] - Progress tracking ID.
257
+ * @property {number} [width] - Width of the progress bar in terminal characters.
258
+ * @property {number} [fps] - Frames per second update rate limit.
259
+ * @property {string} [format] - Custom format string (e.g. '{time} {bar} {percent} {title}').
260
+ * @property {number} [columns] - Number of columns (terminal width).
261
+ * @property {boolean} [forceOneLine] - Prevent wrapping and truncate instead.
262
+ * @property {boolean|'success'|'error'} [stop] - Set to true to stop, or 'success'/'error' to stop with a status icon (for spinners).
263
+ */
264
+
247
265
  /**
248
266
  * Create a progress intent.
249
267
  * @param {string} message - Status message from Model (i18n static field value).
250
268
  * @param {number} [value=0] - Progress value (current step or percentage).
251
- * @param {number|string} [totalOrId] - Absolute total steps (number) OR progress tracking ID (string).
269
+ * @param {ProgressOptions|number|string} [optionsOrTotalOrId] - Options object, or absolute total steps (number), or progress tracking ID (string).
252
270
  * @param {string} [id='default'] - Progress ID (if total is provided).
253
271
  * @returns {ProgressIntent}
254
272
  */
255
- export function progress(message, value = 0, totalOrId, id) {
256
- let total = undefined
257
- let progressId = 'default'
258
-
259
- if (typeof totalOrId === 'number') {
260
- total = totalOrId
261
- if (typeof id === 'string') progressId = id
262
- } else if (typeof totalOrId === 'string') {
263
- progressId = totalOrId
273
+ export function progress(message, value = 0, optionsOrTotalOrId, id) {
274
+ /** @type {ProgressOptions} */
275
+ let options = {}
276
+
277
+ if (typeof optionsOrTotalOrId === 'object' && optionsOrTotalOrId !== null) {
278
+ options = optionsOrTotalOrId
279
+ } else {
280
+ // legacy support
281
+ if (typeof optionsOrTotalOrId === 'number') {
282
+ options.total = optionsOrTotalOrId
283
+ if (typeof id === 'string') options.id = id
284
+ } else if (typeof optionsOrTotalOrId === 'string') {
285
+ options.id = optionsOrTotalOrId
286
+ }
264
287
  }
265
288
 
266
- return { type: 'progress', message, value, total, id: progressId }
289
+ return {
290
+ type: 'progress',
291
+ message,
292
+ value,
293
+ total: options.total,
294
+ id: options.id || 'default',
295
+ options
296
+ }
267
297
  }
268
298
 
269
299
  export function log(level, message, data = {}) {
@@ -283,10 +313,11 @@ export function render(component, props = {}) {
283
313
  /**
284
314
  * Create a result intent.
285
315
  * @param {*} data - The raw result data.
316
+ * @param {boolean} [raw=false] - If true, result is printed raw.
286
317
  * @returns {ResultIntent}
287
318
  */
288
- export function result(data) {
289
- return { type: 'result', data }
319
+ export function result(data, raw = false) {
320
+ return { type: 'result', data, raw }
290
321
  }
291
322
 
292
323
  /**
package/src/core/index.js CHANGED
@@ -42,6 +42,9 @@ export { default as Flow } from './Flow.js'
42
42
 
43
43
  // OLMUI Generator Engine — Intent-based Model→Adapter contract
44
44
  export { validateIntent, ask, progress, log, render, result, INTENT_TYPES, isModelSchema } from './Intent.js'
45
+ /** @typedef {import('./Intent.js').Intent} Intent */
46
+ /** @typedef {import('./Intent.js').AskResponse} AskResponse */
47
+ /** @typedef {import('./InputAdapter.js').AskOptions} AskOptions */
45
48
  export { IntentErrorModel } from './IntentErrorModel.js'
46
49
  export { runGenerator } from './GeneratorRunner.js'
47
50
 
@@ -37,7 +37,7 @@ import { Model } from '@nan0web/types'
37
37
  * @property {string|ContentData[]} [fieldset]
38
38
  * @property {string|ContentData[]} [figcaption]
39
39
  * @property {string|ContentData[]} [figure]
40
- * @property {string|ContentData[]} [footer]
40
+ * @property {any} [footer]
41
41
  * @property {string|ContentData[]} [form]
42
42
  * @property {string|ContentData[]} [h1]
43
43
  * @property {string|ContentData[]} [h2]
@@ -46,7 +46,7 @@ import { Model } from '@nan0web/types'
46
46
  * @property {string|ContentData[]} [h5]
47
47
  * @property {string|ContentData[]} [h6]
48
48
  * @property {string|ContentData[]} [head]
49
- * @property {string|ContentData[]} [header]
49
+ * @property {any} [header]
50
50
  * @property {string|ContentData[]} [hgroup]
51
51
  * @property {boolean|object} [hr]
52
52
  * @property {string|ContentData[]} [html]
@@ -139,6 +139,8 @@ import { Model } from '@nan0web/types'
139
139
  * @property {import('./components/FAQModel.js').FAQModel} [faq]
140
140
  * @property {import('./components/FeatureGridModel.js').FeatureGridModel} [featureGrid]
141
141
  * @property {import('./components/GalleryModel.js').GalleryModel} [gallery]
142
+ * @property {import('./HeaderModel.js').HeaderModel} [header]
143
+ * @property {import('./FooterModel.js').FooterModel} [footer]
142
144
  * @property {import('./components/InputModel.js').InputModel} [input]
143
145
  * @property {import('./components/MarkdownModel.js').MarkdownModel} [markdown]
144
146
  * @property {import('./components/PriceModel.js').PriceModel} [price]
@@ -1,9 +1,14 @@
1
1
  import { Model } from '@nan0web/types'
2
2
  import { Content } from './Content.js'
3
+ import Navigation from './Navigation.js'
4
+ import { Language } from '@nan0web/i18n'
3
5
 
4
6
  export class Document extends Model {
5
7
  static title = { type: 'string', help: 'Title' }
6
8
  static content = { type: 'array', model: Content, help: 'Content' }
9
+ static $content = { type: 'array', model: Content, help: 'Layout configuration' }
10
+ static nav = { type: 'any', model: Navigation, help: 'Navigation config or reference' }
11
+ static langs = { type: 'array', model: Language, help: 'Supported languages array' }
7
12
  /**
8
13
  *
9
14
  * @param {Partial<Document>} [data]
@@ -13,5 +18,8 @@ export class Document extends Model {
13
18
  super(data, options)
14
19
  /** @type {string} Title */ this.title
15
20
  /** @type {Array<Content>} Content */ this.content
21
+ /** @type {Array<Content>} Layout configuration */ this.$content
22
+ /** @type {Navigation|string|Array<Navigation>} Navigation config */ this.nav
23
+ /** @type {Array<Language>} Supported languages */ this.langs
16
24
  }
17
25
  }
@@ -16,7 +16,7 @@ export class ModelAsApp extends Model {
16
16
  }
17
17
  /**
18
18
  * @param {Partial<ModelAsApp> | Record<string, any>} [data={}]
19
- * @param {ModelAsAppOptions} [options={}]
19
+ * @param {Partial<ModelAsAppOptions>} [options={}]
20
20
  */
21
21
  constructor(data = {}, options = {}) {
22
22
  super(data, options)
@@ -0,0 +1,61 @@
1
+ import { ModelAsApp } from '../ModelAsApp.js'
2
+ import { show, result, render } from '../../core/Intent.js'
3
+
4
+ export default class ConfigApp extends ModelAsApp {
5
+ static name = 'config'
6
+ static alias = 'cfg'
7
+
8
+ static resource = {
9
+ type: 'string',
10
+ help: 'Resource to configure (e.g. agents)',
11
+ positional: true,
12
+ default: 'agents',
13
+ }
14
+
15
+ static action = {
16
+ type: 'string',
17
+ help: 'Action to perform (e.g. list)',
18
+ positional: true,
19
+ default: 'list',
20
+ }
21
+
22
+ constructor(data = {}, options = {}) {
23
+ super(data, options)
24
+ this.resource = data.resource || 'agents'
25
+ this.action = data.action || 'list'
26
+ }
27
+
28
+ async *run() {
29
+ if (this.resource === 'agents') {
30
+ if (this.action === 'list') {
31
+ const DBFS = (await import('@nan0web/db-fs')).default
32
+ const db = new DBFS({ root: process.cwd() })
33
+ const config = await db.loadDocument('nan0web.nan0', {}).catch(() => ({}))
34
+
35
+ if (!config || !config.agents || !Array.isArray(config.agents)) {
36
+ yield show('No agents configured in nan0web.nan0')
37
+ return result({ success: true })
38
+ }
39
+
40
+ const tableData = config.agents.map((a) => ({
41
+ ID: a.id,
42
+ Description: a.description,
43
+ Workflows: a.workflows ? a.workflows.length : 0,
44
+ Inspectors: a.inspectors ? a.inspectors.length : 0,
45
+ }))
46
+
47
+ yield render('Table', {
48
+ data: tableData,
49
+ columns: ['ID', 'Description', 'Workflows', 'Inspectors'],
50
+ interactive: false
51
+ })
52
+ return result({ success: true })
53
+ }
54
+ yield show(`Unknown action for agents: ${this.action}`, 'error')
55
+ return result({ success: false })
56
+ }
57
+
58
+ yield show(`Unknown config resource: ${this.resource}`, 'error')
59
+ return result({ success: false })
60
+ }
61
+ }
@@ -64,16 +64,14 @@ export class SnapshotAuditor extends AuditorModel {
64
64
  /** @type {number} Minimum filename length */
65
65
  static MIN_FILENAME_LENGTH = 3
66
66
 
67
- /** @type {import('../../index.js').ModelAsAppOptions} */
68
- _
69
-
70
67
  /**
71
68
  * @param {Partial<SnapshotAuditor> | Record<string, any>} [data={}]
72
69
  * @param {Partial<import('@nan0web/types').ModelOptions>} [options={}]
73
70
  */
74
71
  constructor(data = {}, options = {}) {
75
72
  super(data, options)
76
- this._ = options
73
+ /** @type {import('@nan0web/types').ModelOptions} */
74
+ this.options = options
77
75
  /** @type {string} Target directory to audit */ this.dir
78
76
  /** @type {string} Directory to scan for dictionaries */ this.data
79
77
  }
@@ -160,7 +158,7 @@ export class SnapshotAuditor extends AuditorModel {
160
158
  * @returns {AsyncGenerator<import('@nan0web/ui').Intent, any, any>}
161
159
  */
162
160
  async *run() {
163
- const { t } = this._
161
+ const { t } = this.options
164
162
  const snapshotsDir = this.dir || '.'
165
163
 
166
164
  yield show(t(SnapshotAuditor.UI.starting, { dir: snapshotsDir }))
@@ -168,7 +166,7 @@ export class SnapshotAuditor extends AuditorModel {
168
166
  const files = []
169
167
 
170
168
  /** @type {import('@nan0web/db').DB} */
171
- let fsDb = this._.db
169
+ let fsDb = this.options.db
172
170
  if (fsDb && fsDb.mounts && fsDb.mounts.has('')) {
173
171
  fsDb = /** @type {import('@nan0web/db').DB} */ (fsDb.mounts.get(''))
174
172
  }
@@ -2,13 +2,14 @@ import { ModelAsApp } from '../ModelAsApp.js'
2
2
  import { resolvePositionalArgs } from '@nan0web/ui-cli'
3
3
  import SnapshotAuditor from './SnapshotAuditor.js'
4
4
  import GalleryCommand from './GalleryCommand.js'
5
+ import ConfigApp from './ConfigApp.js'
5
6
  import { show, result } from '../../core/Intent.js'
6
7
 
7
8
  export class UIApp extends ModelAsApp {
8
9
  static command = {
9
10
  type: 'string',
10
11
  help: 'Command to run (e.g. gallery)',
11
- options: [GalleryCommand, SnapshotAuditor],
12
+ options: [GalleryCommand, SnapshotAuditor, ConfigApp],
12
13
  default: GalleryCommand.alias || GalleryCommand.name,
13
14
  positional: true,
14
15
  }
package/src/index.js CHANGED
@@ -33,6 +33,7 @@ export { default as UiAdapter } from './core/UiAdapter.js'
33
33
  /** @typedef {import('./core/Intent.js').IntentResponse} IntentResponse */
34
34
  /** @typedef {import('./core/Intent.js').AskIntent} AskIntent */
35
35
  /** @typedef {import('./core/Intent.js').ProgressIntent} ProgressIntent */
36
+ /** @typedef {import('./core/Intent.js').ProgressOptions} ProgressOptions */
36
37
  /** @typedef {import('./core/Intent.js').LogIntent} LogIntent */
37
38
  /** @typedef {import('./core/Intent.js').ShowIntent} ShowIntent */
38
39
  /** @typedef {import('./core/Intent.js').RenderIntent} RenderIntent */
@@ -41,6 +42,7 @@ export { default as UiAdapter } from './core/UiAdapter.js'
41
42
  /** @typedef {import('./core/Intent.js').AskResponse} AskResponse */
42
43
  /** @typedef {import('./core/Intent.js').AbortResponse} AbortResponse */
43
44
  /** @typedef {import('./core/Intent.js').ShowData} ShowData */
45
+ /** @typedef {import('./core/InputAdapter.js').AskOptions} AskOptions */
44
46
  export * from './core/Intent.js'
45
47
 
46
48
  export { IntentErrorModel } from './core/IntentErrorModel.js'
@@ -34,7 +34,7 @@ export default class ScenarioAdapter extends InputAdapter {
34
34
  return { value: match.value, cancelled: !!match.cancelled }
35
35
  }
36
36
  // If no specific match, try to use the first available answer or default
37
- return { value: null }
37
+ return { value: null, cancelled: false }
38
38
  }
39
39
 
40
40
  /** @param {import('../core/Intent.js').ProgressIntent} intent */
@@ -27,7 +27,7 @@ export class LogicInspector {
27
27
  const value = resolvedInputs[inputIdx++]
28
28
  const entry = { type: 'ask', field: i.field, schema: i.schema, input: value }
29
29
  intents.push(entry)
30
- return { value }
30
+ return { value, cancelled: false }
31
31
  },
32
32
  /** @param {import('../core/Intent.js').ShowIntent} i */
33
33
  show: async (i) => {
@@ -43,8 +43,7 @@ export class SpecAdapter {
43
43
  async ask(intent) {
44
44
  const step = this.#popExpected('ask')
45
45
  this.assert.equal(step.ask, intent.field, `Field mismatch on ask. Expected '${step.ask}', got '${intent.field}'`)
46
-
47
- return { value: step.$value }
46
+ return { value: step.$value, cancelled: false }
48
47
  }
49
48
 
50
49
  /**
@@ -20,8 +20,8 @@ export default class CoreApp {
20
20
  name: string;
21
21
  /** @type {Map<string, CommandFn>} Registered command handlers */
22
22
  commands: Map<string, CommandFn>;
23
- /** @type {object} App state */
24
- state: object;
23
+ /** @type {Record<string, any>} App state */
24
+ state: Record<string, any>;
25
25
  /** @type {Message} Starting command parsed from argv */
26
26
  startCommand: Message;
27
27
  /**
@@ -22,7 +22,7 @@ export default class View {
22
22
  * @param {number} [input.startedAt]
23
23
  * @param {Frame} [input.frame]
24
24
  * @param {Locale} [input.locale]
25
- * @param {Map} [input.vocab]
25
+ * @param {Map<string, string>} [input.vocab]
26
26
  * @param {number[]} [input.windowSize]
27
27
  * @param {Map<string, ComponentFn>} [input.components]
28
28
  * @param {string} [input.renderMethod]
@@ -33,7 +33,7 @@ export default class View {
33
33
  startedAt?: number | undefined;
34
34
  frame?: Frame | undefined;
35
35
  locale?: Locale | undefined;
36
- vocab?: Map<any, any> | undefined;
36
+ vocab?: Map<string, string> | undefined;
37
37
  windowSize?: number[] | undefined;
38
38
  components?: Map<string, ComponentFn> | undefined;
39
39
  renderMethod?: string | undefined;
@@ -48,8 +48,8 @@ export default class View {
48
48
  frame: Frame;
49
49
  /** @type {Locale} */
50
50
  locale: Locale;
51
- /** @type {Map} */
52
- vocab: Map<any, any>;
51
+ /** @type {Map<string, string>} */
52
+ vocab: Map<string, string>;
53
53
  /** @type {number[]} */
54
54
  windowSize: number[];
55
55
  /** @type {Map<string, ComponentFn>} */
@@ -60,7 +60,11 @@ export default class View {
60
60
  get RenderMethod(): typeof FrameRenderMethod;
61
61
  get RenderOptions(): typeof RenderOptions;
62
62
  getWindowSize(): number[];
63
- setWindowSize(width: any, height: any): void;
63
+ /**
64
+ * @param {number} width
65
+ * @param {number} height
66
+ */
67
+ setWindowSize(width: number, height: number): void;
64
68
  startTimer(): void;
65
69
  spent(checkpoint?: number): number;
66
70
  /**
@@ -71,10 +75,15 @@ export default class View {
71
75
  render(shouldRender?: boolean | number | Function | ComponentFn, options?: RenderOptions): (value: Frame | string | string[], ...args: any) => Frame;
72
76
  clear(shouldRender?: number): Frame;
73
77
  progress(shouldRender?: boolean): (value: any) => Frame;
78
+ /** @param {any} value */
74
79
  t(value: any): any;
80
+ /** @param {any[]} args */
75
81
  debug(...args: any[]): Frame;
82
+ /** @param {any[]} args */
76
83
  info(...args: any[]): Frame;
84
+ /** @param {any[]} args */
77
85
  warn(...args: any[]): Frame;
86
+ /** @param {any[]} args */
78
87
  error(...args: any[]): Frame;
79
88
  /**
80
89
  * @param {string} name
@@ -1,3 +1,14 @@
1
+ /**
2
+ * @typedef {Object} AskOptions
3
+ * @property {boolean} [silent] - Suppress logs or output.
4
+ * @property {string} [title] - Custom title for the prompt.
5
+ * @property {string} [hint] - Presentation hint (e.g., 'password', 'tree', 'markdown').
6
+ * @property {any} [default] - Default value if no input is provided.
7
+ * @property {Array<string|Object>} [options] - Array of options for select inputs.
8
+ * @property {Record<string, any>} [UI] - Localization dictionary/overrides.
9
+ * @property {string} [component] - Target specific component override.
10
+ */
11
+ /** @typedef {import('./index.js').AskResponse} AskResponse */
1
12
  /**
2
13
  * Abstract input adapter for UI implementations.
3
14
  *
@@ -28,20 +39,52 @@ export class InputAdapter extends Event {
28
39
  isReady(): boolean;
29
40
  /**
30
41
  * Helper to ask a question.
31
- * @param {string} question - Question to ask.
32
- * @returns {Promise<string>}
42
+ * @param {string|import('./Message/Message.js').default|any} question - Question to ask, Form instance, or AskIntent.
43
+ * @param {AskOptions} [options] - Additional options.
44
+ * @returns {Promise<AskResponse>}
33
45
  */
34
- ask(question: string): Promise<string>;
46
+ ask(question: string | import("./Message/Message.js").default | any, options?: AskOptions): Promise<AskResponse>;
35
47
  /**
36
48
  * Generic selection prompt.
37
49
  * @param {Object} config - Selection configuration.
38
- * @returns {Promise<{ index: number, value: string | null }>}
50
+ * @returns {Promise<{ index?: number, value: string | null }>}
39
51
  */
40
52
  select(config: any): Promise<{
41
- index: number;
53
+ index?: number;
42
54
  value: string | null;
43
55
  }>;
44
56
  }
45
57
  export default InputAdapter;
58
+ export type AskOptions = {
59
+ /**
60
+ * - Suppress logs or output.
61
+ */
62
+ silent?: boolean | undefined;
63
+ /**
64
+ * - Custom title for the prompt.
65
+ */
66
+ title?: string | undefined;
67
+ /**
68
+ * - Presentation hint (e.g., 'password', 'tree', 'markdown').
69
+ */
70
+ hint?: string | undefined;
71
+ /**
72
+ * - Default value if no input is provided.
73
+ */
74
+ default?: any;
75
+ /**
76
+ * - Array of options for select inputs.
77
+ */
78
+ options?: any[] | undefined;
79
+ /**
80
+ * - Localization dictionary/overrides.
81
+ */
82
+ UI?: Record<string, any> | undefined;
83
+ /**
84
+ * - Target specific component override.
85
+ */
86
+ component?: string | undefined;
87
+ };
88
+ export type AskResponse = import("./index.js").AskResponse;
46
89
  import Event from '@nan0web/event/oop';
47
90
  import CancelError from './Error/CancelError.js';
@@ -24,15 +24,26 @@ export function validateIntent(intent: any): intent is Intent;
24
24
  * @returns {AskIntent}
25
25
  */
26
26
  export function ask(field: string, schema: object | Function): AskIntent;
27
+ /**
28
+ * @typedef {Object} ProgressOptions
29
+ * @property {number} [total] - Absolute total steps.
30
+ * @property {string} [id] - Progress tracking ID.
31
+ * @property {number} [width] - Width of the progress bar in terminal characters.
32
+ * @property {number} [fps] - Frames per second update rate limit.
33
+ * @property {string} [format] - Custom format string (e.g. '{time} {bar} {percent} {title}').
34
+ * @property {number} [columns] - Number of columns (terminal width).
35
+ * @property {boolean} [forceOneLine] - Prevent wrapping and truncate instead.
36
+ * @property {boolean|'success'|'error'} [stop] - Set to true to stop, or 'success'/'error' to stop with a status icon (for spinners).
37
+ */
27
38
  /**
28
39
  * Create a progress intent.
29
40
  * @param {string} message - Status message from Model (i18n static field value).
30
41
  * @param {number} [value=0] - Progress value (current step or percentage).
31
- * @param {number|string} [totalOrId] - Absolute total steps (number) OR progress tracking ID (string).
42
+ * @param {ProgressOptions|number|string} [optionsOrTotalOrId] - Options object, or absolute total steps (number), or progress tracking ID (string).
32
43
  * @param {string} [id='default'] - Progress ID (if total is provided).
33
44
  * @returns {ProgressIntent}
34
45
  */
35
- export function progress(message: string, value?: number, totalOrId?: number | string, id?: string): ProgressIntent;
46
+ export function progress(message: string, value?: number, optionsOrTotalOrId?: ProgressOptions | number | string, id?: string): ProgressIntent;
36
47
  export function log(level: any, message: any, data?: {}): {
37
48
  type: string;
38
49
  level: any;
@@ -48,9 +59,10 @@ export function render(component: string, props?: object): RenderIntent;
48
59
  /**
49
60
  * Create a result intent.
50
61
  * @param {*} data - The raw result data.
62
+ * @param {boolean} [raw=false] - If true, result is printed raw.
51
63
  * @returns {ResultIntent}
52
64
  */
53
- export function result(data: any): ResultIntent;
65
+ export function result(data: any, raw?: boolean): ResultIntent;
54
66
  /**
55
67
  * @typedef {Object} ShowData
56
68
  * @property {any} [component]
@@ -98,6 +110,7 @@ export function agent(task: string, context?: AgentContext): AgentIntent;
98
110
  * @property {number} [total] - Absolute total (if value is absolute).
99
111
  * @property {string} [id] - Progress ID for tracking by Adapter to calculate speed/eta.
100
112
  * @property {string} message - Status message from Model (i18n static field value).
113
+ * @property {ProgressOptions} [options] - Additional options for progress intent.
101
114
  */
102
115
  /**
103
116
  * @typedef {'info' | 'warn' | 'error' | 'success'} ShowLevel
@@ -123,6 +136,7 @@ export function agent(task: string, context?: AgentContext): AgentIntent;
123
136
  * @typedef {Object} ResultIntent
124
137
  * @property {'result'} type
125
138
  * @property {*} data - The raw result data (JSON-serializable).
139
+ * @property {boolean} [raw] - If true, Adapter MUST output data raw (no UI decorations).
126
140
  */
127
141
  /**
128
142
  * Model requests rendering of a pure UI component (Header, Footer, Static Map).
@@ -163,7 +177,11 @@ export function agent(task: string, context?: AgentContext): AgentIntent;
163
177
  * The value MUST conform to the type described in the requested FieldSchema.
164
178
  * @typedef {Object} AskResponse
165
179
  * @property {*} value - The value matching schema.type (collected from user / LLM / test fixture).
166
- * @property {boolean} [cancelled] - Whether the user cancelled this interaction (e.g. pressed ESC).
180
+ * @property {boolean} cancelled - Whether the user cancelled this interaction (e.g. pressed ESC).
181
+ * @property {string} [action] - The action identifier (e.g., 'submit', 'exit', 'back').
182
+ * @property {any} [body] - Additional payload or form data.
183
+ * @property {any} [form] - The form model instance (if applicable).
184
+ * @property {number} [index] - The selected index for lists/tables.
167
185
  */
168
186
  /**
169
187
  * Response to an AgentIntent.
@@ -201,6 +219,40 @@ export function agent(task: string, context?: AgentContext): AgentIntent;
201
219
  * @typedef {'ask' | 'show' | 'progress' | 'render' | 'agent'} IntentType
202
220
  */
203
221
  export const INTENT_TYPES: readonly ["ask", "progress", "show", "log", "render", "agent"];
222
+ export type ProgressOptions = {
223
+ /**
224
+ * - Absolute total steps.
225
+ */
226
+ total?: number | undefined;
227
+ /**
228
+ * - Progress tracking ID.
229
+ */
230
+ id?: string | undefined;
231
+ /**
232
+ * - Width of the progress bar in terminal characters.
233
+ */
234
+ width?: number | undefined;
235
+ /**
236
+ * - Frames per second update rate limit.
237
+ */
238
+ fps?: number | undefined;
239
+ /**
240
+ * - Custom format string (e.g. '{time} {bar} {percent} {title}').
241
+ */
242
+ format?: string | undefined;
243
+ /**
244
+ * - Number of columns (terminal width).
245
+ */
246
+ columns?: number | undefined;
247
+ /**
248
+ * - Prevent wrapping and truncate instead.
249
+ */
250
+ forceOneLine?: boolean | undefined;
251
+ /**
252
+ * - Set to true to stop, or 'success'/'error' to stop with a status icon (for spinners).
253
+ */
254
+ stop?: boolean | "error" | "success" | undefined;
255
+ };
204
256
  export type ShowData = {
205
257
  component?: any;
206
258
  model?: import("@nan0web/types").Model | undefined;
@@ -275,6 +327,10 @@ export type ProgressIntent = {
275
327
  * - Status message from Model (i18n static field value).
276
328
  */
277
329
  message: string;
330
+ /**
331
+ * - Additional options for progress intent.
332
+ */
333
+ options?: ProgressOptions | undefined;
278
334
  };
279
335
  export type ShowLevel = "info" | "warn" | "error" | "success";
280
336
  export type LogLevel = ShowLevel;
@@ -310,6 +366,10 @@ export type ResultIntent = {
310
366
  * - The raw result data (JSON-serializable).
311
367
  */
312
368
  data: any;
369
+ /**
370
+ * - If true, Adapter MUST output data raw (no UI decorations).
371
+ */
372
+ raw?: boolean | undefined;
313
373
  };
314
374
  /**
315
375
  * Model requests rendering of a pure UI component (Header, Footer, Static Map).
@@ -384,7 +444,23 @@ export type AskResponse = {
384
444
  /**
385
445
  * - Whether the user cancelled this interaction (e.g. pressed ESC).
386
446
  */
387
- cancelled?: boolean | undefined;
447
+ cancelled: boolean;
448
+ /**
449
+ * - The action identifier (e.g., 'submit', 'exit', 'back').
450
+ */
451
+ action?: string | undefined;
452
+ /**
453
+ * - Additional payload or form data.
454
+ */
455
+ body?: any;
456
+ /**
457
+ * - The form model instance (if applicable).
458
+ */
459
+ form?: any;
460
+ /**
461
+ * - The selected index for lists/tables.
462
+ */
463
+ index?: number | undefined;
388
464
  };
389
465
  /**
390
466
  * Response to an AgentIntent.
@@ -8,6 +8,9 @@ export { IntentErrorModel } from "./IntentErrorModel.js";
8
8
  export { runGenerator } from "./GeneratorRunner.js";
9
9
  export { MaskHandler } from "./MaskHandler.js";
10
10
  export { LayoutModel } from "../domain/LayoutModel.js";
11
+ export type Intent = import("./Intent.js").Intent;
12
+ export type AskResponse = import("./Intent.js").AskResponse;
13
+ export type AskOptions = import("./InputAdapter.js").AskOptions;
11
14
  import UIStream from './Stream.js';
12
15
  import StreamEntry from './StreamEntry.js';
13
16
  import UIForm from './Form/Form.js';
@@ -35,7 +35,7 @@
35
35
  * @property {string|ContentData[]} [fieldset]
36
36
  * @property {string|ContentData[]} [figcaption]
37
37
  * @property {string|ContentData[]} [figure]
38
- * @property {string|ContentData[]} [footer]
38
+ * @property {any} [footer]
39
39
  * @property {string|ContentData[]} [form]
40
40
  * @property {string|ContentData[]} [h1]
41
41
  * @property {string|ContentData[]} [h2]
@@ -44,7 +44,7 @@
44
44
  * @property {string|ContentData[]} [h5]
45
45
  * @property {string|ContentData[]} [h6]
46
46
  * @property {string|ContentData[]} [head]
47
- * @property {string|ContentData[]} [header]
47
+ * @property {any} [header]
48
48
  * @property {string|ContentData[]} [hgroup]
49
49
  * @property {boolean|object} [hr]
50
50
  * @property {string|ContentData[]} [html]
@@ -136,6 +136,8 @@
136
136
  * @property {import('./components/FAQModel.js').FAQModel} [faq]
137
137
  * @property {import('./components/FeatureGridModel.js').FeatureGridModel} [featureGrid]
138
138
  * @property {import('./components/GalleryModel.js').GalleryModel} [gallery]
139
+ * @property {import('./HeaderModel.js').HeaderModel} [header]
140
+ * @property {import('./FooterModel.js').FooterModel} [footer]
139
141
  * @property {import('./components/InputModel.js').InputModel} [input]
140
142
  * @property {import('./components/MarkdownModel.js').MarkdownModel} [markdown]
141
143
  * @property {import('./components/PriceModel.js').PriceModel} [price]
@@ -213,7 +215,7 @@ export type HTML5Elements = {
213
215
  fieldset?: string | ContentData[] | undefined;
214
216
  figcaption?: string | ContentData[] | undefined;
215
217
  figure?: string | ContentData[] | undefined;
216
- footer?: string | ContentData[] | undefined;
218
+ footer?: any;
217
219
  form?: string | ContentData[] | undefined;
218
220
  h1?: string | ContentData[] | undefined;
219
221
  h2?: string | ContentData[] | undefined;
@@ -222,7 +224,7 @@ export type HTML5Elements = {
222
224
  h5?: string | ContentData[] | undefined;
223
225
  h6?: string | ContentData[] | undefined;
224
226
  head?: string | ContentData[] | undefined;
225
- header?: string | ContentData[] | undefined;
227
+ header?: any;
226
228
  hgroup?: string | ContentData[] | undefined;
227
229
  hr?: boolean | object;
228
230
  html?: string | ContentData[] | undefined;
@@ -313,6 +315,8 @@ export type CoreUIElements = {
313
315
  faq?: import("./components/FAQModel.js").FAQModel | undefined;
314
316
  featureGrid?: import("./components/FeatureGridModel.js").FeatureGridModel | undefined;
315
317
  gallery?: import("./components/GalleryModel.js").GalleryModel | undefined;
318
+ header?: import("./HeaderModel.js").HeaderModel | undefined;
319
+ footer?: import("./FooterModel.js").FooterModel | undefined;
316
320
  input?: import("./components/InputModel.js").InputModel | undefined;
317
321
  markdown?: import("./components/MarkdownModel.js").MarkdownModel | undefined;
318
322
  price?: import("./components/PriceModel.js").PriceModel | undefined;
@@ -8,6 +8,21 @@ export class Document extends Model {
8
8
  model: typeof Content;
9
9
  help: string;
10
10
  };
11
+ static $content: {
12
+ type: string;
13
+ model: typeof Content;
14
+ help: string;
15
+ };
16
+ static nav: {
17
+ type: string;
18
+ model: typeof Navigation;
19
+ help: string;
20
+ };
21
+ static langs: {
22
+ type: string;
23
+ model: any;
24
+ help: string;
25
+ };
11
26
  /**
12
27
  *
13
28
  * @param {Partial<Document>} [data]
@@ -16,6 +31,10 @@ export class Document extends Model {
16
31
  constructor(data?: Partial<Document>, options?: import("@nan0web/types").ModelOptions);
17
32
  /** @type {string} Title */ title: string;
18
33
  /** @type {Array<Content>} Content */ content: Array<Content>;
34
+ /** @type {Array<Content>} Layout configuration */ $content: Array<Content>;
35
+ /** @type {Navigation|string|Array<Navigation>} Navigation config */ nav: Navigation | string | Array<Navigation>;
36
+ /** @type {Array<Language>} Supported languages */ langs: Array<Language>;
19
37
  }
20
38
  import { Model } from '@nan0web/types';
21
39
  import { Content } from './Content.js';
40
+ import Navigation from './Navigation.js';
@@ -5,9 +5,9 @@
5
5
  export class ModelAsApp extends Model {
6
6
  /**
7
7
  * @param {Partial<ModelAsApp> | Record<string, any>} [data={}]
8
- * @param {ModelAsAppOptions} [options={}]
8
+ * @param {Partial<ModelAsAppOptions>} [options={}]
9
9
  */
10
- constructor(data?: Partial<ModelAsApp> | Record<string, any>, options?: ModelAsAppOptions);
10
+ constructor(data?: Partial<ModelAsApp> | Record<string, any>, options?: Partial<ModelAsAppOptions>);
11
11
  /** @returns {ModelAsAppOptions} */
12
12
  get _(): ModelAsAppOptions;
13
13
  /**
@@ -0,0 +1,21 @@
1
+ export default class ConfigApp extends ModelAsApp {
2
+ static name: string;
3
+ static alias: string;
4
+ static resource: {
5
+ type: string;
6
+ help: string;
7
+ positional: boolean;
8
+ default: string;
9
+ };
10
+ static action: {
11
+ type: string;
12
+ help: string;
13
+ positional: boolean;
14
+ default: string;
15
+ };
16
+ constructor(data?: {}, options?: {});
17
+ resource: any;
18
+ action: any;
19
+ run(): AsyncGenerator<import("../../core/Intent.js").ShowIntent | import("../../core/Intent.js").RenderIntent, import("../../core/Intent.js").ResultIntent, unknown>;
20
+ }
21
+ import { ModelAsApp } from '../ModelAsApp.js';
@@ -86,8 +86,8 @@ export class SnapshotAuditor {
86
86
  * @param {Partial<import('@nan0web/types').ModelOptions>} [options={}]
87
87
  */
88
88
  constructor(data?: Partial<SnapshotAuditor> | Record<string, any>, options?: Partial<import("@nan0web/types").ModelOptions>);
89
- /** @type {import('../../index.js').ModelAsAppOptions} */
90
- _: import("../../index.js").ModelAsAppOptions;
89
+ /** @type {import('@nan0web/types').ModelOptions} */
90
+ options: import("@nan0web/types").ModelOptions;
91
91
  /** @type {string} Target directory to audit */ dir: string;
92
92
  /** @type {string} Directory to scan for dictionaries */ data: string;
93
93
  /**
@@ -2,7 +2,7 @@ export class UIApp extends ModelAsApp {
2
2
  static command: {
3
3
  type: string;
4
4
  help: string;
5
- options: (typeof SnapshotAuditor | typeof GalleryCommand)[];
5
+ options: (typeof SnapshotAuditor | typeof GalleryCommand | typeof ConfigApp)[];
6
6
  default: string;
7
7
  positional: boolean;
8
8
  };
@@ -22,7 +22,7 @@ export class UIApp extends ModelAsApp {
22
22
  /** @type {string[]} */ _positionals: string[];
23
23
  /** @type {string} Type of command to run */ command: string;
24
24
  /** @type {boolean} Show help message */ help: boolean;
25
- run(): AsyncGenerator<import("../../core/Intent.js").ShowIntent | (import("../../core/Intent.js").AskIntent & {
25
+ run(): AsyncGenerator<import("../../core/Intent.js").ShowIntent | import("../../core/Intent.js").RenderIntent | (import("../../core/Intent.js").AskIntent & {
26
26
  $value?: any;
27
27
  $success?: boolean;
28
28
  $files?: Record<string, string>;
@@ -37,11 +37,6 @@ export class UIApp extends ModelAsApp {
37
37
  $success?: boolean;
38
38
  $files?: Record<string, string>;
39
39
  $message?: string;
40
- }) | (import("../../core/Intent.js").RenderIntent & {
41
- $value?: any;
42
- $success?: boolean;
43
- $files?: Record<string, string>;
44
- $message?: string;
45
40
  }) | (import("../../core/Intent.js").AgentIntent & {
46
41
  $value?: any;
47
42
  $success?: boolean;
@@ -58,3 +53,4 @@ export default UIApp;
58
53
  import { ModelAsApp } from '../ModelAsApp.js';
59
54
  import SnapshotAuditor from './SnapshotAuditor.js';
60
55
  import GalleryCommand from './GalleryCommand.js';
56
+ import ConfigApp from './ConfigApp.js';
package/types/index.d.ts CHANGED
@@ -21,6 +21,7 @@ export type Intent = import("./core/Intent.js").Intent;
21
21
  export type IntentResponse = import("./core/Intent.js").IntentResponse;
22
22
  export type AskIntent = import("./core/Intent.js").AskIntent;
23
23
  export type ProgressIntent = import("./core/Intent.js").ProgressIntent;
24
+ export type ProgressOptions = import("./core/Intent.js").ProgressOptions;
24
25
  export type LogIntent = import("./core/Intent.js").LogIntent;
25
26
  export type ShowIntent = import("./core/Intent.js").ShowIntent;
26
27
  export type RenderIntent = import("./core/Intent.js").RenderIntent;
@@ -29,6 +30,7 @@ export type IntentType = import("./core/Intent.js").IntentType;
29
30
  export type AskResponse = import("./core/Intent.js").AskResponse;
30
31
  export type AbortResponse = import("./core/Intent.js").AbortResponse;
31
32
  export type ShowData = import("./core/Intent.js").ShowData;
33
+ export type AskOptions = import("./core/InputAdapter.js").AskOptions;
32
34
  export type ModelAsAppOptions = import("./domain/index.js").ModelAsAppOptions;
33
35
  import Frame from './Frame/Frame.js';
34
36
  import FrameProps from './Frame/Props.js';
@@ -17,6 +17,7 @@ export class SpecAdapter {
17
17
  */
18
18
  ask(intent: import("../core/Intent.js").AskIntent): Promise<{
19
19
  value: any;
20
+ cancelled: boolean;
20
21
  }>;
21
22
  /**
22
23
  * @param {import('../core/Intent.js').ShowIntent} intent