cli-kiss 0.2.4 → 0.2.6

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.
@@ -5,6 +5,7 @@ export default defineConfig({
5
5
  title: "cli-kiss 💋",
6
6
  base: "/cli-kiss/",
7
7
  head: [
8
+ ["link", { rel: "icon", href: "/cli-kiss/favicon.ico" }],
8
9
  [
9
10
  "style",
10
11
  {},
@@ -27,8 +28,8 @@ export default defineConfig({
27
28
  { text: "Commands", link: "/guide/02_commands" },
28
29
  { text: "Options", link: "/guide/03_options" },
29
30
  { text: "Positionals", link: "/guide/04_positionals" },
30
- { text: "Types", link: "/guide/05_types" },
31
- { text: "Running your CLI", link: "/guide/06_run" },
31
+ { text: "Input Types", link: "/guide/05_input_types" },
32
+ { text: "Running your CLI", link: "/guide/06_run_as_cli" },
32
33
  ],
33
34
  },
34
35
  ],
@@ -1,4 +1,8 @@
1
1
  :root {
2
- --vp-home-hero-image-background-image: linear-gradient( -50deg, #ff003cff 0%, #00000000 50%, #459900ff 100% );
3
- --vp-home-hero-image-filter: blur(150px);
2
+ /*
3
+ --vp-home-hero-name-color: transparent;
4
+ --vp-home-hero-name-background: linear-gradient(-50deg, #ff003caa 30%, #459900aa 70%);
5
+ */
6
+ --vp-home-hero-image-background-image: linear-gradient( -50deg, #ff003c88 25%, #008732aa 60% );
7
+ --vp-home-hero-image-filter: blur(60px);
4
8
  }
@@ -8,10 +8,10 @@ npm install cli-kiss
8
8
 
9
9
  ## Your first CLI
10
10
 
11
- Minimal "greet" CLI with:
11
+ Example minimal "greet" CLI with:
12
12
 
13
- - a required `NAME` positional
14
- - optional `--loud` flag:
13
+ - a required `name` positional argument
14
+ - optional `--loud` boolean flag
15
15
 
16
16
  ```ts
17
17
  import {
@@ -20,7 +20,7 @@ import {
20
20
  optionFlag,
21
21
  positionalRequired,
22
22
  runAndExit,
23
- typeString,
23
+ type,
24
24
  } from "cli-kiss";
25
25
 
26
26
  const greetCommand = command(
@@ -32,20 +32,19 @@ const greetCommand = command(
32
32
  },
33
33
  positionals: [
34
34
  positionalRequired({
35
- type: typeString,
36
- label: "NAME",
37
- description: "The name to greet",
35
+ type: type("name"),
36
+ description: "The name of the person to greet",
38
37
  }),
39
38
  ],
40
39
  },
41
- async (_ctx, { options: { loud }, positionals: [name] }) => {
40
+ async (_context, { options: { loud }, positionals: [name] }) => {
42
41
  const message = `Hello, ${name}!`;
43
42
  console.log(loud ? message.toUpperCase() : message);
44
43
  },
45
44
  ),
46
45
  );
47
46
 
48
- await runAndExit("greet", process.argv.slice(2), undefined, greetCommand, {
47
+ await runAndExit("greet", process.argv.slice(2), {}, greetCommand, {
49
48
  buildVersion: "1.0.0",
50
49
  });
51
50
  ```
@@ -70,19 +69,19 @@ greet --loud Alice
70
69
  HELLO, ALICE!
71
70
  ```
72
71
 
73
- Help (built-in):
72
+ Help and color (built-in):
74
73
 
75
74
  ```sh
76
- greet --help
75
+ greet --help --color
77
76
  ```
78
77
 
79
78
  ```text
80
- Usage: greet <NAME>
79
+ Usage: greet <name>
81
80
 
82
81
  Greet someone
83
82
 
84
83
  Positionals:
85
- <NAME> The name to greet
84
+ <name> The name of the person to greet
86
85
 
87
86
  Options:
88
87
  --loud[=no] Print in uppercase
@@ -7,14 +7,12 @@ Three factory functions cover every use-case.
7
7
  No subcommands — directly runs an operation.
8
8
 
9
9
  ```ts
10
- import { command, operation, positionalRequired, typeString } from "cli-kiss";
11
-
12
10
  const greet = command(
13
11
  { description: "Greet a user" },
14
12
  operation(
15
13
  {
16
14
  options: {},
17
- positionals: [positionalRequired({ type: typeString, label: "NAME" })],
15
+ positionals: [positionalRequired({ type: type("name") })],
18
16
  },
19
17
  async function (_ctx, { positionals: [name] }) {
20
18
  console.log(`Hello, ${name}!`);
@@ -28,13 +26,6 @@ const greet = command(
28
26
  User must pick one of several sub-actions.
29
27
 
30
28
  ```ts
31
- import {
32
- command,
33
- commandWithSubcommands,
34
- operation,
35
- runAndExit,
36
- } from "cli-kiss";
37
-
38
29
  const rootCmd = commandWithSubcommands(
39
30
  { description: "My deployment CLI" },
40
31
  // This operation runs before the subcommand is selected.
@@ -68,7 +59,7 @@ deploy-cli --help
68
59
  ```
69
60
 
70
61
  ```text
71
- Usage: deploy-cli <SUBCOMMAND>
62
+ Usage: deploy-cli <subcommand>
72
63
 
73
64
  My deployment CLI
74
65
 
@@ -79,21 +70,13 @@ Subcommands:
79
70
 
80
71
  ### Subcommand names
81
72
 
82
- Keys are the tokens users type — must be lowercase strings.
73
+ Keys are the tokens users must type.
83
74
 
84
75
  ## `commandChained` — sequential stages
85
76
 
86
77
  Splits a command into reusable steps with no extra user-visible token.
87
78
 
88
79
  ```ts
89
- import {
90
- command,
91
- commandChained,
92
- operation,
93
- optionSingleValue,
94
- typeString,
95
- } from "cli-kiss";
96
-
97
80
  const authenticatedDeploy = commandChained(
98
81
  { description: "Authenticate then deploy" },
99
82
  // Stage 1: parse a --token option and forward the token as context
@@ -102,9 +85,9 @@ const authenticatedDeploy = commandChained(
102
85
  options: {
103
86
  token: optionSingleValue({
104
87
  long: "token",
105
- type: typeString,
88
+ type: type("secret"),
106
89
  description: "API token",
107
- default: function () {
90
+ defaultWhenNotDefined: function () {
108
91
  const t = process.env.API_TOKEN;
109
92
  if (!t) throw new Error("API_TOKEN env var is required");
110
93
  return t;
@@ -147,13 +130,13 @@ Each `Example` entry has:
147
130
 
148
131
  Each `CommandArg` is one of:
149
132
 
150
- | Shape | Renders as |
151
- | ----------------------------------------------- | ---------------- |
152
- | `string` | literal text |
153
- | `{ positional: string }` | positional label |
154
- | `{ subcommand: string }` | subcommand name |
155
- | `{ option: { long: string; value?: string } }` | `--long[=value]` |
156
- | `{ option: { short: string; value?: string } }` | `-s[=value]` |
133
+ | Shape | Renders as |
134
+ | ----------------------------------------------------------------------- | --------------------- |
135
+ | `string` | literal text |
136
+ | `{ positional: string }` | positional label |
137
+ | `{ subcommand: string }` | subcommand name |
138
+ | `{ option: { long: string; inlined?: string; separated?: string[] } }` | `--long[=val] [args]` |
139
+ | `{ option: { short: string; inlined?: string; separated?: string[] } }` | `-s[=val] [args]` |
157
140
 
158
141
  ```ts
159
142
  command(
@@ -5,11 +5,9 @@ Named `--` arguments (or `-` for short forms). Declared in the `options` map of
5
5
 
6
6
  ## `optionFlag` — boolean toggle
7
7
 
8
- Present or absent. Also accepts `--flag=true` / `--flag=no` / `--no-flag`.
8
+ Present or absent. Also accepts `--flag=true` / `--flag=no`.
9
9
 
10
10
  ```ts
11
- import { optionFlag } from "cli-kiss";
12
-
13
11
  const verbose = optionFlag({
14
12
  long: "verbose",
15
13
  short: "v",
@@ -23,7 +21,7 @@ const verbose = optionFlag({
23
21
 
24
22
  | Parameter | Type | Description |
25
23
  | ------------- | --------------------- | ------------------------------------ |
26
- | `long` | `Lowercase<string>` | Long flag name (without `--`) |
24
+ | `long` | `string` | Long flag name (without `--`) |
27
25
  | `short` | `string?` | Short flag name (without `-`) |
28
26
  | `description` | `string?` | Help text |
29
27
  | `hint` | `string?` | Short note in parentheses |
@@ -41,15 +39,12 @@ multiple values.
41
39
  Exactly one typed value.
42
40
 
43
41
  ```ts
44
- import { optionSingleValue, typeString } from "cli-kiss";
45
-
46
42
  const output = optionSingleValue({
47
43
  long: "output",
48
44
  short: "o",
49
- type: typeString,
50
- label: "PATH",
45
+ type: typePath(),
51
46
  description: "Output directory",
52
- default: () => "dist/",
47
+ defaultWhenNotDefined: () => "dist/",
53
48
  });
54
49
  // --output dist/ → "dist/"
55
50
  // --output=dist/ → "dist/"
@@ -57,29 +52,26 @@ const output = optionSingleValue({
57
52
  // (absent) → "dist/"
58
53
  ```
59
54
 
60
- | Parameter | Type | Description |
61
- | ------------- | --------------------- | ----------------------------------------------------------- |
62
- | `long` | `Lowercase<string>` | Long option name |
63
- | `short` | `string?` | Short option name |
64
- | `type` | `Type<Value>` | Decoder for the value |
65
- | `label` | `Uppercase<string>?` | Placeholder in help (defaults to uppercased type content) |
66
- | `description` | `string?` | Help text |
67
- | `hint` | `string?` | Short note in parentheses |
68
- | `default` | `() => Value` | Default when absent **throw** to make the option required |
69
- | `aliases` | `{ longs?, shorts? }` | Additional names |
55
+ | Parameter | Type | Description |
56
+ | ----------------------- | --------------------- | ---------------------------------------------------------------------------- |
57
+ | `long` | `string` | Long option name |
58
+ | `short` | `string?` | Short option name |
59
+ | `type` | `Type<Value>` | Decoder for the value |
60
+ | `description` | `string?` | Help text |
61
+ | `hint` | `string?` | Short note in parentheses |
62
+ | `defaultWhenNotDefined` | `() => Value` | Value when option is absent — **throw** to make it required |
63
+ | `defaultWhenNotInlined` | `() => Value?` | Value when option is present but has no inline value (e.g. `--output` alone) |
64
+ | `aliases` | `{ longs?, shorts? }` | Additional names |
70
65
 
71
66
  ## `optionRepeatable` — collect multiple values
72
67
 
73
68
  Collects every occurrence into an array.
74
69
 
75
70
  ```ts
76
- import { optionRepeatable, typeString } from "cli-kiss";
77
-
78
71
  const files = optionRepeatable({
79
72
  long: "file",
80
73
  short: "f",
81
- type: typeString,
82
- label: "PATH",
74
+ type: typePath("FILE_PATH"),
83
75
  description: "Input file (may be repeated)",
84
76
  });
85
77
  // --file a.ts --file b.ts → ["a.ts", "b.ts"]
@@ -88,10 +80,9 @@ const files = optionRepeatable({
88
80
 
89
81
  | Parameter | Type | Description |
90
82
  | ------------- | --------------------- | ---------------------------------- |
91
- | `long` | `Lowercase<string>` | Long option name |
83
+ | `long` | `string` | Long option name |
92
84
  | `short` | `string?` | Short option name |
93
85
  | `type` | `Type<Value>` | Decoder applied to each occurrence |
94
- | `label` | `Uppercase<string>?` | Placeholder in help |
95
86
  | `description` | `string?` | Help text |
96
87
  | `hint` | `string?` | Short note in parentheses |
97
88
  | `aliases` | `{ longs?, shorts? }` | Additional names |
@@ -1,69 +1,63 @@
1
1
  # Positionals
2
2
 
3
- Bare (non-`--`) arguments, consumed in order. Declared in the `positionals` array of [`operation`](/guide/02_commands).
3
+ Bare (non-`--`) arguments, consumed in order. Declared in the `positionals`
4
+ array of [`operation`](/guide/02_commands).
4
5
 
5
6
  ## `positionalRequired` — must be present
6
7
 
7
8
  Fails if missing.
8
9
 
9
10
  ```ts
10
- import { positionalRequired, typeString } from "cli-kiss";
11
-
12
11
  const name = positionalRequired({
13
- type: typeString,
14
- label: "NAME",
15
- description: "The name to greet",
12
+ type: type("person"),
13
+ description: "The name of the person to greet",
16
14
  });
17
- // my-cli Alice → "Alice"
18
- // my-cli Error: <NAME>: Is required, but was not provided
15
+ // Usage:
16
+ // my-cli Alice "Alice"
17
+ // my-cli → Error: <person>: Is required, but was not provided
19
18
  ```
20
19
 
21
- | Parameter | Type | Description |
22
- | ------------- | -------------------- | --------------------------------------------------------- |
23
- | `type` | `Type<Value>` | Decoder for the raw string token |
24
- | `label` | `Uppercase<string>?` | Placeholder in help (defaults to uppercased type content) |
25
- | `description` | `string?` | Help text |
26
- | `hint` | `string?` | Short note in parentheses |
20
+ | Parameter | Type | Description |
21
+ | ------------- | ------------- | -------------------------------- |
22
+ | `type` | `Type<Value>` | Decoder for the raw string token |
23
+ | `description` | `string?` | Help text |
24
+ | `hint` | `string?` | Short note in parentheses |
27
25
 
28
26
  ## `positionalOptional` — may be absent
29
27
 
30
28
  Falls back to a default when absent.
31
29
 
32
30
  ```ts
33
- import { positionalOptional, typeString } from "cli-kiss";
34
-
35
31
  const greeting = positionalOptional({
36
- type: typeString,
37
- label: "GREETING",
38
- description: "Custom greeting (default: Hello)",
32
+ type: type("greeting"),
33
+ description: "Custom greeting",
34
+ hint: "default to 'Hello'",
39
35
  default: () => "Hello",
40
36
  });
41
- // my-cli → "Hello"
42
- // my-cli Howdy → "Howdy"
37
+ // Usage:
38
+ // my-cli → "Hello"
39
+ //. my-cli Howdy → "Howdy"
43
40
  ```
44
41
 
45
- | Parameter | Type | Description |
46
- | ------------- | -------------------- | ------------------------------------------------- |
47
- | `type` | `Type<Value>` | Decoder for the raw string token |
48
- | `label` | `Uppercase<string>?` | Placeholder in help |
49
- | `description` | `string?` | Help text |
50
- | `hint` | `string?` | Short note in parentheses |
51
- | `default` | `() => Value` | Value when absent — **throw** to make it required |
42
+ | Parameter | Type | Description |
43
+ | ------------- | ------------- | ------------------------------------------------- |
44
+ | `type` | `Type<Value>` | Decoder for the raw string token |
45
+ | `description` | `string?` | Help text |
46
+ | `hint` | `string?` | Short note in parentheses |
47
+ | `default` | `() => Value` | Value when absent — **throw** to make it required |
52
48
 
53
49
  ## `positionalVariadics` — zero or more
54
50
 
55
51
  Consumes all remaining tokens into an array.
56
52
 
57
53
  ```ts
58
- import { positionalVariadics, typeString } from "cli-kiss";
59
-
60
54
  const files = positionalVariadics({
61
- type: typeString,
62
- label: "FILE",
55
+ type: typePath(),
63
56
  description: "Files to process",
64
57
  });
65
- // my-cli a.ts b.ts c.ts → ["a.ts", "b.ts", "c.ts"]
66
- // my-cli → []
58
+ // Usage:
59
+ // my-cli a.ts b.ts c.ts → ["a.ts", "b.ts", "c.ts"]
60
+ // my-cli → []
67
61
  ```
68
62
 
69
63
  ### End delimiter
@@ -72,21 +66,20 @@ Optionally stop collecting at a specific sentinel token:
72
66
 
73
67
  ```ts
74
68
  const args = positionalVariadics({
75
- type: typeString,
76
- label: "ARG",
69
+ type: type("argument"),
77
70
  endDelimiter: "STOP",
78
71
  description: "Arguments (end with STOP)",
79
72
  });
80
- // my-cli foo bar STOP → ["foo", "bar"]
73
+ // Usage:
74
+ // my-cli foo bar STOP → ["foo", "bar"]
81
75
  ```
82
76
 
83
- | Parameter | Type | Description |
84
- | -------------- | -------------------- | ------------------------------------ |
85
- | `type` | `Type<Value>` | Decoder applied to each token |
86
- | `label` | `Uppercase<string>?` | Placeholder in help |
87
- | `description` | `string?` | Help text |
88
- | `hint` | `string?` | Short note in parentheses |
89
- | `endDelimiter` | `string?` | Sentinel token that stops collection |
77
+ | Parameter | Type | Description |
78
+ | -------------- | ------------- | ------------------------------------ |
79
+ | `type` | `Type<Value>` | Decoder applied to each token |
80
+ | `description` | `string?` | Help text |
81
+ | `hint` | `string?` | Short note in parentheses |
82
+ | `endDelimiter` | `string?` | Sentinel token that stops collection |
90
83
 
91
84
  ## Ordering rules
92
85
 
@@ -97,20 +90,17 @@ operation(
97
90
  {
98
91
  options: {},
99
92
  positionals: [
100
- positionalRequired({ type: typeString, label: "SOURCE" }),
101
- positionalRequired({ type: typeString, label: "DEST" }),
102
- positionalOptional({
103
- type: typeString,
104
- label: "TAG",
105
- default: () => "latest",
106
- }),
107
- positionalVariadics({ type: typeString, label: "EXTRA" }),
93
+ positionalRequired({ type: type("src") }),
94
+ positionalRequired({ type: type("dst") }),
95
+ positionalOptional({ type: type("tag"), default: () => "latest" }),
96
+ positionalVariadics({ type: type("extra") }),
108
97
  ],
109
98
  },
110
- async (_ctx, { positionals: [source, dest, tag, extras] }) => {
99
+ async function (_ctx, { positionals: [src, dst, tag, extras] }) {
111
100
  /* ... */
112
101
  },
113
102
  );
114
- // my-cli src/ dst/ → source="src/", dest="dst/", tag="latest", extras=[]
115
- // my-cli src/ dst/ v2 a b c source="src/", dest="dst/", tag="v2", extras=["a","b","c"]
103
+ // Usage:
104
+ // my-cli in out src="in", src="out", tag="latest", extras=[]
105
+ // my-cli in out v2 a b c → src="in", src="out", tag="v2", extras=["a","b","c"]
116
106
  ```
@@ -0,0 +1,134 @@
1
+ # Input Types
2
+
3
+ A `Type<Value>` converts a raw CLI string into a typed value:
4
+
5
+ - Contains a `content` label about the type of data being decoded
6
+ - Paired with a `decoder` function that throws if the value is invalid.
7
+
8
+ A `Type<Value>` can then be used as a value for an `Option` or `Positional`
9
+
10
+ ## Built-in types
11
+
12
+ All type factories accept an optional `name` parameter that overrides the label
13
+ shown in help/errors.
14
+
15
+ | Type factory | Content type | Accepts |
16
+ | -------------- | ------------ | ------------------------------------------------------------------- |
17
+ | `type` | `string` | Any string |
18
+ | `typeBoolean` | `boolean` | `true/yes/on/y` → true, `false/no/off/n` → false (case-insensitive) |
19
+ | `typeNumber` | `number` | Integers, floats, scientific notation |
20
+ | `typeInteger` | `bigint` | Integer strings only |
21
+ | `typeDatetime` | `Date` | Any format accepted by `Date.parse` (ISO 8601 recommended) |
22
+ | `typeUrl` | `URL` | Absolute URLs |
23
+ | `typePath` | `string` | Non-empty path strings; optional sync existence check |
24
+
25
+ ```ts
26
+ type("greeting").decoder("hello"); // → "hello"
27
+ typeBoolean("flag").decoder("yes"); // → true
28
+ typeNumber("pi").decoder("3.14"); // → 3.14
29
+ typeInteger("id").decoder("9007199254740993"); // → 9007199254740993n
30
+ typeDatetime("birthday").decoder("2024-01-15"); // → Date object
31
+ typeUrl("redirect").decoder("https://example.com/path"); // → URL object
32
+ typePath().decoder("/usr/bin"); // → "/usr/bin"
33
+ ```
34
+
35
+ `typePath` also accepts a second argument for existence checks:
36
+
37
+ ```ts
38
+ typePath("config", { checkSyncExistAs: "file" }); // throws if not a file
39
+ typePath("dir", { checkSyncExistAs: "directory" }); // throws if not a directory
40
+ ```
41
+
42
+ ## `typeChoice` — string enum
43
+
44
+ Accepts only a fixed set of strings (case-insensitive by default):
45
+
46
+ ```ts
47
+ const typeEnv = typeChoice("environment", ["dev", "staging", "prod"]);
48
+ typeEnv.decoder("prod"); // → "prod"
49
+ typeEnv.decoder("PROD"); // → "prod" (case-insensitive)
50
+ typeEnv.decoder("unknown");
51
+ // Error: Invalid value: "unknown" (expected one of: "dev" | "staging" | "prod")
52
+ ```
53
+
54
+ Pass `true` as third argument to make matching case-sensitive.
55
+
56
+ ## `typeTuple` — fixed-length delimited value
57
+
58
+ Splits a string into a fixed-length typed tuple:
59
+
60
+ ```ts
61
+ const typePoint = typeTuple([typeNumber(), typeNumber()]);
62
+ typePoint.decoder("3.14,2.71"); // → [3.14, 2.71]
63
+ typePoint.decoder("x,2"); // → Error: at 0: Number: Unable to parse: "x"
64
+ ```
65
+
66
+ The default separator is `","`. Pass a second argument to change it:
67
+
68
+ ```ts
69
+ typeTuple([type("name"), typeNumber()], ":");
70
+ // "foo:42" → ["foo", 42]
71
+ ```
72
+
73
+ ## `typeList` — variable-length delimited value
74
+
75
+ Splits a string into an array of typed values:
76
+
77
+ ```ts
78
+ const typeNumbers = typeList(typeNumber());
79
+ typeNumbers.decoder("1,2,3"); // → [1, 2, 3]
80
+ typeNumbers.decoder("1,x,3"); // → Error: at 1: Number: Unable to parse: "x"
81
+ ```
82
+
83
+ Custom separator:
84
+
85
+ ```ts
86
+ const typePaths = typeList(typePath(), ":");
87
+ typePaths.decoder("/usr/bin:/usr/local/bin"); // → ["/usr/bin", "/usr/local/bin"]
88
+ ```
89
+
90
+ ::: tip Prefer
91
+ [`optionRepeatable`](/guide/03_options#optionrepeatable-collect-multiple-values)
92
+ over `typeList` when users should pass multiple values as separate flags
93
+ (`--file a --file b` rather than `--files a,b`).
94
+
95
+ :::
96
+
97
+ ## `typeConverted` — transform decoded value
98
+
99
+ Chains a base type with a transformation function:
100
+
101
+ ```ts
102
+ const typePort = typeConverted("port", typeNumber(), (n) => {
103
+ if (n < 1 || n > 65535) throw new Error("Out of range");
104
+ return n;
105
+ });
106
+ // "--port 8080" → 8080
107
+ // "--port 99999" → throws
108
+ ```
109
+
110
+ ## `typeRenamed` — rename a type
111
+
112
+ Wraps a type with a different label for clearer errors:
113
+
114
+ ```ts
115
+ const typeUserId = typeRenamed(typeInteger("u64"), "user-id");
116
+ ```
117
+
118
+ ## Custom types
119
+
120
+ Implement the `Type<Value>` interface directly:
121
+
122
+ ```ts
123
+ const typeHexColor: Type<string> = {
124
+ content: "hex-color",
125
+ decoder(value) {
126
+ if (/^#[0-9a-fA-F]{6}$/.test(value)) {
127
+ return value;
128
+ }
129
+ throw new Error(`Not a valid hex color: "${value}"`);
130
+ },
131
+ };
132
+ // "#ff0000" → "#ff0000"
133
+ // "red" → Error: HexColor: Not a valid value: "red"
134
+ ```