@sectui/operations 0.0.0-stage → 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.
Files changed (147) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -2
  3. package/dist/args.d.ts +8 -0
  4. package/dist/args.js +47 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/cli-flags.d.ts +7 -0
  7. package/dist/cli-flags.js +28 -0
  8. package/dist/cli-flags.js.map +1 -0
  9. package/dist/cli.d.ts +2 -0
  10. package/dist/cli.js +185 -0
  11. package/dist/cli.js.map +1 -0
  12. package/dist/completions.d.ts +3 -0
  13. package/dist/completions.js +113 -0
  14. package/dist/completions.js.map +1 -0
  15. package/dist/display.d.ts +4 -0
  16. package/dist/display.js +29 -0
  17. package/dist/display.js.map +1 -0
  18. package/dist/errors.d.ts +6 -0
  19. package/dist/errors.js +7 -0
  20. package/dist/errors.js.map +1 -0
  21. package/dist/index.d.ts +4 -0
  22. package/dist/index.js +5 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/operation.d.ts +64 -0
  25. package/dist/operation.js +5 -0
  26. package/dist/operation.js.map +1 -0
  27. package/dist/operations/base/alphabets.d.ts +28 -0
  28. package/dist/operations/base/alphabets.js +27 -0
  29. package/dist/operations/base/alphabets.js.map +1 -0
  30. package/dist/operations/base/base32.d.ts +33 -0
  31. package/dist/operations/base/base32.js +85 -0
  32. package/dist/operations/base/base32.js.map +1 -0
  33. package/dist/operations/base/base58.d.ts +34 -0
  34. package/dist/operations/base/base58.js +43 -0
  35. package/dist/operations/base/base58.js.map +1 -0
  36. package/dist/operations/base/base62.d.ts +28 -0
  37. package/dist/operations/base/base62.js +43 -0
  38. package/dist/operations/base/base62.js.map +1 -0
  39. package/dist/operations/base/base64.d.ts +19 -0
  40. package/dist/operations/base/base64.js +72 -0
  41. package/dist/operations/base/base64.js.map +1 -0
  42. package/dist/operations/base/base64url.d.ts +15 -0
  43. package/dist/operations/base/base64url.js +44 -0
  44. package/dist/operations/base/base64url.js.map +1 -0
  45. package/dist/operations/base/big-number.d.ts +6 -0
  46. package/dist/operations/base/big-number.js +54 -0
  47. package/dist/operations/base/big-number.js.map +1 -0
  48. package/dist/operations/color/hex-to-rgb.d.ts +14 -0
  49. package/dist/operations/color/hex-to-rgb.js +41 -0
  50. package/dist/operations/color/hex-to-rgb.js.map +1 -0
  51. package/dist/operations/data/json-format.d.ts +20 -0
  52. package/dist/operations/data/json-format.js +33 -0
  53. package/dist/operations/data/json-format.js.map +1 -0
  54. package/dist/operations/data/json.d.ts +20 -0
  55. package/dist/operations/data/json.js +59 -0
  56. package/dist/operations/data/json.js.map +1 -0
  57. package/dist/operations/data/yaml.d.ts +21 -0
  58. package/dist/operations/data/yaml.js +54 -0
  59. package/dist/operations/data/yaml.js.map +1 -0
  60. package/dist/operations/extract/extract.d.ts +55 -0
  61. package/dist/operations/extract/extract.js +129 -0
  62. package/dist/operations/extract/extract.js.map +1 -0
  63. package/dist/operations/hash/bcrypt.d.ts +16 -0
  64. package/dist/operations/hash/bcrypt.js +45 -0
  65. package/dist/operations/hash/bcrypt.js.map +1 -0
  66. package/dist/operations/hash/hashes.d.ts +10 -0
  67. package/dist/operations/hash/hashes.js +29 -0
  68. package/dist/operations/hash/hashes.js.map +1 -0
  69. package/dist/operations/hex/from-hex.d.ts +1 -0
  70. package/dist/operations/hex/from-hex.js +31 -0
  71. package/dist/operations/hex/from-hex.js.map +1 -0
  72. package/dist/operations/hex/to-hex.d.ts +31 -0
  73. package/dist/operations/hex/to-hex.js +44 -0
  74. package/dist/operations/hex/to-hex.js.map +1 -0
  75. package/dist/operations/html/entities.d.ts +2 -0
  76. package/dist/operations/html/entities.js +69 -0
  77. package/dist/operations/html/entities.js.map +1 -0
  78. package/dist/operations/html/html-decode.d.ts +1 -0
  79. package/dist/operations/html/html-decode.js +36 -0
  80. package/dist/operations/html/html-decode.js.map +1 -0
  81. package/dist/operations/html/html-encode.d.ts +8 -0
  82. package/dist/operations/html/html-encode.js +30 -0
  83. package/dist/operations/html/html-encode.js.map +1 -0
  84. package/dist/operations/text/case.d.ts +14 -0
  85. package/dist/operations/text/case.js +85 -0
  86. package/dist/operations/text/case.js.map +1 -0
  87. package/dist/operations/text/count.d.ts +19 -0
  88. package/dist/operations/text/count.js +59 -0
  89. package/dist/operations/text/count.js.map +1 -0
  90. package/dist/operations/text/line-operations.d.ts +48 -0
  91. package/dist/operations/text/line-operations.js +134 -0
  92. package/dist/operations/text/line-operations.js.map +1 -0
  93. package/dist/operations/text/lines.d.ts +13 -0
  94. package/dist/operations/text/lines.js +22 -0
  95. package/dist/operations/text/lines.js.map +1 -0
  96. package/dist/operations/text/text-operations.d.ts +37 -0
  97. package/dist/operations/text/text-operations.js +79 -0
  98. package/dist/operations/text/text-operations.js.map +1 -0
  99. package/dist/operations/url/url-decode.d.ts +8 -0
  100. package/dist/operations/url/url-decode.js +56 -0
  101. package/dist/operations/url/url-decode.js.map +1 -0
  102. package/dist/operations/url/url-encode.d.ts +8 -0
  103. package/dist/operations/url/url-encode.js +37 -0
  104. package/dist/operations/url/url-encode.js.map +1 -0
  105. package/dist/registry.d.ts +395 -0
  106. package/dist/registry.js +131 -0
  107. package/dist/registry.js.map +1 -0
  108. package/dist/template.d.ts +23 -0
  109. package/dist/template.js +198 -0
  110. package/dist/template.js.map +1 -0
  111. package/package.json +67 -4
  112. package/src/args.ts +47 -0
  113. package/src/cli-flags.ts +29 -0
  114. package/src/cli.ts +189 -0
  115. package/src/completions.ts +129 -0
  116. package/src/display.ts +29 -0
  117. package/src/errors.ts +6 -0
  118. package/src/index.ts +4 -0
  119. package/src/operation.ts +62 -0
  120. package/src/operations/base/alphabets.ts +33 -0
  121. package/src/operations/base/base32.ts +84 -0
  122. package/src/operations/base/base58.ts +45 -0
  123. package/src/operations/base/base62.ts +44 -0
  124. package/src/operations/base/base64.ts +76 -0
  125. package/src/operations/base/base64url.ts +45 -0
  126. package/src/operations/base/big-number.ts +54 -0
  127. package/src/operations/color/hex-to-rgb.ts +42 -0
  128. package/src/operations/data/json-format.ts +33 -0
  129. package/src/operations/data/json.ts +53 -0
  130. package/src/operations/data/yaml.ts +56 -0
  131. package/src/operations/extract/extract.ts +135 -0
  132. package/src/operations/hash/bcrypt.ts +44 -0
  133. package/src/operations/hash/hashes.ts +64 -0
  134. package/src/operations/hex/from-hex.ts +31 -0
  135. package/src/operations/hex/to-hex.ts +46 -0
  136. package/src/operations/html/entities.ts +71 -0
  137. package/src/operations/html/html-decode.ts +36 -0
  138. package/src/operations/html/html-encode.ts +31 -0
  139. package/src/operations/text/case.ts +140 -0
  140. package/src/operations/text/count.ts +61 -0
  141. package/src/operations/text/line-operations.ts +144 -0
  142. package/src/operations/text/lines.ts +22 -0
  143. package/src/operations/text/text-operations.ts +85 -0
  144. package/src/operations/url/url-decode.ts +55 -0
  145. package/src/operations/url/url-encode.ts +38 -0
  146. package/src/registry.ts +152 -0
  147. package/src/template.ts +214 -0
