@ecmaos/coreutils 0.2.0 → 0.3.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 (123) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +25 -0
  3. package/LICENSE +1 -1
  4. package/dist/commands/cal.js +2 -2
  5. package/dist/commands/cal.js.map +1 -1
  6. package/dist/commands/cat.js +2 -2
  7. package/dist/commands/cd.js +2 -2
  8. package/dist/commands/chmod.d.ts.map +1 -1
  9. package/dist/commands/chmod.js +16 -11
  10. package/dist/commands/chmod.js.map +1 -1
  11. package/dist/commands/cp.js +2 -2
  12. package/dist/commands/cp.js.map +1 -1
  13. package/dist/commands/date.js +2 -2
  14. package/dist/commands/date.js.map +1 -1
  15. package/dist/commands/echo.js +2 -2
  16. package/dist/commands/echo.js.map +1 -1
  17. package/dist/commands/false.js +2 -2
  18. package/dist/commands/fetch.d.ts +4 -0
  19. package/dist/commands/fetch.d.ts.map +1 -0
  20. package/dist/commands/fetch.js +210 -0
  21. package/dist/commands/fetch.js.map +1 -0
  22. package/dist/commands/format.d.ts +4 -0
  23. package/dist/commands/format.d.ts.map +1 -0
  24. package/dist/commands/format.js +178 -0
  25. package/dist/commands/format.js.map +1 -0
  26. package/dist/commands/hash.d.ts +4 -0
  27. package/dist/commands/hash.d.ts.map +1 -0
  28. package/dist/commands/hash.js +200 -0
  29. package/dist/commands/hash.js.map +1 -0
  30. package/dist/commands/head.js +2 -2
  31. package/dist/commands/id.js +2 -2
  32. package/dist/commands/id.js.map +1 -1
  33. package/dist/commands/less.d.ts.map +1 -1
  34. package/dist/commands/less.js +53 -2
  35. package/dist/commands/less.js.map +1 -1
  36. package/dist/commands/ls.d.ts.map +1 -1
  37. package/dist/commands/ls.js +120 -97
  38. package/dist/commands/ls.js.map +1 -1
  39. package/dist/commands/man.d.ts +4 -0
  40. package/dist/commands/man.d.ts.map +1 -0
  41. package/dist/commands/man.js +554 -0
  42. package/dist/commands/man.js.map +1 -0
  43. package/dist/commands/mkdir.js +2 -2
  44. package/dist/commands/mkdir.js.map +1 -1
  45. package/dist/commands/mktemp.d.ts +4 -0
  46. package/dist/commands/mktemp.d.ts.map +1 -0
  47. package/dist/commands/mktemp.js +229 -0
  48. package/dist/commands/mktemp.js.map +1 -0
  49. package/dist/commands/nc.js +2 -2
  50. package/dist/commands/nc.js.map +1 -1
  51. package/dist/commands/passkey.js +3 -3
  52. package/dist/commands/pwd.js +2 -2
  53. package/dist/commands/pwd.js.map +1 -1
  54. package/dist/commands/rm.d.ts.map +1 -1
  55. package/dist/commands/rm.js +57 -12
  56. package/dist/commands/rm.js.map +1 -1
  57. package/dist/commands/rmdir.js +2 -2
  58. package/dist/commands/rmdir.js.map +1 -1
  59. package/dist/commands/sockets.js +1 -1
  60. package/dist/commands/stat.d.ts.map +1 -1
  61. package/dist/commands/stat.js +37 -15
  62. package/dist/commands/stat.js.map +1 -1
  63. package/dist/commands/tail.js +2 -2
  64. package/dist/commands/tar.d.ts +4 -0
  65. package/dist/commands/tar.d.ts.map +1 -0
  66. package/dist/commands/tar.js +693 -0
  67. package/dist/commands/tar.js.map +1 -0
  68. package/dist/commands/touch.js +2 -2
  69. package/dist/commands/touch.js.map +1 -1
  70. package/dist/commands/true.js +2 -2
  71. package/dist/commands/unzip.d.ts +4 -0
  72. package/dist/commands/unzip.d.ts.map +1 -0
  73. package/dist/commands/unzip.js +443 -0
  74. package/dist/commands/unzip.js.map +1 -0
  75. package/dist/commands/user.d.ts +4 -0
  76. package/dist/commands/user.d.ts.map +1 -0
  77. package/dist/commands/user.js +427 -0
  78. package/dist/commands/user.js.map +1 -0
  79. package/dist/commands/whoami.js +2 -2
  80. package/dist/commands/whoami.js.map +1 -1
  81. package/dist/commands/zip.d.ts +4 -0
  82. package/dist/commands/zip.d.ts.map +1 -0
  83. package/dist/commands/zip.js +264 -0
  84. package/dist/commands/zip.js.map +1 -0
  85. package/dist/index.d.ts +9 -0
  86. package/dist/index.d.ts.map +1 -1
  87. package/dist/index.js +28 -1
  88. package/dist/index.js.map +1 -1
  89. package/package.json +6 -4
  90. package/src/commands/cal.ts +2 -2
  91. package/src/commands/cat.ts +2 -2
  92. package/src/commands/cd.ts +2 -2
  93. package/src/commands/chmod.ts +19 -11
  94. package/src/commands/cp.ts +2 -2
  95. package/src/commands/date.ts +2 -2
  96. package/src/commands/echo.ts +2 -2
  97. package/src/commands/false.ts +2 -2
  98. package/src/commands/fetch.ts +205 -0
  99. package/src/commands/format.ts +204 -0
  100. package/src/commands/hash.ts +215 -0
  101. package/src/commands/head.ts +2 -2
  102. package/src/commands/id.ts +2 -2
  103. package/src/commands/less.ts +50 -2
  104. package/src/commands/ls.ts +131 -91
  105. package/src/commands/man.ts +643 -0
  106. package/src/commands/mkdir.ts +2 -2
  107. package/src/commands/mktemp.ts +235 -0
  108. package/src/commands/nc.ts +2 -2
  109. package/src/commands/passkey.ts +3 -3
  110. package/src/commands/pwd.ts +2 -2
  111. package/src/commands/rm.ts +54 -12
  112. package/src/commands/rmdir.ts +2 -2
  113. package/src/commands/sockets.ts +1 -1
  114. package/src/commands/stat.ts +44 -16
  115. package/src/commands/tail.ts +2 -2
  116. package/src/commands/tar.ts +737 -0
  117. package/src/commands/touch.ts +2 -2
  118. package/src/commands/true.ts +2 -2
  119. package/src/commands/unzip.ts +517 -0
  120. package/src/commands/user.ts +436 -0
  121. package/src/commands/whoami.ts +2 -2
  122. package/src/commands/zip.ts +319 -0
  123. package/src/index.ts +28 -1
