@nan0web/ui-cli 1.1.1 → 2.1.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 (117) hide show
  1. package/README.md +153 -203
  2. package/bin/cli.js +11 -0
  3. package/bin/nan0cli.js +86 -0
  4. package/package.json +27 -13
  5. package/src/CLI.js +22 -30
  6. package/src/CLiMessage.js +2 -3
  7. package/src/Command.js +26 -24
  8. package/src/CommandError.js +3 -5
  9. package/src/CommandHelp.js +40 -36
  10. package/src/CommandMessage.js +56 -40
  11. package/src/CommandParser.js +27 -25
  12. package/src/InputAdapter.js +630 -90
  13. package/src/OutputAdapter.js +7 -8
  14. package/src/README.md.js +241 -312
  15. package/src/components/Alert.js +3 -6
  16. package/src/components/prompt/Autocomplete.js +12 -0
  17. package/src/components/prompt/Confirm.js +29 -0
  18. package/src/components/prompt/DateTime.js +26 -0
  19. package/src/components/prompt/Input.js +15 -0
  20. package/src/components/prompt/Mask.js +12 -0
  21. package/src/components/prompt/Multiselect.js +26 -0
  22. package/src/components/prompt/Next.js +8 -0
  23. package/src/components/prompt/Password.js +13 -0
  24. package/src/components/prompt/Pause.js +9 -0
  25. package/src/components/prompt/ProgressBar.js +16 -0
  26. package/src/components/prompt/Select.js +29 -0
  27. package/src/components/prompt/Slider.js +16 -0
  28. package/src/components/prompt/Spinner.js +29 -0
  29. package/src/components/prompt/Toggle.js +13 -0
  30. package/src/components/prompt/Tree.js +17 -0
  31. package/src/components/view/Alert.js +78 -0
  32. package/src/components/view/Badge.js +11 -0
  33. package/src/components/view/Nav.js +23 -0
  34. package/src/components/view/Table.js +12 -0
  35. package/src/components/view/Toast.js +9 -0
  36. package/src/core/Component.js +79 -0
  37. package/src/core/PropValidation.js +138 -0
  38. package/src/core/render.js +37 -0
  39. package/src/index.js +85 -40
  40. package/src/test/PlaygroundTest.js +37 -25
  41. package/src/test/index.js +2 -4
  42. package/src/ui/alert.js +58 -0
  43. package/src/ui/autocomplete.js +86 -0
  44. package/src/ui/badge.js +35 -0
  45. package/src/ui/confirm.js +49 -0
  46. package/src/ui/date-time.js +45 -0
  47. package/src/ui/form.js +120 -55
  48. package/src/ui/index.js +18 -4
  49. package/src/ui/input.js +79 -152
  50. package/src/ui/mask.js +132 -0
  51. package/src/ui/multiselect.js +59 -0
  52. package/src/ui/nav.js +74 -0
  53. package/src/ui/next.js +18 -13
  54. package/src/ui/progress.js +88 -0
  55. package/src/ui/select.js +49 -72
  56. package/src/ui/slider.js +154 -0
  57. package/src/ui/spinner.js +65 -0
  58. package/src/ui/table.js +163 -0
  59. package/src/ui/toast.js +34 -0
  60. package/src/ui/toggle.js +34 -0
  61. package/src/ui/tree.js +393 -0
  62. package/src/utils/parse.js +1 -1
  63. package/types/CLI.d.ts +5 -5
  64. package/types/CLiMessage.d.ts +1 -1
  65. package/types/Command.d.ts +2 -2
  66. package/types/CommandHelp.d.ts +3 -3
  67. package/types/CommandMessage.d.ts +8 -8
  68. package/types/CommandParser.d.ts +3 -3
  69. package/types/InputAdapter.d.ts +149 -15
  70. package/types/OutputAdapter.d.ts +1 -1
  71. package/types/README.md.d.ts +1 -1
  72. package/types/UiMessage.d.ts +31 -29
  73. package/types/components/prompt/Autocomplete.d.ts +6 -0
  74. package/types/components/prompt/Confirm.d.ts +6 -0
  75. package/types/components/prompt/DateTime.d.ts +6 -0
  76. package/types/components/prompt/Input.d.ts +6 -0
  77. package/types/components/prompt/Mask.d.ts +6 -0
  78. package/types/components/prompt/Multiselect.d.ts +6 -0
  79. package/types/components/prompt/Next.d.ts +6 -0
  80. package/types/components/prompt/Password.d.ts +6 -0
  81. package/types/components/prompt/Pause.d.ts +6 -0
  82. package/types/components/prompt/ProgressBar.d.ts +12 -0
  83. package/types/components/prompt/Select.d.ts +18 -0
  84. package/types/components/prompt/Slider.d.ts +6 -0
  85. package/types/components/prompt/Spinner.d.ts +21 -0
  86. package/types/components/prompt/Toggle.d.ts +6 -0
  87. package/types/components/prompt/Tree.d.ts +6 -0
  88. package/types/components/view/Alert.d.ts +21 -0
  89. package/types/components/view/Badge.d.ts +5 -0
  90. package/types/components/view/Nav.d.ts +15 -0
  91. package/types/components/view/Table.d.ts +10 -0
  92. package/types/components/view/Toast.d.ts +5 -0
  93. package/types/core/Component.d.ts +34 -0
  94. package/types/core/PropValidation.d.ts +48 -0
  95. package/types/core/render.d.ts +6 -0
  96. package/types/index.d.ts +47 -15
  97. package/types/test/PlaygroundTest.d.ts +12 -8
  98. package/types/test/index.d.ts +1 -1
  99. package/types/ui/alert.d.ts +14 -0
  100. package/types/ui/autocomplete.d.ts +20 -0
  101. package/types/ui/badge.d.ts +8 -0
  102. package/types/ui/confirm.d.ts +21 -0
  103. package/types/ui/date-time.d.ts +19 -0
  104. package/types/ui/form.d.ts +43 -12
  105. package/types/ui/index.d.ts +17 -2
  106. package/types/ui/input.d.ts +31 -74
  107. package/types/ui/mask.d.ts +29 -0
  108. package/types/ui/multiselect.d.ts +25 -0
  109. package/types/ui/nav.d.ts +27 -0
  110. package/types/ui/progress.d.ts +43 -0
  111. package/types/ui/select.d.ts +25 -64
  112. package/types/ui/slider.d.ts +23 -0
  113. package/types/ui/spinner.d.ts +28 -0
  114. package/types/ui/table.d.ts +28 -0
  115. package/types/ui/toast.d.ts +8 -0
  116. package/types/ui/toggle.d.ts +17 -0
  117. package/types/ui/tree.d.ts +48 -0
package/src/CLI.js CHANGED
@@ -4,9 +4,9 @@
4
4
  * @module CLi
5
5
  */
6
6
 
7
- import { Message, OutputMessage } from "@nan0web/co"
8
- import Logger from "@nan0web/log"
9
- import CommandParser from "./CommandParser.js"
7
+ import { Message, OutputMessage } from '@nan0web/co'
8
+ import Logger from '@nan0web/log'
9
+ import CommandParser from './CommandParser.js'
10
10
 
11
11
  /**
12
12
  * Main CLi class.
@@ -28,17 +28,12 @@ export default class CLi {
28
28
  * @param {Array<Function>} [input.Messages] - Message classes for root commands.
29
29
  */
30
30
  constructor(input = {}) {
31
- const {
32
- argv = process.argv.slice(2),
33
- commands = {},
34
- logger,
35
- Messages = [],
36
- } = input
31
+ const { argv = process.argv.slice(2), commands = {}, logger, Messages = [] } = input
37
32
  this.argv = argv.map(String).filter(Boolean)
38
33
  this.logger = logger ?? new Logger({ level: Logger.detectLevel(this.argv) })
39
34
  this.Messages = Messages
40
35
  this.#commands = new Map(Object.entries(commands))
41
- this.#commands.set("help", () => this.#help())
36
+ this.#commands.set('help', () => this.#help())
42
37
  if (Messages.length > 0) this.#registerMessageCommands(Messages)
43
38
  }
44
39
 
@@ -53,14 +48,16 @@ export default class CLi {
53
48
  * @param {any} cmdClasses - Array of Message classes exposing a `run` generator.
54
49
  */
55
50
  #registerMessageCommands(cmdClasses) {
56
- cmdClasses.forEach(Class => {
51
+ cmdClasses.forEach((Class) => {
57
52
  const cmd = Class.name.toLowerCase()
58
53
  this.#commands.set(cmd, async function* (msg) {
59
54
  const validated = new Class(msg.body)
60
55
  /** @ts-ignore – only `content` needed for tests */
61
- yield new OutputMessage({ content: [`Executed ${cmd} with body: ${JSON.stringify(validated.body)}`] })
62
- if (typeof Class.run === "function") yield* Class.run(validated)
63
- if (typeof validated.run === "function") yield* validated.run(msg)
56
+ yield new OutputMessage({
57
+ content: [`Executed ${cmd} with body: ${JSON.stringify(validated.body)}`],
58
+ })
59
+ if (typeof Class.run === 'function') yield* Class.run(validated)
60
+ if (typeof validated.run === 'function') yield* validated.run(msg)
64
61
  })
65
62
  })
66
63
  }
@@ -71,7 +68,7 @@ export default class CLi {
71
68
  * @param {Message} [msg] - Optional pre‑built message.
72
69
  * @returns {AsyncGenerator<OutputMessage>}
73
70
  */
74
- async * run(msg) {
71
+ async *run(msg) {
75
72
  // const command = msg?.body?.command ?? this.#parseCommandName()
76
73
  const command =
77
74
  msg?.body?.command ??
@@ -84,18 +81,17 @@ export default class CLi {
84
81
 
85
82
  if (!fn) {
86
83
  yield new OutputMessage(`Unknown command: ${command}`)
87
- yield new OutputMessage(`Available commands: ${Array.from(this.#commands.keys()).join(", ")}`)
84
+ yield new OutputMessage(`Available commands: ${Array.from(this.#commands.keys()).join(', ')}`)
88
85
  return
89
86
  }
90
87
 
91
88
  // When there are no message‑based commands we forward the original message.
92
- const fullMsg = this.Messages.length > 0
93
- ? new CommandParser(this.Messages).parse(this.argv)
94
- : msg
89
+ const fullMsg =
90
+ this.Messages.length > 0 ? new CommandParser(this.Messages).parse(this.argv) : msg
95
91
 
96
92
  // `help` command – return a single OutputMessage that contains the three‑part body
97
93
  // expected by the test suite.
98
- if (command === "help") {
94
+ if (command === 'help') {
99
95
  yield* fn(fullMsg)
100
96
  return
101
97
  }
@@ -110,7 +106,7 @@ export default class CLi {
110
106
  * @returns {string}
111
107
  */
112
108
  #parseCommandName() {
113
- return this.argv.find(arg => !arg.startsWith("-")) || "help"
109
+ return this.argv.find((arg) => !arg.startsWith('-')) || 'help'
114
110
  }
115
111
 
116
112
  /**
@@ -118,19 +114,15 @@ export default class CLi {
118
114
  *
119
115
  * @yields {OutputMessage}
120
116
  */
121
- async * #help() {
122
- const lines = ["Available commands:"]
117
+ async *#help() {
118
+ const lines = ['Available commands:']
123
119
  for (const [name] of this.#commands) lines.push(` ${name}`)
124
120
 
125
121
  // The test expects a *single* message whose `body` is an array with three items:
126
122
  // 1. placeholder error line (when no message‑based commands exist)
127
123
  // 2. meta object describing the invoked command
128
124
  // 3. the array of help lines
129
- const body = [
130
- ["No commands defined for the CLi"],
131
- { command: "help", msg: undefined },
132
- lines,
133
- ]
125
+ const body = [['No commands defined for the CLi'], { command: 'help', msg: undefined }, lines]
134
126
 
135
127
  /** @ts-ignore – only `content` needed for tests */
136
128
  yield new OutputMessage({ body, content: lines })
@@ -145,7 +137,7 @@ export default class CLi {
145
137
  */
146
138
  static from(input) {
147
139
  if (input instanceof CLi) return input
148
- if (input && typeof input === "object") return new CLi(input)
149
- throw new TypeError("CLi.from expects an object or CLi instance")
140
+ if (input && typeof input === 'object') return new CLi(input)
141
+ throw new TypeError('CLi.from expects an object or CLi instance')
150
142
  }
151
143
  }
package/src/CLiMessage.js CHANGED
@@ -1,9 +1,8 @@
1
- import { UiMessage } from "@nan0web/ui"
1
+ import { UiMessage } from '@nan0web/ui'
2
2
 
3
3
  /**
4
4
  * @class CLiMessage
5
5
  * @deprecated Use UiMessage that is the same
6
6
  * @extends UiMessage
7
7
  */
8
- export default class CLiMessage extends UiMessage {
9
- }
8
+ export default class CLiMessage extends UiMessage {}
package/src/Command.js CHANGED
@@ -4,9 +4,9 @@
4
4
  * @module Command
5
5
  */
6
6
 
7
- import { Message } from "@nan0web/co"
8
- import CommandMessage from "./CommandMessage.js"
9
- import CommandError from "./CommandError.js"
7
+ import { Message } from '@nan0web/co'
8
+ import CommandMessage from './CommandMessage.js'
9
+ import CommandError from './CommandError.js'
10
10
 
11
11
  /**
12
12
  * Represents a command definition.
@@ -15,8 +15,8 @@ import CommandError from "./CommandError.js"
15
15
  * @deprecated Use CLI instead
16
16
  */
17
17
  export default class Command {
18
- /** @type {string} */ name = ""
19
- /** @type {string} */ help = ""
18
+ /** @type {string} */ name = ''
19
+ /** @type {string} */ help = ''
20
20
  /** @type {Object} */ options = {}
21
21
  /** @type {Function} */ run = async function* () {}
22
22
  /** @type {Command[]} */ children = []
@@ -30,10 +30,10 @@ export default class Command {
30
30
  * @param {Command[]} [config.children] - Sub‑commands.
31
31
  */
32
32
  constructor(config) {
33
- this.name = config.name || ""
34
- this.help = config.help || ""
33
+ this.name = config.name || ''
34
+ this.help = config.help || ''
35
35
  this.options = config.options || {}
36
- this.run = config.run || (async function* () {})
36
+ this.run = config.run || async function* () {}
37
37
  this.children = config.children || []
38
38
  }
39
39
 
@@ -55,7 +55,7 @@ export default class Command {
55
55
  * @returns {Command|null}
56
56
  */
57
57
  findSubcommand(name) {
58
- return this.children.find(c => c.name === name) || null
58
+ return this.children.find((c) => c.name === name) || null
59
59
  }
60
60
 
61
61
  /**
@@ -66,14 +66,14 @@ export default class Command {
66
66
  */
67
67
  parse(argv) {
68
68
  const args = Array.isArray(argv) ? argv : [argv]
69
- const msg = new CommandMessage({ name: "", argv: [], opts: {} })
69
+ const msg = new CommandMessage({ name: '', argv: [], opts: {} })
70
70
  let i = 0
71
71
  while (i < args.length) {
72
72
  const cur = args[i]
73
- if (cur.startsWith("--")) {
73
+ if (cur.startsWith('--')) {
74
74
  handleLongOption(msg, args, i)
75
75
  i = updateIndexAfterOption(args, i)
76
- } else if (cur.startsWith("-")) {
76
+ } else if (cur.startsWith('-')) {
77
77
  handleShortOption(msg, args, i)
78
78
  i = updateIndexAfterOption(args, i)
79
79
  } else {
@@ -84,7 +84,7 @@ export default class Command {
84
84
  }
85
85
  if (msg.name === this.name) {
86
86
  msg.argv = [...msg.argv]
87
- msg.name = ""
87
+ msg.name = ''
88
88
  }
89
89
  if (msg.argv.length > 0) {
90
90
  const subName = msg.argv[0]
@@ -108,9 +108,11 @@ export default class Command {
108
108
  generateHelp() {
109
109
  const parts = []
110
110
  if (this.help) parts.push(this.help)
111
- const optFlags = Object.keys(this.options).map(k => `--${k}`).join(" ")
111
+ const optFlags = Object.keys(this.options)
112
+ .map((k) => `--${k}`)
113
+ .join(' ')
112
114
  parts.push(optFlags ? `Usage: ${this.name} ${optFlags}` : `Usage: ${this.name}`)
113
- return parts.join("\n")
115
+ return parts.join('\n')
114
116
  }
115
117
 
116
118
  /**
@@ -120,13 +122,13 @@ export default class Command {
120
122
  * @yields {any}
121
123
  * @throws {CommandError}
122
124
  */
123
- async * execute(message) {
125
+ async *execute(message) {
124
126
  try {
125
- if (typeof this.run === "function") yield* this.run(message)
127
+ if (typeof this.run === 'function') yield* this.run(message)
126
128
  } catch (e) {
127
129
  if (e instanceof CommandError) throw e
128
130
  /** @ts-ignore */
129
- throw new CommandError("Command execution failed", { message: e.message, stack: e.stack })
131
+ throw new CommandError('Command execution failed', { message: e.message, stack: e.stack })
130
132
  }
131
133
  }
132
134
 
@@ -139,7 +141,7 @@ export default class Command {
139
141
  _applyDefaults(msg) {
140
142
  for (const [opt, [type, def]] of Object.entries(this.options)) {
141
143
  if (!(opt in msg.opts)) {
142
- msg.opts[opt] = def !== undefined ? def : type === Boolean ? false : ""
144
+ msg.opts[opt] = def !== undefined ? def : type === Boolean ? false : ''
143
145
  }
144
146
  }
145
147
  }
@@ -157,14 +159,14 @@ export default class Command {
157
159
  */
158
160
  function handleLongOption(msg, argv, index) {
159
161
  const cur = argv[index]
160
- const eq = cur.indexOf("=")
162
+ const eq = cur.indexOf('=')
161
163
  if (eq > -1) {
162
164
  const k = cur.slice(2, eq)
163
165
  const v = cur.slice(eq + 1)
164
166
  msg.opts[k] = v
165
167
  } else {
166
168
  const k = cur.slice(2)
167
- if (index + 1 < argv.length && !argv[index + 1].startsWith("-")) {
169
+ if (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
168
170
  msg.opts[k] = argv[index + 1]
169
171
  } else {
170
172
  msg.opts[k] = true
@@ -186,7 +188,7 @@ function handleShortOption(msg, argv, index) {
186
188
  for (const ch of cur) msg.opts[ch] = true
187
189
  } else {
188
190
  const k = cur
189
- if (index + 1 < argv.length && !argv[index + 1].startsWith("-")) {
191
+ if (index + 1 < argv.length && !argv[index + 1].startsWith('-')) {
190
192
  msg.opts[k] = argv[index + 1]
191
193
  } else {
192
194
  msg.opts[k] = true
@@ -204,7 +206,7 @@ function handleShortOption(msg, argv, index) {
204
206
  */
205
207
  function updateIndexAfterOption(argv, index) {
206
208
  const cur = argv[index]
207
- if (cur.includes("=")) return index + 1
208
- const hasVal = index + 1 < argv.length && !argv[index + 1].startsWith("-")
209
+ if (cur.includes('=')) return index + 1
210
+ const hasVal = index + 1 < argv.length && !argv[index + 1].startsWith('-')
209
211
  return hasVal ? index + 2 : index + 1
210
212
  }
@@ -17,7 +17,7 @@ export default class CommandError extends Error {
17
17
  */
18
18
  constructor(message, data = null) {
19
19
  super(message)
20
- this.name = "CommandError"
20
+ this.name = 'CommandError'
21
21
  this.data = data
22
22
  Error.captureStackTrace(this, CommandError)
23
23
  }
@@ -28,8 +28,6 @@ export default class CommandError extends Error {
28
28
  * @returns {string}
29
29
  */
30
30
  toString() {
31
- return this.data
32
- ? `${this.message}\n${JSON.stringify(this.data, null, 2)}`
33
- : this.message
31
+ return this.data ? `${this.message}\n${JSON.stringify(this.data, null, 2)}` : this.message
34
32
  }
35
- }
33
+ }
@@ -1,5 +1,5 @@
1
- import { Message } from "@nan0web/co"
2
- import Logger from "@nan0web/log"
1
+ import { Message } from '@nan0web/co'
2
+ import Logger from '@nan0web/log'
3
3
 
4
4
  /**
5
5
  * @typedef {Object} CommandHelpField MessageBodySchema
@@ -55,7 +55,7 @@ export default class CommandHelp {
55
55
  this.#usage(lines)
56
56
  this.#options(lines)
57
57
  this.#subcommands(lines)
58
- return lines.join("\n")
58
+ return lines.join('\n')
59
59
  }
60
60
 
61
61
  /**
@@ -72,12 +72,13 @@ export default class CommandHelp {
72
72
  */
73
73
  #header(lines) {
74
74
  const name = this.MessageClass.name.toLowerCase()
75
- const help = this.MessageClass['help'] || ""
76
- lines.push([
77
- `${this.Logger.style(name, { color: this.Logger.MAGENTA })}`,
78
- help
79
- ].filter(Boolean).join(""))
80
- lines.push("")
75
+ const help = this.MessageClass['help'] || ''
76
+ lines.push(
77
+ [`${this.Logger.style(name, { color: this.Logger.MAGENTA })}`, help]
78
+ .filter(Boolean)
79
+ .join('')
80
+ )
81
+ lines.push('')
81
82
  }
82
83
 
83
84
  /**
@@ -92,17 +93,17 @@ export default class CommandHelp {
92
93
 
93
94
  if (bodyProps.length === 0) {
94
95
  lines.push(`Usage: ${name}`)
95
- lines.push("")
96
+ lines.push('')
96
97
  return
97
98
  }
98
99
 
99
100
  const placeholderParts = []
100
101
  const flagParts = []
101
102
 
102
- bodyProps.forEach(prop => {
103
+ bodyProps.forEach((prop) => {
103
104
  /** @type {CommandHelpField} */
104
105
  const schema = this.BodyClass[prop] || {}
105
- const alias = schema.alias ? `-${schema.alias}, ` : ""
106
+ const alias = schema.alias ? `-${schema.alias}, ` : ''
106
107
  const placeholder = schema.placeholder || schema.defaultValue
107
108
  if (placeholder) {
108
109
  placeholderParts.push(`[${alias}--${prop}=${placeholder}]`)
@@ -116,22 +117,22 @@ export default class CommandHelp {
116
117
  // * when placeholders exist:
117
118
  // – if exactly ONE flag part → prepend with ", "
118
119
  // – otherwise just space‑separate all parts.
119
- let usage = ""
120
+ let usage = ''
120
121
  if (placeholderParts.length) {
121
- usage = placeholderParts.join(" ")
122
+ usage = placeholderParts.join(' ')
122
123
  if (flagParts.length) {
123
124
  if (flagParts.length === 1) {
124
125
  usage = `${usage}, ${flagParts[0]}`
125
126
  } else {
126
- usage = `${usage} ${flagParts.join(" ")}`
127
+ usage = `${usage} ${flagParts.join(' ')}`
127
128
  }
128
129
  }
129
130
  } else {
130
131
  // only flag parts
131
- usage = flagParts.join(" , ")
132
+ usage = flagParts.join(' , ')
132
133
  }
133
134
  lines.push(`Usage: ${name} ${usage}`)
134
- lines.push("")
135
+ lines.push('')
135
136
  }
136
137
 
137
138
  /**
@@ -144,27 +145,30 @@ export default class CommandHelp {
144
145
  const bodyProps = Object.keys(bodyInstance)
145
146
  if (bodyProps.length === 0) return
146
147
 
147
- lines.push("Options:")
148
- bodyProps.forEach(prop => {
148
+ lines.push('Options:')
149
+ bodyProps.forEach((prop) => {
149
150
  /** @type {CommandHelpField} */
150
151
  const schema = this.BodyClass[prop] || {}
151
- if (typeof schema !== "object") return
152
-
153
- const flags = schema.alias
154
- ? `--${prop}, -${schema.alias}`
155
- : `--${prop}`
156
-
157
- const type = undefined !== schema.type ? String(schema.type)
158
- : undefined !== schema.defaultValue ? typeof schema.defaultValue
159
- : undefined !== schema.placeholder ? typeof schema.placeholder
160
- : "any"
161
- const required = schema.required || schema.pattern || schema.defaultValue === undefined ? " *" : " "
162
- const description = schema.help || "No description"
152
+ if (typeof schema !== 'object') return
153
+
154
+ const flags = schema.alias ? `--${prop}, -${schema.alias}` : `--${prop}`
155
+
156
+ const type =
157
+ undefined !== schema.type
158
+ ? String(schema.type)
159
+ : undefined !== schema.defaultValue
160
+ ? typeof schema.defaultValue
161
+ : undefined !== schema.placeholder
162
+ ? typeof schema.placeholder
163
+ : 'any'
164
+ const required =
165
+ schema.required || schema.pattern || schema.defaultValue === undefined ? ' *' : ' '
166
+ const description = schema.help || 'No description'
163
167
 
164
168
  // Pad flags to align the type column with the expectations.
165
169
  lines.push(` ${flags.padEnd(30)} ${type.padEnd(9)}${required} ${description}`)
166
170
  })
167
- lines.push("")
171
+ lines.push('')
168
172
  }
169
173
 
170
174
  /**
@@ -185,12 +189,12 @@ export default class CommandHelp {
185
189
  const children = this.MessageClass['Children'] || []
186
190
  if (children.length === 0) return
187
191
 
188
- lines.push("Subcommands:")
189
- children.forEach(ChildClass => {
192
+ lines.push('Subcommands:')
193
+ children.forEach((ChildClass) => {
190
194
  const childName = ChildClass.name.toLowerCase()
191
- const childHelp = ChildClass.help || "No description"
195
+ const childHelp = ChildClass.help || 'No description'
192
196
  lines.push(` ${childName.padEnd(20)} ${childHelp}`)
193
197
  })
194
- lines.push("")
198
+ lines.push('')
195
199
  }
196
200
  }
@@ -4,9 +4,9 @@
4
4
  * @module CommandMessage
5
5
  */
6
6
 
7
- import { Message } from "@nan0web/co"
8
- import { str2argv } from "./utils/parse.js"
9
- import CommandError from "./CommandError.js"
7
+ import { Message } from '@nan0web/co'
8
+ import { str2argv } from './utils/parse.js'
9
+ import CommandError from './CommandError.js'
10
10
 
11
11
  /**
12
12
  * @class
@@ -14,7 +14,7 @@ import CommandError from "./CommandError.js"
14
14
  * @extends Message
15
15
  */
16
16
  export default class CommandMessage extends Message {
17
- #name = ""
17
+ #name = ''
18
18
  #argv = []
19
19
  #opts = {}
20
20
  #children = []
@@ -30,14 +30,8 @@ export default class CommandMessage extends Message {
30
30
  constructor(input = {}) {
31
31
  super(input)
32
32
  /** @type {any} */
33
- const data = typeof input === "object" && !Array.isArray(input) ? input : {}
34
- const {
35
- name = "",
36
- argv = [],
37
- opts = {},
38
- children = [],
39
- body = {},
40
- } = data
33
+ const data = typeof input === 'object' && !Array.isArray(input) ? input : {}
34
+ const { name = '', argv = [], opts = {}, children = [], body = {} } = data
41
35
 
42
36
  const fullBody = { ...body, ...opts }
43
37
  this.body = fullBody
@@ -45,9 +39,9 @@ export default class CommandMessage extends Message {
45
39
  this.#name = String(name)
46
40
  this.#argv = argv.map(String)
47
41
  this.#opts = opts
48
- this.#children = children.map(c => CommandMessage.from(c))
42
+ this.#children = children.map((c) => CommandMessage.from(c))
49
43
 
50
- if (typeof input === "string" || Array.isArray(input)) {
44
+ if (typeof input === 'string' || Array.isArray(input)) {
51
45
  const parsed = CommandMessage.parse(input)
52
46
  this.#name = parsed.name
53
47
  this.#argv = parsed.argv
@@ -57,38 +51,60 @@ export default class CommandMessage extends Message {
57
51
  }
58
52
 
59
53
  /** @returns {string} */
60
- get name() { return this.#name }
54
+ get name() {
55
+ return this.#name
56
+ }
61
57
  /** @param {string} v */
62
- set name(v) { this.#name = String(v) }
58
+ set name(v) {
59
+ this.#name = String(v)
60
+ }
63
61
 
64
62
  /** @returns {string[]} */
65
- get argv() { return this.#argv }
63
+ get argv() {
64
+ return this.#argv
65
+ }
66
66
  /** @param {string[]} v */
67
- set argv(v) { this.#argv = v.map(String) }
67
+ set argv(v) {
68
+ this.#argv = v.map(String)
69
+ }
68
70
 
69
71
  /** @returns {Object} */
70
- get opts() { return this.#opts }
72
+ get opts() {
73
+ return this.#opts
74
+ }
71
75
  /** @param {Object} v */
72
- set opts(v) { this.#opts = v }
76
+ set opts(v) {
77
+ this.#opts = v
78
+ }
73
79
 
74
80
  /** @returns {Array<CommandMessage>} */
75
- get children() { return this.#children }
81
+ get children() {
82
+ return this.#children
83
+ }
76
84
 
77
85
  /** @returns {Array<string>} Full command line (name + args). */
78
- get args() { return [this.name, ...this.argv].filter(Boolean) }
86
+ get args() {
87
+ return [this.name, ...this.argv].filter(Boolean)
88
+ }
79
89
 
80
90
  /** @returns {string} Sub‑command name of the first child, or empty string. */
81
- get subCommand() { return this.children[0]?.name || "" }
91
+ get subCommand() {
92
+ return this.children[0]?.name || ''
93
+ }
82
94
 
83
95
  /** @returns {CommandMessage|null} First child message, or null. */
84
- get subCommandMessage() { return this.children[0] || null }
96
+ get subCommandMessage() {
97
+ return this.children[0] || null
98
+ }
85
99
 
86
100
  /**
87
101
  * Append a child {@link CommandMessage}.
88
102
  *
89
103
  * @param {CommandMessage|Object} msg
90
104
  */
91
- add(msg) { this.#children.push(CommandMessage.from(msg)) }
105
+ add(msg) {
106
+ this.#children.push(CommandMessage.from(msg))
107
+ }
92
108
 
93
109
  /**
94
110
  * Convert the message back to a command‑line string.
@@ -98,9 +114,9 @@ export default class CommandMessage extends Message {
98
114
  toString() {
99
115
  const optsStr = Object.entries(this.opts)
100
116
  .map(([k, v]) => (v === true ? `--${k}` : `--${k} ${String(v)}`))
101
- .join(" ")
102
- const argsStr = this.argv.join(" ")
103
- return [this.name, argsStr, optsStr].filter(Boolean).join(" ")
117
+ .join(' ')
118
+ const argsStr = this.argv.join(' ')
119
+ return [this.name, argsStr, optsStr].filter(Boolean).join(' ')
104
120
  }
105
121
 
106
122
  /**
@@ -112,37 +128,37 @@ export default class CommandMessage extends Message {
112
128
  * @throws {CommandError} If no input is supplied.
113
129
  */
114
130
  static parse(argv, BodyClass) {
115
- if (typeof argv === "string") argv = str2argv(argv)
116
- if (argv.length === 0) throw new CommandError("No input provided")
117
- const result = { name: "", argv: [], opts: {} }
131
+ if (typeof argv === 'string') argv = str2argv(argv)
132
+ if (argv.length === 0) throw new CommandError('No input provided')
133
+ const result = { name: '', argv: [], opts: {} }
118
134
  let i = 0
119
- if (!argv[0].startsWith("-")) {
135
+ if (!argv[0].startsWith('-')) {
120
136
  result.name = argv[0]
121
137
  i = 1
122
138
  }
123
139
  while (i < argv.length) {
124
140
  const cur = argv[i]
125
- if (cur.startsWith("--")) {
126
- const eq = cur.indexOf("=")
141
+ if (cur.startsWith('--')) {
142
+ const eq = cur.indexOf('=')
127
143
  if (eq > -1) {
128
144
  const k = cur.slice(2, eq)
129
145
  const v = cur.slice(eq + 1)
130
146
  result.opts[k] = v
131
147
  } else {
132
148
  const k = cur.slice(2)
133
- if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
149
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
134
150
  result.opts[k] = argv[++i]
135
151
  } else {
136
152
  result.opts[k] = true
137
153
  }
138
154
  }
139
- } else if (cur.startsWith("-") && cur.length > 1) {
155
+ } else if (cur.startsWith('-') && cur.length > 1) {
140
156
  const shorts = cur.slice(1)
141
157
  if (shorts.length > 1) {
142
- shorts.split("").forEach(s => (result.opts[s] = true))
158
+ shorts.split('').forEach((s) => (result.opts[s] = true))
143
159
  } else {
144
160
  const k = shorts
145
- if (i + 1 < argv.length && !argv[i + 1].startsWith("-")) {
161
+ if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
146
162
  result.opts[k] = argv[++i]
147
163
  } else {
148
164
  result.opts[k] = true
@@ -160,7 +176,7 @@ export default class CommandMessage extends Message {
160
176
  msg.body = body
161
177
  /** @ts-ignore */
162
178
  const errors = body.getErrors?.() || {}
163
- if (Object.keys(errors).length) throw new CommandError("Validation failed", { errors })
179
+ if (Object.keys(errors).length) throw new CommandError('Validation failed', { errors })
164
180
  }
165
181
  return msg
166
182
  }
@@ -175,7 +191,7 @@ export default class CommandMessage extends Message {
175
191
  if (input instanceof CommandMessage) return input
176
192
  if (input instanceof Message) {
177
193
  /** @ts-ignore */
178
- return new CommandMessage({ body: input.body, name: input.name || "" })
194
+ return new CommandMessage({ body: input.body, name: input.name || '' })
179
195
  }
180
196
  return new CommandMessage(input)
181
197
  }