@@ -0,0 +1,198 @@
1
+ import { coerceArg, findArg, kebab } from "./args.js";
2
+ import { getOperation, isOperationId, run } from "./registry.js";
3
+ export class TemplateError extends Error {
4
+ name = "TemplateError";
5
+ /** 1-based line and column of `position`. */
6
+ line;
7
+ column;
8
+ /** Offset in the source. */
9
+ position;
10
+ constructor(message, source, position) {
11
+ const before = source.slice(0, position);
12
+ const line = before.split("\n").length;
13
+ const column = position - before.lastIndexOf("\n");
14
+ super(`${message} (line ${line}, column ${column})`);
15
+ this.position = position;
16
+ this.line = line;
17
+ this.column = column;
18
+ }
19
+ }
20
+ const OP_START = /([a-z0-9]+(?:-[a-z0-9]+)*)([{(])/y;
21
+ const IDENT_CHAR = /[a-z0-9-]/;
22
+ const ARG_NAME = /[a-z0-9]+(?:-[a-z0-9]+)*/y;
23
+ const BARE_VALUE = /[A-Za-z0-9\-_.+/=]+/y;
24
+ const SPACE = /\s/;
25
+ /** Index of the `)` closing an argument list opened before `from`, ignoring quoted parts; -1 if none. */
26
+ function findArgsEnd(source, from) {
27
+ let quote = null;
28
+ for (let j = from; j < source.length; j++) {
29
+ const c = source[j];
30
+ if (quote) {
31
+ if (c === "\\")
32
+ j++;
33
+ else if (c === quote)
34
+ quote = null;
35
+ }
36
+ else if (c === '"' || c === "'") {
37
+ quote = c;
38
+ }
39
+ else if (c === ")") {
40
+ return j;
41
+ }
42
+ }
43
+ return -1;
44
+ }
45
+ function parseArgList(id, source, from, end) {
46
+ const op = getOperation(id);
47
+ const args = {};
48
+ let j = from;
49
+ const skipSpaces = () => {
50
+ while (j < end && SPACE.test(source[j]))
51
+ j++;
52
+ };
53
+ const fail = (message, at = j) => {
54
+ throw new TemplateError(message, source, at);
55
+ };
56
+ const parseValue = () => {
57
+ const quote = source[j];
58
+ if (quote === '"' || quote === "'") {
59
+ let value = "";
60
+ for (j++; source[j] !== quote; j++) {
61
+ if (j >= end)
62
+ fail("Unterminated quote");
63
+ if (source[j] === "\\" && (source[j + 1] === quote || source[j + 1] === "\\"))
64
+ j++;
65
+ value += source[j];
66
+ }
67
+ j++;
68
+ return value;
69
+ }
70
+ BARE_VALUE.lastIndex = j;
71
+ const match = BARE_VALUE.exec(source);
72
+ if (!match || BARE_VALUE.lastIndex > end)
73
+ return fail("Expected a value (use quotes for special characters)");
74
+ j = BARE_VALUE.lastIndex;
75
+ return match[0];
76
+ };
77
+ skipSpaces();
78
+ while (j < end) {
79
+ const nameStart = j;
80
+ ARG_NAME.lastIndex = j;
81
+ const name = ARG_NAME.exec(source)?.[0];
82
+ if (!name || ARG_NAME.lastIndex > end)
83
+ fail("Expected an argument name");
84
+ j = ARG_NAME.lastIndex;
85
+ const arg = findArg(op, name);
86
+ if (!arg) {
87
+ const expected = Object.keys(op.args).map(kebab).join(", ") || "none";
88
+ fail(`Unknown argument "${name}" for "${id}" (expected: ${expected})`, nameStart);
89
+ }
90
+ const [key, def] = arg;
91
+ if (Object.hasOwn(args, key))
92
+ fail(`Duplicate argument "${name}"`, nameStart);
93
+ skipSpaces();
94
+ let raw = true;
95
+ if (source[j] === "=") {
96
+ j++;
97
+ skipSpaces();
98
+ raw = parseValue();
99
+ }
100
+ skipSpaces();
101
+ if (j < end && source[j] !== ",")
102
+ fail(`Unexpected "${source[j]}" (use quotes for special characters)`);
103
+ try {
104
+ args[key] = coerceArg(def, name, raw);
105
+ }
106
+ catch (error) {
107
+ fail(error.message, nameStart);
108
+ }
109
+ if (j < end) {
110
+ j++;
111
+ skipSpaces();
112
+ if (j >= end)
113
+ fail("Expected an argument name");
114
+ }
115
+ }
116
+ return args;
117
+ }
118
+ export function parse(source) {
119
+ let i = 0;
120
+ function parseNodes(open) {
121
+ const nodes = [];
122
+ let text = "";
123
+ const flush = () => {
124
+ if (text)
125
+ nodes.push({ type: "text", value: text });
126
+ text = "";
127
+ };
128
+ while (i < source.length) {
129
+ const ch = source[i];
130
+ if (ch === "\\" && i + 1 < source.length && "{}\\".includes(source[i + 1])) {
131
+ text += source[i + 1];
132
+ i += 2;
133
+ continue;
134
+ }
135
+ if (ch === "}" && open) {
136
+ i++;
137
+ flush();
138
+ return nodes;
139
+ }
140
+ if (i === 0 || !IDENT_CHAR.test(source[i - 1])) {
141
+ OP_START.lastIndex = i;
142
+ const match = OP_START.exec(source);
143
+ const id = match?.[1];
144
+ if (id && isOperationId(id)) {
145
+ let args = {};
146
+ let contentStart = OP_START.lastIndex;
147
+ const argsEnd = match[2] === "(" ? findArgsEnd(source, contentStart) : -1;
148
+ const isBlock = match[2] === "{" || (argsEnd >= 0 && source[argsEnd + 1] === "{");
149
+ if (isBlock) {
150
+ if (argsEnd >= 0) {
151
+ args = parseArgList(id, source, contentStart, argsEnd);
152
+ contentStart = argsEnd + 2;
153
+ }
154
+ flush();
155
+ const start = i;
156
+ i = contentStart;
157
+ nodes.push({ type: "op", id, args, children: parseNodes({ start }), start });
158
+ continue;
159
+ }
160
+ }
161
+ }
162
+ text += ch;
163
+ i++;
164
+ }
165
+ if (open)
166
+ throw new TemplateError("Missing closing brace", source, open.start);
167
+ flush();
168
+ return nodes;
169
+ }
170
+ return parseNodes(null);
171
+ }
172
+ function evaluate(nodes, source) {
173
+ const parts = nodes.map((node) => {
174
+ if (node.type === "text")
175
+ return new TextEncoder().encode(node.value);
176
+ const input = evaluate(node.children, source);
177
+ try {
178
+ return run(node.id, input, node.args);
179
+ }
180
+ catch (error) {
181
+ throw new TemplateError(`${node.id}: ${error.message}`, source, node.start);
182
+ }
183
+ });
184
+ const out = new Uint8Array(parts.reduce((n, p) => n + p.length, 0));
185
+ let offset = 0;
186
+ for (const part of parts) {
187
+ out.set(part, offset);
188
+ offset += part.length;
189
+ }
190
+ return out;
191
+ }
192
+ export function renderBytes(source) {
193
+ return evaluate(parse(source), source);
194
+ }
195
+ export function render(source) {
196
+ return new TextDecoder().decode(renderBytes(source));
197
+ }
198
+ //# sourceMappingURL=template.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../src/template.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,69 @@
1
1
  {
2
2
  "name": "@sectui/operations",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "CyberChef-like data transformation operations: a TypeScript library and the ops CLI",
5
+ "license": "MIT",
6
+ "author": "Hadi",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/sectui/operations.git"
10
+ },
11
+ "homepage": "https://github.com/sectui/operations#readme",
12
+ "bugs": {
13
+ "url": "https://github.com/sectui/operations/issues"
14
+ },
15
+ "keywords": [
16
+ "cyberchef",
17
+ "encoding",
18
+ "base64",
19
+ "security",
20
+ "cli"
21
+ ],
22
+ "type": "module",
23
+ "exports": {
24
+ ".": {
25
+ "source": "./src/index.ts",
26
+ "types": "./dist/index.d.ts",
27
+ "default": "./dist/index.js"
28
+ },
29
+ "./template": {
30
+ "source": "./src/template.ts",
31
+ "types": "./dist/template.d.ts",
32
+ "default": "./dist/template.js"
33
+ }
34
+ },
35
+ "bin": {
36
+ "ops": "./dist/cli.js"
37
+ },
38
+ "engines": {
39
+ "node": ">=26"
40
+ },
41
+ "scripts": {
42
+ "build": "tsc -p tsconfig.build.json",
43
+ "bundle": "node scripts/bundle.ts",
44
+ "test": "node --conditions=source --test 'src/**/*.test.ts'",
45
+ "typecheck": "tsc",
46
+ "docs": "node scripts/gen-docs.ts",
47
+ "format": "prettier --write .",
48
+ "format:check": "prettier --check .",
49
+ "prepack": "npm run build"
50
+ },
51
+ "devDependencies": {
52
+ "@types/node": "^24.0.0",
53
+ "esbuild": "^0.28.2",
54
+ "prettier": "3.8.3",
55
+ "typescript": "^7"
56
+ },
57
+ "files": [
58
+ "dist",
59
+ "!dist/ops.mjs",
60
+ "src",
61
+ "!src/**/*.test.ts",
62
+ "!src/test",
63
+ "!dist/**/*.test.*"
64
+ ],
65
+ "dependencies": {
66
+ "bcryptjs": "^3.0.3",
67
+ "yaml": "^2.9.1"
68
+ }
69
+ }
package/src/args.ts ADDED
@@ -0,0 +1,47 @@
1
+ import type { ArgDef, Operation } from "./operation.ts";
2
+
3
+ export const kebab = (key: string) => key.replace(/[A-Z]/g, (c) => `-${c.toLowerCase()}`);
4
+
5
+ /** Finds an argument by its kebab-case name, as used by the CLI and templates. */
6
+ export function findArg(op: Operation, name: string): [key: string, def: ArgDef] | undefined {
7
+ return Object.entries(op.args).find(([key]) => kebab(key) === name);
8
+ }
9
+
10
+ /** Throws a readable error if `value` does not fit the argument definition. */
11
+ export function checkArg(def: ArgDef, name: string, value: unknown): void {
12
+ switch (def.type) {
13
+ case "boolean":
14
+ if (typeof value !== "boolean") throw new Error(`"${name}" expects true or false, got ${JSON.stringify(value)}`);
15
+ return;
16
+ case "string":
17
+ if (typeof value !== "string") throw new Error(`"${name}" expects a string, got ${JSON.stringify(value)}`);
18
+ return;
19
+ case "number":
20
+ if (typeof value !== "number" || Number.isNaN(value)) {
21
+ throw new Error(`"${name}" expects a number, got ${JSON.stringify(value)}`);
22
+ }
23
+ if ((def.min !== undefined && value < def.min) || (def.max !== undefined && value > def.max)) {
24
+ throw new Error(`"${name}" must be between ${def.min ?? "-∞"} and ${def.max ?? "∞"}, got ${value}`);
25
+ }
26
+ return;
27
+ case "select":
28
+ if (!def.options.some((o) => o.value === value)) {
29
+ const expected = def.options.map((o) => o.value).join(", ");
30
+ throw new Error(`Invalid value ${JSON.stringify(value)} for "${name}" (expected: ${expected})`);
31
+ }
32
+ }
33
+ }
34
+
35
+ /** Converts a raw CLI or template value to the argument's type, then checks it. */
36
+ export function coerceArg(def: ArgDef, name: string, raw: string | boolean): unknown {
37
+ let value: unknown = raw;
38
+ if (typeof raw === "string") {
39
+ if (def.type === "boolean" && (raw === "true" || raw === "false")) value = raw === "true";
40
+ if (def.type === "number" && raw.trim() !== "") value = Number(raw);
41
+ }
42
+ if (def.type === "number" && typeof value !== "number") {
43
+ throw new Error(`"${name}" expects a number, got ${JSON.stringify(raw)}`);
44
+ }
45
+ checkArg(def, name, value);
46
+ return value;
47
+ }
@@ -0,0 +1,29 @@
1
+ import { kebab } from "./args.ts";
2
+ import type { ArgDef, Example, Operation } from "./operation.ts";
3
+
4
+ /** The flag users type: booleans that default to true are toggled with `--no-*`. */
5
+ export function cliFlag(key: string, def: ArgDef): string {
6
+ return def.type === "boolean" && def.default ? `--no-${kebab(key)}` : `--${kebab(key)}`;
7
+ }
8
+
9
+ /** Quotes a value for a POSIX shell, using $'...' when it contains control characters. */
10
+ export function shellQuote(value: string): string {
11
+ if (/^[\w\-.,/=+:@%]+$/.test(value)) return value;
12
+ if (!/[\x00-\x1f\x7f]/.test(value)) return `'${value.replaceAll("'", `'\\''`)}'`;
13
+ const escaped = value.replace(/[\\'\x00-\x1f\x7f]/g, (c) =>
14
+ c === "\n" ? "\\n" : c === "\t" ? "\\t" : c === "\\" || c === "'" ? `\\${c}` : `\\x${c.charCodeAt(0).toString(16).padStart(2, "0")}`,
15
+ );
16
+ return `$'${escaped}'`;
17
+ }
18
+
19
+ /** The CLI invocation of an example, e.g. `ops to-base64 --no-padding hi`. */
20
+ export function cliExample(op: Operation, example: Example): string {
21
+ const parts = ["ops", op.id];
22
+ for (const [key, value] of Object.entries(example.args ?? {})) {
23
+ const def = op.args[key]!;
24
+ if (def.type !== "boolean") parts.push(`--${kebab(key)}`, shellQuote(String(value)));
25
+ else if (value !== def.default) parts.push(cliFlag(key, def));
26
+ }
27
+ parts.push(shellQuote(example.input));
28
+ return parts.join(" ");
29
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,189 @@
1
+ #!/usr/bin/env node
2
+ import { readFileSync } from "node:fs";
3
+ import { buffer } from "node:stream/consumers";
4
+ import { parseArgs, type ParseArgsOptionsConfig } from "node:util";
5
+ import { coerceArg, kebab } from "./args.ts";
6
+ import { cliExample, cliFlag } from "./cli-flags.ts";
7
+ import { completions, SHELLS, type Shell } from "./completions.ts";
8
+ import { hexdump, isPrintable } from "./display.ts";
9
+ import { OperationError } from "./errors.ts";
10
+ import type { ArgDef, Operation } from "./operation.ts";
11
+ import { getOperation, isOperationId, operations, run } from "./registry.ts";
12
+ import { renderBytes } from "./template.ts";
13
+
14
+ type Package = { version: string; description: string };
15
+ /** Inlined by scripts/bundle.ts, undefined everywhere else. */
16
+ declare const BUNDLED_PACKAGE: Package | undefined;
17
+
18
+ // Read at run time: the same relative path works from src/ and from the built dist/.
19
+ const pkg: Package =
20
+ typeof BUNDLED_PACKAGE !== "undefined"
21
+ ? BUNDLED_PACKAGE
22
+ : (JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8")) as Package);
23
+
24
+ class UsageError extends Error {}
25
+
26
+ const USAGE = `ops ${pkg.version} - ${pkg.description}
27
+
28
+ Usage:
29
+ ops <operation> [options] [input] Apply an operation (input from stdin if omitted)
30
+ ops render [text] Replace operation{...} blocks in text
31
+ ops list List available operations
32
+ ops help [operation] Show help
33
+ ops completions <${SHELLS.join("|")}> Print a shell completion script
34
+
35
+ Global options:
36
+ --raw Write binary output to the terminal as is (by default it is shown as a hexdump)
37
+ --help Show help
38
+
39
+ Examples:
40
+ ops to-base64 hello
41
+ echo -n aGVsbG8= | ops from-base64
42
+ cat token.txt | ops from-base64 | ops url-encode --encode-all
43
+ ops render "hello my name is to-base64{hadi}"`;
44
+
45
+ const RENDER_HELP = `ops render [--raw] [text]
46
+
47
+ Replaces every operation{...} block in the text (or stdin) with the result of the operation.
48
+ Arguments go between parentheses: to-hex(delimiter=colon){hadi}
49
+
50
+ Example:
51
+ ops render "hello my name is to-base64{hadi}"`;
52
+
53
+ function describeArg(key: string, def: ArgDef): string {
54
+ const value = def.type === "select" ? ` <${def.options.map((o) => o.value).join("|")}>` : def.type === "boolean" ? "" : ` <${def.type}>`;
55
+ const details = [def.name, def.description, def.type === "boolean" ? undefined : `default: ${def.default}`].filter(Boolean).join(". ");
56
+ return ` ${cliFlag(key, def)}${value}\n ${details}`;
57
+ }
58
+
59
+ function operationHelp(op: Operation): string {
60
+ const lines = [`${op.id} - ${op.name}`, "", op.description];
61
+ if (op.aliases?.length) lines.push("", `Aliases: ${op.aliases.join(", ")}`);
62
+ if (op.inverse) lines.push(`Inverse: ${op.inverse}`);
63
+ const args = Object.entries(op.args);
64
+ if (args.length) lines.push("", "Options:", ...args.map(([key, def]) => describeArg(key, def)));
65
+ lines.push("", "Examples:", ...op.examples.map((ex) => ` ${cliExample(op, ex)}\n -> ${JSON.stringify(ex.output)}`));
66
+ return lines.join("\n");
67
+ }
68
+
69
+ function list(): string {
70
+ const byCategory = Map.groupBy(operations, (op) => op.category);
71
+ const width = Math.max(...operations.map((op) => op.id.length));
72
+ return [...byCategory]
73
+ .map(([category, ops]) => [category, ...ops.map((op) => ` ${op.id.padEnd(width)} ${op.name}`)].join("\n"))
74
+ .join("\n\n");
75
+ }
76
+
77
+ /** Extracts the global flags, which are only recognized before a `--` separator. */
78
+ function splitGlobalFlags(argv: string[]) {
79
+ const end = argv.includes("--") ? argv.indexOf("--") : argv.length;
80
+ const isGlobal = (arg: string, i: number) => i < end && ["--raw", "--help", "-h"].includes(arg);
81
+ return {
82
+ raw: argv.some((arg, i) => isGlobal(arg, i) && arg === "--raw"),
83
+ help: argv.some((arg, i) => isGlobal(arg, i) && arg !== "--raw"),
84
+ rest: argv.filter((arg, i) => !isGlobal(arg, i)),
85
+ };
86
+ }
87
+
88
+ function parse(argv: string[], options: ParseArgsOptionsConfig, helpHint: string) {
89
+ try {
90
+ return parseArgs({ args: argv, options, allowPositionals: true, allowNegative: true, strict: true });
91
+ } catch (error) {
92
+ throw new UsageError(`${(error as Error).message} (see: ${helpHint})`);
93
+ }
94
+ }
95
+
96
+ function parseOperationArgs(op: Operation, argv: string[]) {
97
+ const options: ParseArgsOptionsConfig = {};
98
+ for (const [key, def] of Object.entries(op.args)) {
99
+ options[kebab(key)] = { type: def.type === "boolean" ? "boolean" : "string" };
100
+ }
101
+ const parsed = parse(argv, options, `ops help ${op.id}`);
102
+
103
+ const args: Record<string, unknown> = {};
104
+ for (const [key, def] of Object.entries(op.args)) {
105
+ const raw = parsed.values[kebab(key)];
106
+ if (raw === undefined || Array.isArray(raw)) continue;
107
+ try {
108
+ args[key] = coerceArg(def, `--${kebab(key)}`, raw);
109
+ } catch (error) {
110
+ throw new UsageError((error as Error).message);
111
+ }
112
+ }
113
+ return { args, positionals: parsed.positionals };
114
+ }
115
+
116
+ async function readInput(positionals: string[]): Promise<Uint8Array> {
117
+ if (positionals.length) return new TextEncoder().encode(positionals.join(" "));
118
+ if (process.stdin.isTTY) throw new UsageError("No input: pass it as an argument or pipe it through stdin");
119
+ return new Uint8Array(await buffer(process.stdin));
120
+ }
121
+
122
+ async function write(output: Uint8Array | string, { raw = false } = {}) {
123
+ let bytes = typeof output === "string" ? new TextEncoder().encode(output) : output;
124
+ if (process.stdout.isTTY) {
125
+ // Decoded data may hold escape sequences that would drive the terminal.
126
+ if (!raw && !isPrintable(bytes)) {
127
+ console.error("ops: output contains binary data or control characters, shown as a hexdump (use --raw to print it as is)");
128
+ bytes = new TextEncoder().encode(hexdump(bytes));
129
+ }
130
+ // Keep the shell prompt off the output line, without altering piped bytes.
131
+ bytes = new Uint8Array([...bytes, 0x0a]);
132
+ }
133
+ await new Promise<void>((resolve, reject) => process.stdout.write(bytes, (error) => (error ? reject(error) : resolve())));
134
+ }
135
+
136
+ async function main(argv: string[]): Promise<void> {
137
+ const [command, ...args] = argv;
138
+ const { raw, help, rest } = splitGlobalFlags(args);
139
+
140
+ if (command === undefined || command === "--help" || command === "-h") return write(USAGE);
141
+ if (command === "--version" || command === "-v") return write(pkg.version);
142
+ if (command === "list") return write(list());
143
+
144
+ if (command === "help") {
145
+ const id = rest[0];
146
+ if (id === undefined) return write(USAGE);
147
+ if (id === "render") return write(RENDER_HELP);
148
+ if (!isOperationId(id)) throw new UsageError(`Unknown operation "${id}" (see: ops list)`);
149
+ return write(operationHelp(getOperation(id)));
150
+ }
151
+
152
+ if (command === "completions") {
153
+ const shell = rest[0];
154
+ if (!SHELLS.includes(shell as Shell)) throw new UsageError(`Expected a shell: ${SHELLS.join(", ")}`);
155
+ return write(completions(shell as Shell));
156
+ }
157
+
158
+ if (command === "render") {
159
+ if (help) return write(RENDER_HELP);
160
+ const { positionals } = parse(rest, {}, "ops help render");
161
+ const input = await readInput(positionals);
162
+ return write(renderBytes(new TextDecoder().decode(input)), { raw });
163
+ }
164
+
165
+ if (!isOperationId(command)) throw new UsageError(`Unknown operation "${command}" (see: ops list)`);
166
+ const op = getOperation(command);
167
+ if (help) return write(operationHelp(op));
168
+
169
+ const { args: opArgs, positionals } = parseOperationArgs(op, rest);
170
+ return write(run(command, await readInput(positionals), opArgs as never), { raw });
171
+ }
172
+
173
+ if (import.meta.main) {
174
+ // The reader went away (e.g. `ops ... | head`): stop quietly like other Unix tools.
175
+ process.stdout.on("error", (error: NodeJS.ErrnoException) => {
176
+ if (error.code === "EPIPE") process.exit(0);
177
+ throw error;
178
+ });
179
+ try {
180
+ await main(process.argv.slice(2));
181
+ } catch (error) {
182
+ // The reader went away (e.g. `ops ... | head`): stop quietly like other Unix tools.
183
+ if ((error as { code?: string }).code === "EPIPE") process.exit(0);
184
+ const message = error instanceof Error ? error.message : String(error);
185
+ const prefix = error instanceof OperationError && error.operation ? `${error.operation}: ` : "";
186
+ console.error(`ops: ${prefix}${message}`);
187
+ process.exit(error instanceof UsageError ? 2 : 1);
188
+ }
189
+ }