@@ -0,0 +1,436 @@
1
+ import chalk from 'chalk'
2
+ import type { Kernel, Process, Shell, Terminal, User } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: user [COMMAND] [OPTIONS] [USERNAME]
8
+ Manage users on the system.
9
+
10
+ Commands:
11
+ add USERNAME Add a new user
12
+ del USERNAME Delete a user
13
+ mod USERNAME Modify a user
14
+ list List all users (default)
15
+
16
+ Options for 'add':
17
+ -m, --create-home Create home directory
18
+ -s, --shell SHELL Login shell (default: ecmaos)
19
+ -g, --gid GID Group ID (default: same as UID)
20
+ -u, --uid UID User ID (default: auto-assigned)
21
+ -p, --password PASS Password (will prompt if not provided)
22
+
23
+ Options for 'del':
24
+ -r, --remove-home Remove home directory
25
+
26
+ Options for 'mod':
27
+ -s, --shell SHELL Change login shell
28
+ -g, --gid GID Change group ID
29
+ -p, --password Change password (will prompt)
30
+
31
+ --help Display this help and exit`
32
+ writelnStderr(process, terminal, usage)
33
+ }
34
+
35
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
36
+ return new TerminalCommand({
37
+ command: 'user',
38
+ description: 'Manage users on the system',
39
+ kernel,
40
+ shell,
41
+ terminal,
42
+ run: async (pid: number, argv: string[]) => {
43
+ const process = kernel.processes.get(pid) as Process | undefined
44
+
45
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
46
+ printUsage(process, terminal)
47
+ return 0
48
+ }
49
+
50
+ if (shell.credentials.suid !== 0) {
51
+ await writelnStderr(process, terminal, chalk.red('user: permission denied'))
52
+ return 1
53
+ }
54
+
55
+ const command = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : 'list'
56
+ const remainingArgs = command !== 'list' ? argv.slice(1) : argv
57
+
58
+ switch (command) {
59
+ case 'list': {
60
+ const users = Array.from(kernel.users.all.values()) as User[]
61
+
62
+ if (users.length === 0) {
63
+ await writelnStdout(process, terminal, 'No users found')
64
+ return 0
65
+ }
66
+
67
+ const uidWidth = Math.max(3, ...users.map(u => u.uid.toString().length))
68
+ const usernameWidth = Math.max(8, ...users.map(u => u.username.length))
69
+ const gidWidth = Math.max(3, ...users.map(u => u.gid.toString().length))
70
+
71
+ await writelnStdout(process, terminal, chalk.bold(
72
+ 'UID'.padEnd(uidWidth) + '\t' +
73
+ 'Username'.padEnd(usernameWidth) + '\t' +
74
+ 'GID'.padEnd(gidWidth) + '\t' +
75
+ 'Groups'
76
+ ))
77
+
78
+ for (const usr of users) {
79
+ await writelnStdout(process, terminal,
80
+ chalk.yellow(usr.uid.toString().padEnd(uidWidth)) + '\t' +
81
+ chalk.green(usr.username.padEnd(usernameWidth)) + '\t' +
82
+ chalk.cyan(usr.gid.toString().padEnd(gidWidth)) + '\t' +
83
+ chalk.blue(usr.groups.join(', ') || '-')
84
+ )
85
+ }
86
+
87
+ return 0
88
+ }
89
+
90
+ case 'add': {
91
+ let username = ''
92
+ let createHome = false
93
+ let shellValue = 'ecmaos'
94
+ let gid: number | undefined
95
+ let uid: number | undefined
96
+ let password: string | undefined
97
+
98
+ for (let i = 0; i < remainingArgs.length; i++) {
99
+ const arg = remainingArgs[i]
100
+ if (!arg || typeof arg !== 'string') continue
101
+ if (arg.startsWith('-')) {
102
+ if (arg === '-m' || arg === '--create-home') {
103
+ createHome = true
104
+ } else if (arg === '-s' || arg === '--shell') {
105
+ if (i + 1 < remainingArgs.length) {
106
+ const nextArg = remainingArgs[++i]
107
+ if (nextArg) {
108
+ shellValue = nextArg
109
+ } else {
110
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'s\''))
111
+ return 1
112
+ }
113
+ } else {
114
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'s\''))
115
+ return 1
116
+ }
117
+ } else if (arg === '-g' || arg === '--gid') {
118
+ if (i + 1 < remainingArgs.length) {
119
+ const gidStr = remainingArgs[++i]
120
+ if (gidStr) {
121
+ gid = parseInt(gidStr, 10)
122
+ if (isNaN(gid)) {
123
+ await writelnStderr(process, terminal, chalk.red(`user add: invalid GID '${gidStr}'`))
124
+ return 1
125
+ }
126
+ } else {
127
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'g\''))
128
+ return 1
129
+ }
130
+ } else {
131
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'g\''))
132
+ return 1
133
+ }
134
+ } else if (arg === '-u' || arg === '--uid') {
135
+ if (i + 1 < remainingArgs.length) {
136
+ const uidStr = remainingArgs[++i]
137
+ if (uidStr) {
138
+ uid = parseInt(uidStr, 10)
139
+ if (isNaN(uid)) {
140
+ await writelnStderr(process, terminal, chalk.red(`user add: invalid UID '${uidStr}'`))
141
+ return 1
142
+ }
143
+ } else {
144
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'u\''))
145
+ return 1
146
+ }
147
+ } else {
148
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'u\''))
149
+ return 1
150
+ }
151
+ } else if (arg === '-p' || arg === '--password') {
152
+ if (i + 1 < remainingArgs.length) {
153
+ const nextArg = remainingArgs[++i]
154
+ if (nextArg) {
155
+ password = nextArg
156
+ } else {
157
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'p\''))
158
+ return 1
159
+ }
160
+ } else {
161
+ await writelnStderr(process, terminal, chalk.red('user add: option requires an argument -- \'p\''))
162
+ return 1
163
+ }
164
+ } else if (arg === '--help' || arg === '-h') {
165
+ printUsage(process, terminal)
166
+ return 0
167
+ } else {
168
+ await writelnStderr(process, terminal, chalk.red(`user add: invalid option -- '${arg.replace(/^-+/, '')}'`))
169
+ return 1
170
+ }
171
+ } else {
172
+ if (!username) {
173
+ username = arg
174
+ } else {
175
+ await writelnStderr(process, terminal, chalk.red(`user add: unexpected argument '${arg}'`))
176
+ return 1
177
+ }
178
+ }
179
+ }
180
+
181
+ if (!username) {
182
+ await writelnStderr(process, terminal, chalk.red('user add: username required'))
183
+ await writelnStdout(process, terminal, 'Try \'user add --help\' for more information.')
184
+ return 1
185
+ }
186
+
187
+ const allUsers = Array.from(kernel.users.all.values()) as User[]
188
+ if (allUsers.some((u: User) => u.username === username)) {
189
+ await writelnStderr(process, terminal, chalk.red(`user add: user '${username}' already exists`))
190
+ return 1
191
+ }
192
+
193
+ if (uid !== undefined && kernel.users.all.has(uid)) {
194
+ await writelnStderr(process, terminal, chalk.red(`user add: UID ${uid} already in use`))
195
+ return 1
196
+ }
197
+
198
+ if (!password) {
199
+ password = await terminal.readline(chalk.cyan(`New password: `), true)
200
+ const confirm = await terminal.readline(chalk.cyan('Retype new password: '), true)
201
+ if (password !== confirm) {
202
+ await writelnStderr(process, terminal, chalk.red('user add: password mismatch'))
203
+ return 1
204
+ }
205
+ }
206
+
207
+ try {
208
+ await kernel.users.add({
209
+ username,
210
+ password,
211
+ uid,
212
+ gid,
213
+ shell: shellValue,
214
+ home: `/home/${username}`
215
+ }, { noHome: !createHome })
216
+ await writelnStdout(process, terminal, chalk.green(`user add: user '${username}' created successfully`))
217
+ return 0
218
+ } catch (error) {
219
+ await writelnStderr(process, terminal, chalk.red(`user add: ${error instanceof Error ? error.message : 'Unknown error'}`))
220
+ return 1
221
+ }
222
+ }
223
+
224
+ case 'del': {
225
+ let username = ''
226
+ let removeHome = false
227
+
228
+ for (let i = 0; i < remainingArgs.length; i++) {
229
+ const arg = remainingArgs[i]
230
+ if (!arg || typeof arg !== 'string') continue
231
+ if (arg.startsWith('-')) {
232
+ if (arg === '-r' || arg === '--remove-home') {
233
+ removeHome = true
234
+ } else if (arg === '--help' || arg === '-h') {
235
+ printUsage(process, terminal)
236
+ return 0
237
+ } else {
238
+ await writelnStderr(process, terminal, chalk.red(`user del: invalid option -- '${arg.replace(/^-+/, '')}'`))
239
+ return 1
240
+ }
241
+ } else {
242
+ if (!username) {
243
+ username = arg
244
+ } else {
245
+ await writelnStderr(process, terminal, chalk.red(`user del: unexpected argument '${arg}'`))
246
+ return 1
247
+ }
248
+ }
249
+ }
250
+
251
+ if (!username) {
252
+ await writelnStderr(process, terminal, chalk.red('user del: username required'))
253
+ await writelnStdout(process, terminal, 'Try \'user del --help\' for more information.')
254
+ return 1
255
+ }
256
+
257
+ const allUsers = Array.from(kernel.users.all.values()) as User[]
258
+ const usr = allUsers.find((u: User) => u.username === username)
259
+ if (!usr) {
260
+ await writelnStderr(process, terminal, chalk.red(`user del: user '${username}' does not exist`))
261
+ return 1
262
+ }
263
+
264
+ if (usr.uid === 0) {
265
+ await writelnStderr(process, terminal, chalk.red('user del: cannot delete root user'))
266
+ return 1
267
+ }
268
+
269
+ try {
270
+ await kernel.users.remove(usr.uid)
271
+
272
+ if (removeHome && usr.home) {
273
+ try {
274
+ const removeDirRecursive = async (dirPath: string): Promise<void> => {
275
+ const entries = await shell.context.fs.promises.readdir(dirPath)
276
+ for (const entry of entries) {
277
+ const entryPath = `${dirPath}/${entry}`
278
+ const stat = await shell.context.fs.promises.stat(entryPath)
279
+ if (stat.isDirectory()) {
280
+ await removeDirRecursive(entryPath)
281
+ } else {
282
+ await shell.context.fs.promises.unlink(entryPath)
283
+ }
284
+ }
285
+ await shell.context.fs.promises.rmdir(dirPath)
286
+ }
287
+ await removeDirRecursive(usr.home)
288
+ } catch {
289
+ await writelnStderr(process, terminal, chalk.yellow(`user del: warning: could not remove home directory '${usr.home}'`))
290
+ }
291
+ }
292
+
293
+ await shell.context.fs.promises.writeFile(
294
+ '/etc/passwd',
295
+ (await shell.context.fs.promises.readFile('/etc/passwd', 'utf8'))
296
+ .split('\n')
297
+ .filter((line: string) => !line.startsWith(`${username}:`))
298
+ .join('\n')
299
+ )
300
+ await shell.context.fs.promises.writeFile(
301
+ '/etc/shadow',
302
+ (await shell.context.fs.promises.readFile('/etc/shadow', 'utf8'))
303
+ .split('\n')
304
+ .filter((line: string) => !line.startsWith(`${username}:`))
305
+ .join('\n')
306
+ )
307
+
308
+ await writelnStdout(process, terminal, chalk.green(`user del: user '${username}' deleted successfully`))
309
+ return 0
310
+ } catch (error) {
311
+ await writelnStderr(process, terminal, chalk.red(`user del: ${error instanceof Error ? error.message : 'Unknown error'}`))
312
+ return 1
313
+ }
314
+ }
315
+
316
+ case 'mod': {
317
+ let username = ''
318
+ let shellValue: string | undefined
319
+ let gid: number | undefined
320
+ let changePassword = false
321
+
322
+ for (let i = 0; i < remainingArgs.length; i++) {
323
+ const arg = remainingArgs[i]
324
+ if (!arg || typeof arg !== 'string') continue
325
+ if (arg.startsWith('-')) {
326
+ if (arg === '-s' || arg === '--shell') {
327
+ if (i + 1 < remainingArgs.length) {
328
+ const nextArg = remainingArgs[++i]
329
+ if (nextArg) {
330
+ shellValue = nextArg
331
+ } else {
332
+ await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'s\''))
333
+ return 1
334
+ }
335
+ } else {
336
+ await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'s\''))
337
+ return 1
338
+ }
339
+ } else if (arg === '-g' || arg === '--gid') {
340
+ if (i + 1 < remainingArgs.length) {
341
+ const gidStr = remainingArgs[++i]
342
+ if (gidStr) {
343
+ gid = parseInt(gidStr, 10)
344
+ if (isNaN(gid)) {
345
+ await writelnStderr(process, terminal, chalk.red(`user mod: invalid GID '${gidStr}'`))
346
+ return 1
347
+ }
348
+ } else {
349
+ await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'g\''))
350
+ return 1
351
+ }
352
+ } else {
353
+ await writelnStderr(process, terminal, chalk.red('user mod: option requires an argument -- \'g\''))
354
+ return 1
355
+ }
356
+ } else if (arg === '-p' || arg === '--password') {
357
+ changePassword = true
358
+ } else if (arg === '--help' || arg === '-h') {
359
+ printUsage(process, terminal)
360
+ return 0
361
+ } else {
362
+ await writelnStderr(process, terminal, chalk.red(`user mod: invalid option -- '${arg.replace(/^-+/, '')}'`))
363
+ return 1
364
+ }
365
+ } else {
366
+ if (!username) {
367
+ username = arg
368
+ } else {
369
+ await writelnStderr(process, terminal, chalk.red(`user mod: unexpected argument '${arg}'`))
370
+ return 1
371
+ }
372
+ }
373
+ }
374
+
375
+ if (!username) {
376
+ await writelnStderr(process, terminal, chalk.red('user mod: username required'))
377
+ await writelnStdout(process, terminal, 'Try \'user mod --help\' for more information.')
378
+ return 1
379
+ }
380
+
381
+ const allUsers = Array.from(kernel.users.all.values()) as User[]
382
+ const usr = allUsers.find((u: User) => u.username === username)
383
+ if (!usr) {
384
+ await writelnStderr(process, terminal, chalk.red(`user mod: user '${username}' does not exist`))
385
+ return 1
386
+ }
387
+
388
+ const updates: Partial<User> = {}
389
+ if (shellValue !== undefined) {
390
+ updates.shell = shellValue
391
+ }
392
+ if (gid !== undefined) {
393
+ updates.gid = gid
394
+ }
395
+
396
+ if (changePassword) {
397
+ const newPassword = await terminal.readline(chalk.cyan('New password: '), true)
398
+ const confirm = await terminal.readline(chalk.cyan('Retype new password: '), true)
399
+
400
+ if (newPassword !== confirm) {
401
+ await writelnStderr(process, terminal, chalk.red('user mod: password mismatch'))
402
+ return 1
403
+ }
404
+
405
+ try {
406
+ const hashedPassword = await crypto.subtle.digest('SHA-256', new TextEncoder().encode(newPassword.trim()))
407
+ updates.password = Array.from(new Uint8Array(hashedPassword)).map(b => b.toString(16).padStart(2, '0')).join('')
408
+ } catch (error) {
409
+ await writelnStderr(process, terminal, chalk.red(`user mod: failed to hash password: ${error instanceof Error ? error.message : 'Unknown error'}`))
410
+ return 1
411
+ }
412
+ }
413
+
414
+ if (Object.keys(updates).length === 0 && !changePassword) {
415
+ await writelnStderr(process, terminal, chalk.red('user mod: no changes specified'))
416
+ return 1
417
+ }
418
+
419
+ try {
420
+ await kernel.users.update(usr.uid, updates)
421
+ await writelnStdout(process, terminal, chalk.green(`user mod: user '${username}' modified successfully`))
422
+ return 0
423
+ } catch (error) {
424
+ await writelnStderr(process, terminal, chalk.red(`user mod: ${error instanceof Error ? error.message : 'Unknown error'}`))
425
+ return 1
426
+ }
427
+ }
428
+
429
+ default:
430
+ await writelnStderr(process, terminal, chalk.red(`user: invalid command '${command}'`))
431
+ await writelnStdout(process, terminal, 'Try \'user --help\' for more information.')
432
+ return 1
433
+ }
434
+ }
435
+ })
436
+ }
@@ -1,13 +1,13 @@
1
1
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
2
  import { TerminalCommand } from '../shared/terminal-command.js'
3
- import { writelnStdout } from '../shared/helpers.js'
3
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
4
4
 
5
5
  function printUsage(process: Process | undefined, terminal: Terminal): void {
6
6
  const usage = `Usage: whoami
7
7
  Print effective user ID.
8
8
 
9
9
  --help display this help and exit`
10
- writelnStdout(process, terminal, usage)
10
+ writelnStderr(process, terminal, usage)
11
11
  }
12
12
 
13
13
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {