@ball-lang/cli 0.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.
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Static mapping of every Ball base function to its capability category.
3
+ *
4
+ * Since every side effect in Ball flows through a named base function in a
5
+ * known module, this table is provably complete. No function can perform I/O,
6
+ * access the filesystem, or spawn threads without appearing here.
7
+ *
8
+ * Ported from `dart/shared/lib/capability_table.dart`.
9
+ */
10
+
11
+ /** Capability categories for Ball base functions. */
12
+ export type Capability =
13
+ | 'pure'
14
+ | 'io'
15
+ | 'fs'
16
+ | 'process'
17
+ | 'time'
18
+ | 'random'
19
+ | 'memory'
20
+ | 'concurrency'
21
+ | 'network'
22
+ | 'async';
23
+
24
+ /** Canonical ordering of capabilities (matches the Dart enum order). */
25
+ export const ALL_CAPABILITIES: readonly Capability[] = [
26
+ 'pure',
27
+ 'io',
28
+ 'fs',
29
+ 'process',
30
+ 'time',
31
+ 'random',
32
+ 'memory',
33
+ 'concurrency',
34
+ 'network',
35
+ 'async',
36
+ ];
37
+
38
+ /** Risk level associated with each capability. */
39
+ export const capabilityRiskLevel: Record<Capability, string> = {
40
+ pure: 'none',
41
+ io: 'low',
42
+ fs: 'medium',
43
+ process: 'high',
44
+ time: 'low',
45
+ random: 'low',
46
+ memory: 'high',
47
+ concurrency: 'medium',
48
+ network: 'high',
49
+ async: 'low',
50
+ };
51
+
52
+ /**
53
+ * Lookup the capability of a base function call.
54
+ *
55
+ * Returns `undefined` for non-base / user-defined functions (which are pure
56
+ * by construction — they can only call other functions in this table).
57
+ */
58
+ export function lookupCapability(
59
+ module: string,
60
+ fn: string,
61
+ ): Capability | undefined {
62
+ return CAPABILITY_TABLE[`${module}.${fn}`];
63
+ }
64
+
65
+ export const CAPABILITY_TABLE: Readonly<Record<string, Capability>> = {
66
+ // ── std: print ──
67
+ 'std.print': 'io',
68
+
69
+ // ── std: arithmetic (pure) ──
70
+ 'std.add': 'pure',
71
+ 'std.subtract': 'pure',
72
+ 'std.multiply': 'pure',
73
+ 'std.divide': 'pure',
74
+ 'std.divide_double': 'pure',
75
+ 'std.modulo': 'pure',
76
+ 'std.negate': 'pure',
77
+
78
+ // ── std: comparison (pure) ──
79
+ 'std.equals': 'pure',
80
+ 'std.not_equals': 'pure',
81
+ 'std.less_than': 'pure',
82
+ 'std.greater_than': 'pure',
83
+ 'std.lte': 'pure',
84
+ 'std.gte': 'pure',
85
+
86
+ // ── std: logical (pure) ──
87
+ 'std.and': 'pure',
88
+ 'std.or': 'pure',
89
+ 'std.not': 'pure',
90
+
91
+ // ── std: bitwise (pure) ──
92
+ 'std.bitwise_and': 'pure',
93
+ 'std.bitwise_or': 'pure',
94
+ 'std.bitwise_xor': 'pure',
95
+ 'std.bitwise_not': 'pure',
96
+ 'std.left_shift': 'pure',
97
+ 'std.right_shift': 'pure',
98
+ 'std.unsigned_right_shift': 'pure',
99
+
100
+ // ── std: increment/decrement (pure) ──
101
+ 'std.pre_increment': 'pure',
102
+ 'std.pre_decrement': 'pure',
103
+ 'std.post_increment': 'pure',
104
+ 'std.post_decrement': 'pure',
105
+
106
+ // ── std: string & conversion (pure) ──
107
+ 'std.concat': 'pure',
108
+ 'std.length': 'pure',
109
+ 'std.to_string': 'pure',
110
+ 'std.int_to_string': 'pure',
111
+ 'std.double_to_string': 'pure',
112
+ 'std.string_to_int': 'pure',
113
+ 'std.string_to_double': 'pure',
114
+
115
+ // ── std: null safety (pure) ──
116
+ 'std.null_coalesce': 'pure',
117
+ 'std.null_check': 'pure',
118
+
119
+ // ── std: control flow (pure) ──
120
+ 'std.if': 'pure',
121
+ 'std.for': 'pure',
122
+ 'std.for_in': 'pure',
123
+ 'std.while': 'pure',
124
+ 'std.do_while': 'pure',
125
+ 'std.switch': 'pure',
126
+
127
+ // ── std: error handling (pure) ──
128
+ 'std.try': 'pure',
129
+ 'std.throw': 'pure',
130
+ 'std.rethrow': 'pure',
131
+
132
+ // ── std: assertions (pure) ──
133
+ 'std.assert': 'pure',
134
+
135
+ // ── std: flow control (pure) ──
136
+ 'std.return': 'pure',
137
+ 'std.break': 'pure',
138
+ 'std.continue': 'pure',
139
+
140
+ // ── std: generators & async ──
141
+ 'std.yield': 'async',
142
+ 'std.yield_each': 'async',
143
+ 'std.await': 'async',
144
+ 'std.async': 'async',
145
+
146
+ // ── std: assignment (pure) ──
147
+ 'std.assign': 'pure',
148
+ 'std.compound_assign': 'pure',
149
+
150
+ // ── std: type operations (pure) ──
151
+ 'std.is': 'pure',
152
+ 'std.is_not': 'pure',
153
+ 'std.as': 'pure',
154
+
155
+ // ── std: indexing (pure) ──
156
+ 'std.index': 'pure',
157
+ 'std.index_assign': 'pure',
158
+
159
+ // ── std: labels (pure) ──
160
+ 'std.labeled': 'pure',
161
+ 'std.label': 'pure',
162
+ 'std.goto': 'pure',
163
+ 'std.paren': 'pure',
164
+
165
+ // ── std: string operations (pure) ──
166
+ 'std.string_length': 'pure',
167
+ 'std.string_is_empty': 'pure',
168
+ 'std.string_concat': 'pure',
169
+ 'std.string_contains': 'pure',
170
+ 'std.string_starts_with': 'pure',
171
+ 'std.string_ends_with': 'pure',
172
+ 'std.string_index_of': 'pure',
173
+ 'std.string_last_index_of': 'pure',
174
+ 'std.string_substring': 'pure',
175
+ 'std.string_char_at': 'pure',
176
+ 'std.string_char_code_at': 'pure',
177
+ 'std.string_from_char_code': 'pure',
178
+ 'std.string_to_upper': 'pure',
179
+ 'std.string_to_lower': 'pure',
180
+ 'std.string_trim': 'pure',
181
+ 'std.string_trim_start': 'pure',
182
+ 'std.string_trim_end': 'pure',
183
+ 'std.string_replace': 'pure',
184
+ 'std.string_replace_all': 'pure',
185
+ 'std.string_split': 'pure',
186
+ 'std.string_repeat': 'pure',
187
+ 'std.string_pad_left': 'pure',
188
+ 'std.string_pad_right': 'pure',
189
+ 'std.string_interpolation': 'pure',
190
+
191
+ // ── std: regex (pure) ──
192
+ 'std.regex_match': 'pure',
193
+ 'std.regex_find': 'pure',
194
+ 'std.regex_find_all': 'pure',
195
+ 'std.regex_replace': 'pure',
196
+ 'std.regex_replace_all': 'pure',
197
+
198
+ // ── std: math (pure) ──
199
+ 'std.math_abs': 'pure',
200
+ 'std.math_floor': 'pure',
201
+ 'std.math_ceil': 'pure',
202
+ 'std.math_round': 'pure',
203
+ 'std.math_trunc': 'pure',
204
+ 'std.math_sqrt': 'pure',
205
+ 'std.math_pow': 'pure',
206
+ 'std.math_log': 'pure',
207
+ 'std.math_log2': 'pure',
208
+ 'std.math_log10': 'pure',
209
+ 'std.math_exp': 'pure',
210
+ 'std.math_sin': 'pure',
211
+ 'std.math_cos': 'pure',
212
+ 'std.math_tan': 'pure',
213
+ 'std.math_asin': 'pure',
214
+ 'std.math_acos': 'pure',
215
+ 'std.math_atan': 'pure',
216
+ 'std.math_atan2': 'pure',
217
+ 'std.math_min': 'pure',
218
+ 'std.math_max': 'pure',
219
+ 'std.math_clamp': 'pure',
220
+ 'std.math_pi': 'pure',
221
+ 'std.math_e': 'pure',
222
+ 'std.math_infinity': 'pure',
223
+ 'std.math_nan': 'pure',
224
+ 'std.math_is_nan': 'pure',
225
+ 'std.math_is_finite': 'pure',
226
+ 'std.math_is_infinite': 'pure',
227
+ 'std.math_sign': 'pure',
228
+ 'std.math_gcd': 'pure',
229
+ 'std.math_lcm': 'pure',
230
+
231
+ // ── std_io ──
232
+ 'std_io.print_error': 'io',
233
+ 'std_io.read_line': 'io',
234
+ 'std_io.exit': 'process',
235
+ 'std_io.panic': 'process',
236
+ 'std_io.sleep_ms': 'time',
237
+ 'std_io.timestamp_ms': 'time',
238
+ 'std_io.random_int': 'random',
239
+ 'std_io.random_double': 'random',
240
+ 'std_io.env_get': 'io',
241
+ 'std_io.args_get': 'io',
242
+
243
+ // ── std_fs ──
244
+ 'std_fs.file_read': 'fs',
245
+ 'std_fs.file_read_bytes': 'fs',
246
+ 'std_fs.file_write': 'fs',
247
+ 'std_fs.file_write_bytes': 'fs',
248
+ 'std_fs.file_append': 'fs',
249
+ 'std_fs.file_exists': 'fs',
250
+ 'std_fs.file_delete': 'fs',
251
+ 'std_fs.dir_list': 'fs',
252
+ 'std_fs.dir_create': 'fs',
253
+ 'std_fs.dir_exists': 'fs',
254
+
255
+ // ── std_collections (all pure) ──
256
+ 'std_collections.list_push': 'pure',
257
+ 'std_collections.list_pop': 'pure',
258
+ 'std_collections.list_insert': 'pure',
259
+ 'std_collections.list_remove_at': 'pure',
260
+ 'std_collections.list_get': 'pure',
261
+ 'std_collections.list_set': 'pure',
262
+ 'std_collections.list_length': 'pure',
263
+ 'std_collections.list_is_empty': 'pure',
264
+ 'std_collections.list_first': 'pure',
265
+ 'std_collections.list_last': 'pure',
266
+ 'std_collections.list_single': 'pure',
267
+ 'std_collections.list_contains': 'pure',
268
+ 'std_collections.list_index_of': 'pure',
269
+ 'std_collections.list_map': 'pure',
270
+ 'std_collections.list_filter': 'pure',
271
+ 'std_collections.list_reduce': 'pure',
272
+ 'std_collections.list_find': 'pure',
273
+ 'std_collections.list_any': 'pure',
274
+ 'std_collections.list_all': 'pure',
275
+ 'std_collections.list_none': 'pure',
276
+ 'std_collections.list_sort': 'pure',
277
+ 'std_collections.list_sort_by': 'pure',
278
+ 'std_collections.list_reverse': 'pure',
279
+ 'std_collections.list_slice': 'pure',
280
+ 'std_collections.list_flat_map': 'pure',
281
+ 'std_collections.list_zip': 'pure',
282
+ 'std_collections.list_take': 'pure',
283
+ 'std_collections.list_drop': 'pure',
284
+ 'std_collections.list_concat': 'pure',
285
+ 'std_collections.map_get': 'pure',
286
+ 'std_collections.map_set': 'pure',
287
+ 'std_collections.map_delete': 'pure',
288
+ 'std_collections.map_contains_key': 'pure',
289
+ 'std_collections.map_keys': 'pure',
290
+ 'std_collections.map_values': 'pure',
291
+ 'std_collections.map_entries': 'pure',
292
+ 'std_collections.map_from_entries': 'pure',
293
+ 'std_collections.map_merge': 'pure',
294
+ 'std_collections.map_map': 'pure',
295
+ 'std_collections.map_filter': 'pure',
296
+ 'std_collections.map_is_empty': 'pure',
297
+ 'std_collections.map_length': 'pure',
298
+ 'std_collections.set_create': 'pure',
299
+ 'std_collections.set_add': 'pure',
300
+ 'std_collections.set_remove': 'pure',
301
+ 'std_collections.set_contains': 'pure',
302
+ 'std_collections.set_union': 'pure',
303
+ 'std_collections.set_intersection': 'pure',
304
+ 'std_collections.set_difference': 'pure',
305
+ 'std_collections.set_length': 'pure',
306
+ 'std_collections.set_is_empty': 'pure',
307
+ 'std_collections.set_to_list': 'pure',
308
+ 'std_collections.string_join': 'pure',
309
+
310
+ // ── std_convert (all pure) ──
311
+ 'std_convert.json_encode': 'pure',
312
+ 'std_convert.json_decode': 'pure',
313
+ 'std_convert.utf8_encode': 'pure',
314
+ 'std_convert.utf8_decode': 'pure',
315
+ 'std_convert.base64_encode': 'pure',
316
+ 'std_convert.base64_decode': 'pure',
317
+
318
+ // ── std_time ──
319
+ 'std_time.now': 'time',
320
+ 'std_time.now_micros': 'time',
321
+ 'std_time.format_timestamp': 'time',
322
+ 'std_time.parse_timestamp': 'time',
323
+ 'std_time.duration_add': 'pure',
324
+ 'std_time.duration_subtract': 'pure',
325
+ 'std_time.year': 'time',
326
+ 'std_time.month': 'time',
327
+ 'std_time.day': 'time',
328
+ 'std_time.hour': 'time',
329
+ 'std_time.minute': 'time',
330
+ 'std_time.second': 'time',
331
+
332
+ // ── std_memory (all memory/unsafe) ──
333
+ 'std_memory.memory_alloc': 'memory',
334
+ 'std_memory.memory_free': 'memory',
335
+ 'std_memory.memory_realloc': 'memory',
336
+ 'std_memory.memory_read_i8': 'memory',
337
+ 'std_memory.memory_read_u8': 'memory',
338
+ 'std_memory.memory_read_i16': 'memory',
339
+ 'std_memory.memory_read_u16': 'memory',
340
+ 'std_memory.memory_read_i32': 'memory',
341
+ 'std_memory.memory_read_u32': 'memory',
342
+ 'std_memory.memory_read_i64': 'memory',
343
+ 'std_memory.memory_read_u64': 'memory',
344
+ 'std_memory.memory_read_f32': 'memory',
345
+ 'std_memory.memory_read_f64': 'memory',
346
+ 'std_memory.memory_write_i8': 'memory',
347
+ 'std_memory.memory_write_u8': 'memory',
348
+ 'std_memory.memory_write_i16': 'memory',
349
+ 'std_memory.memory_write_u16': 'memory',
350
+ 'std_memory.memory_write_i32': 'memory',
351
+ 'std_memory.memory_write_u32': 'memory',
352
+ 'std_memory.memory_write_i64': 'memory',
353
+ 'std_memory.memory_write_u64': 'memory',
354
+ 'std_memory.memory_write_f32': 'memory',
355
+ 'std_memory.memory_write_f64': 'memory',
356
+ 'std_memory.memory_copy': 'memory',
357
+ 'std_memory.memory_set': 'memory',
358
+ 'std_memory.memory_compare': 'memory',
359
+ 'std_memory.ptr_add': 'memory',
360
+ 'std_memory.ptr_sub': 'memory',
361
+ 'std_memory.ptr_diff': 'memory',
362
+ 'std_memory.stack_alloc': 'memory',
363
+ 'std_memory.stack_push_frame': 'memory',
364
+ 'std_memory.stack_pop_frame': 'memory',
365
+ 'std_memory.memory_sizeof': 'memory',
366
+ 'std_memory.address_of': 'memory',
367
+ 'std_memory.deref': 'memory',
368
+ 'std_memory.nullptr': 'memory',
369
+ 'std_memory.memory_heap_size': 'memory',
370
+ 'std_memory.memory_stack_size': 'memory',
371
+
372
+ // ── std_concurrency ──
373
+ 'std_concurrency.thread_spawn': 'concurrency',
374
+ 'std_concurrency.thread_join': 'concurrency',
375
+ 'std_concurrency.mutex_create': 'concurrency',
376
+ 'std_concurrency.mutex_lock': 'concurrency',
377
+ 'std_concurrency.mutex_unlock': 'concurrency',
378
+ 'std_concurrency.scoped_lock': 'concurrency',
379
+ 'std_concurrency.atomic_load': 'concurrency',
380
+ 'std_concurrency.atomic_store': 'concurrency',
381
+ 'std_concurrency.atomic_compare_exchange': 'concurrency',
382
+ };
package/src/index.ts ADDED
@@ -0,0 +1,245 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Ball CLI — command-line interface for the Ball programming language.
4
+ *
5
+ * Commands:
6
+ * ball run <program.ball.json> Execute a Ball program.
7
+ * ball audit <program.ball.json> Static capability analysis.
8
+ * ball --version Print version.
9
+ * ball --help Print usage.
10
+ */
11
+
12
+ import { readFileSync, writeFileSync } from 'node:fs';
13
+ import { fileURLToPath } from 'node:url';
14
+ import { dirname, join } from 'node:path';
15
+ import { BallEngine } from '@ball-lang/engine';
16
+ import {
17
+ analyzeCapabilities,
18
+ checkPolicy,
19
+ formatCapabilityReport,
20
+ type Program,
21
+ } from './capability_analyzer.ts';
22
+
23
+ const VERSION = readVersion();
24
+
25
+ const USAGE = `ball — the Ball language CLI (v${VERSION})
26
+
27
+ USAGE
28
+ ball <command> [options]
29
+
30
+ COMMANDS
31
+ run <program.ball.json> Execute a Ball program and print stdout.
32
+ audit <program.ball.json> Static capability analysis (I/O, fs, network, ...).
33
+
34
+ OPTIONS
35
+ -h, --help Print this help message.
36
+ -v, --version Print version information.
37
+
38
+ AUDIT OPTIONS
39
+ --output <path> Write the JSON report to <path>.
40
+ --deny <caps> Comma-separated capabilities to deny
41
+ (e.g. 'fs,network,process'). Exit 1 on violation.
42
+ --reachable-only Only analyze functions reachable from the entry.
43
+ --json Emit JSON report to stdout (instead of text).
44
+
45
+ EXAMPLES
46
+ ball run examples/hello_world/hello_world.ball.json
47
+ ball audit my_program.ball.json
48
+ ball audit my_program.ball.json --deny fs,network
49
+ ball audit my_program.ball.json --output report.json --json
50
+ `;
51
+
52
+ type ParsedArgs = {
53
+ command?: string;
54
+ positional: string[];
55
+ flags: Record<string, string | true>;
56
+ };
57
+
58
+ function parseArgs(argv: string[]): ParsedArgs {
59
+ const flags: Record<string, string | true> = {};
60
+ const positional: string[] = [];
61
+ let command: string | undefined;
62
+
63
+ for (let i = 0; i < argv.length; i++) {
64
+ const arg = argv[i]!;
65
+
66
+ if (arg === '--') {
67
+ for (let j = i + 1; j < argv.length; j++) positional.push(argv[j]!);
68
+ break;
69
+ }
70
+
71
+ if (arg.startsWith('--')) {
72
+ const eq = arg.indexOf('=');
73
+ if (eq >= 0) {
74
+ const key = arg.substring(2, eq);
75
+ flags[key] = arg.substring(eq + 1);
76
+ } else {
77
+ const key = arg.substring(2);
78
+ const next = argv[i + 1];
79
+ if (next !== undefined && !next.startsWith('-')) {
80
+ flags[key] = next;
81
+ i++;
82
+ } else {
83
+ flags[key] = true;
84
+ }
85
+ }
86
+ continue;
87
+ }
88
+
89
+ if (arg.startsWith('-') && arg.length > 1) {
90
+ const short = arg.substring(1);
91
+ flags[short] = true;
92
+ continue;
93
+ }
94
+
95
+ if (command === undefined) {
96
+ command = arg;
97
+ } else {
98
+ positional.push(arg);
99
+ }
100
+ }
101
+
102
+ return { command, positional, flags };
103
+ }
104
+
105
+ function readVersion(): string {
106
+ // package.json lives one directory up from either dist/ or src/.
107
+ try {
108
+ const here = dirname(fileURLToPath(import.meta.url));
109
+ const pkgPath = join(here, '..', 'package.json');
110
+ const pkg = JSON.parse(readFileSync(pkgPath, 'utf8')) as { version?: string };
111
+ if (typeof pkg.version === 'string') return pkg.version;
112
+ } catch {
113
+ // Fallthrough.
114
+ }
115
+ return '0.0.0';
116
+ }
117
+
118
+ function loadProgram(path: string): Program {
119
+ let raw: string;
120
+ try {
121
+ raw = readFileSync(path, 'utf8');
122
+ } catch (e) {
123
+ const err = e as NodeJS.ErrnoException;
124
+ if (err.code === 'ENOENT') {
125
+ fail(`File not found: ${path}`);
126
+ }
127
+ fail(`Could not read ${path}: ${err.message}`);
128
+ }
129
+ try {
130
+ return JSON.parse(raw) as Program;
131
+ } catch (e) {
132
+ const err = e as Error;
133
+ fail(`Invalid JSON in ${path}: ${err.message}`);
134
+ }
135
+ }
136
+
137
+ function fail(message: string): never {
138
+ process.stderr.write(`ball: ${message}\n`);
139
+ process.exit(1);
140
+ }
141
+
142
+ function cmdRun(args: ParsedArgs): number {
143
+ const programPath = args.positional[0];
144
+ if (!programPath) {
145
+ process.stderr.write('ball: run requires a program path\n\n');
146
+ process.stderr.write(USAGE);
147
+ return 1;
148
+ }
149
+
150
+ const program = loadProgram(programPath);
151
+ const engine = new BallEngine(program as any, {
152
+ stdout: (msg: string) => process.stdout.write(msg + '\n'),
153
+ stderr: (msg: string) => process.stderr.write(msg + '\n'),
154
+ });
155
+
156
+ try {
157
+ engine.run();
158
+ } catch (e) {
159
+ const err = e as Error;
160
+ fail(`runtime error: ${err.message}`);
161
+ }
162
+ return 0;
163
+ }
164
+
165
+ function cmdAudit(args: ParsedArgs): number {
166
+ const programPath = args.positional[0];
167
+ if (!programPath) {
168
+ process.stderr.write('ball: audit requires a program path\n\n');
169
+ process.stderr.write(USAGE);
170
+ return 1;
171
+ }
172
+
173
+ const program = loadProgram(programPath);
174
+ const reachableOnly = args.flags['reachable-only'] === true;
175
+ const report = analyzeCapabilities(program, { reachableOnly });
176
+
177
+ const denyRaw = args.flags['deny'];
178
+ const denySet =
179
+ typeof denyRaw === 'string'
180
+ ? new Set(
181
+ denyRaw
182
+ .split(',')
183
+ .map((c) => c.trim())
184
+ .filter((c) => c.length > 0),
185
+ )
186
+ : new Set<string>();
187
+
188
+ const outputPath = args.flags['output'];
189
+ if (typeof outputPath === 'string') {
190
+ try {
191
+ writeFileSync(outputPath, JSON.stringify(report, null, 2) + '\n');
192
+ } catch (e) {
193
+ const err = e as Error;
194
+ fail(`could not write ${outputPath}: ${err.message}`);
195
+ }
196
+ }
197
+
198
+ const asJson = args.flags['json'] === true;
199
+ if (asJson) {
200
+ process.stdout.write(JSON.stringify(report, null, 2) + '\n');
201
+ } else {
202
+ process.stdout.write(formatCapabilityReport(report));
203
+ }
204
+
205
+ if (denySet.size > 0) {
206
+ const violations = checkPolicy(report, denySet);
207
+ if (violations.length > 0) {
208
+ process.stderr.write('\nPolicy violations:\n');
209
+ for (const v of violations) process.stderr.write(` - ${v}\n`);
210
+ return 1;
211
+ }
212
+ }
213
+
214
+ return 0;
215
+ }
216
+
217
+ function main(argv: string[]): number {
218
+ const args = parseArgs(argv);
219
+
220
+ if (args.flags['help'] === true || args.flags['h'] === true) {
221
+ process.stdout.write(USAGE);
222
+ return 0;
223
+ }
224
+
225
+ if (args.flags['version'] === true || args.flags['v'] === true) {
226
+ process.stdout.write(`${VERSION}\n`);
227
+ return 0;
228
+ }
229
+
230
+ switch (args.command) {
231
+ case 'run':
232
+ return cmdRun(args);
233
+ case 'audit':
234
+ return cmdAudit(args);
235
+ case undefined:
236
+ process.stdout.write(USAGE);
237
+ return 0;
238
+ default:
239
+ process.stderr.write(`ball: unknown command '${args.command}'\n\n`);
240
+ process.stderr.write(USAGE);
241
+ return 1;
242
+ }
243
+ }
244
+
245
+ process.exit(main(process.argv.slice(2)));