@emisso/cli-core 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.
package/dist/index.cjs ADDED
@@ -0,0 +1,372 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CliError: () => CliError,
24
+ ExitCode: () => ExitCode,
25
+ OutputRenderer: () => OutputRenderer,
26
+ OutputRendererLive: () => OutputRendererLive,
27
+ detectFormat: () => detectFormat,
28
+ errorEnvelope: () => errorEnvelope,
29
+ formatCsv: () => formatCsv,
30
+ formatJson: () => formatJson,
31
+ formatOption: () => formatOption,
32
+ formatTable: () => formatTable,
33
+ inputFileOption: () => inputFileOption,
34
+ isTTY: () => isTTY,
35
+ jsonFlag: () => jsonFlag,
36
+ makeTestRenderer: () => makeTestRenderer,
37
+ mapErrorToExitCode: () => mapErrorToExitCode,
38
+ outputFileOption: () => outputFileOption,
39
+ readAllConfig: () => readAllConfig,
40
+ readConfig: () => readConfig,
41
+ resolveFormat: () => resolveFormat,
42
+ runCli: () => runCli,
43
+ successEnvelope: () => successEnvelope,
44
+ verboseFlag: () => verboseFlag,
45
+ writeConfig: () => writeConfig
46
+ });
47
+ module.exports = __toCommonJS(index_exports);
48
+
49
+ // src/output/detect.ts
50
+ function isTTY() {
51
+ return process.stdout.isTTY === true;
52
+ }
53
+ function detectFormat(explicitFormat, ttyDefault = "table") {
54
+ if (explicitFormat) return explicitFormat;
55
+ return isTTY() ? ttyDefault : "json";
56
+ }
57
+
58
+ // src/output/json-envelope.ts
59
+ function successEnvelope(data, durationMs) {
60
+ const envelope = { ok: true, data };
61
+ if (durationMs !== void 0) {
62
+ envelope.meta = { duration_ms: Math.round(durationMs) };
63
+ }
64
+ return envelope;
65
+ }
66
+ function errorEnvelope(message, detail) {
67
+ return { ok: false, error: { message, ...detail ? { detail } : {} } };
68
+ }
69
+ function formatJson(envelope) {
70
+ return JSON.stringify(envelope, null, 2);
71
+ }
72
+
73
+ // src/output/table.ts
74
+ function formatTable(columns, rows) {
75
+ if (rows.length === 0) {
76
+ return columns.map((c) => c.label).join(" ") + "\n(no data)";
77
+ }
78
+ const widths = columns.map((col) => {
79
+ if (col.width) return col.width;
80
+ const dataMax = rows.reduce((max, row) => {
81
+ const val = String(row[col.key] ?? "");
82
+ return Math.max(max, val.length);
83
+ }, 0);
84
+ return Math.max(col.label.length, dataMax);
85
+ });
86
+ const header = columns.map((col, i) => pad(col.label, widths[i], col.align ?? "left")).join(" ");
87
+ const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500");
88
+ const body = rows.map(
89
+ (row) => columns.map((col, i) => {
90
+ const val = String(row[col.key] ?? "");
91
+ return pad(val, widths[i], col.align ?? "left");
92
+ }).join(" ")
93
+ );
94
+ return [header, separator, ...body].join("\n");
95
+ }
96
+ function pad(str, width, align) {
97
+ if (align === "right") return str.padStart(width);
98
+ return str.padEnd(width);
99
+ }
100
+
101
+ // src/output/csv.ts
102
+ function formatCsv(columns, rows) {
103
+ const header = columns.map((c) => escapeField(c.label)).join(",");
104
+ const body = rows.map(
105
+ (row) => columns.map((c) => escapeField(String(row[c.key] ?? ""))).join(",")
106
+ );
107
+ return [header, ...body].join("\r\n");
108
+ }
109
+ function escapeField(value) {
110
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
111
+ return `"${value.replace(/"/g, '""')}"`;
112
+ }
113
+ return value;
114
+ }
115
+
116
+ // src/output/renderer.ts
117
+ var import_effect = require("effect");
118
+ var OutputRenderer = class extends import_effect.Context.Tag("OutputRenderer")() {
119
+ };
120
+ var OutputRendererLive = import_effect.Layer.succeed(OutputRenderer, {
121
+ render: (rows, config, opts) => import_effect.Effect.sync(() => {
122
+ const format = detectFormat(opts?.format, config.ttyDefault);
123
+ let output;
124
+ switch (format) {
125
+ case "json":
126
+ output = formatJson(successEnvelope(rows, opts?.durationMs));
127
+ break;
128
+ case "csv":
129
+ output = formatCsv(
130
+ config.columns.map(
131
+ (c) => ({ key: c.key, label: c.label })
132
+ ),
133
+ rows
134
+ );
135
+ break;
136
+ case "table":
137
+ output = formatTable(config.columns, rows);
138
+ break;
139
+ }
140
+ process.stdout.write(output + "\n");
141
+ return output;
142
+ }),
143
+ renderSuccess: (data, durationMs) => import_effect.Effect.sync(() => {
144
+ const output = formatJson(successEnvelope(data, durationMs));
145
+ process.stdout.write(output + "\n");
146
+ return output;
147
+ }),
148
+ renderError: (message, detail) => import_effect.Effect.sync(() => {
149
+ const output = formatJson(errorEnvelope(message, detail));
150
+ process.stderr.write(output + "\n");
151
+ return output;
152
+ })
153
+ });
154
+ function makeTestRenderer() {
155
+ const stdout = [];
156
+ const stderr = [];
157
+ const layer = import_effect.Layer.succeed(OutputRenderer, {
158
+ render: (rows, config, opts) => import_effect.Effect.sync(() => {
159
+ const format = detectFormat(opts?.format, config.ttyDefault);
160
+ let output;
161
+ switch (format) {
162
+ case "json":
163
+ output = formatJson(successEnvelope(rows, opts?.durationMs));
164
+ break;
165
+ case "csv":
166
+ output = formatCsv(
167
+ config.columns.map(
168
+ (c) => ({ key: c.key, label: c.label })
169
+ ),
170
+ rows
171
+ );
172
+ break;
173
+ case "table":
174
+ output = formatTable(config.columns, rows);
175
+ break;
176
+ }
177
+ stdout.push(output);
178
+ return output;
179
+ }),
180
+ renderSuccess: (data, durationMs) => import_effect.Effect.sync(() => {
181
+ const output = formatJson(successEnvelope(data, durationMs));
182
+ stdout.push(output);
183
+ return output;
184
+ }),
185
+ renderError: (message, detail) => import_effect.Effect.sync(() => {
186
+ const output = formatJson(errorEnvelope(message, detail));
187
+ stderr.push(output);
188
+ return output;
189
+ })
190
+ });
191
+ return { stdout, stderr, layer };
192
+ }
193
+
194
+ // src/errors/exit-codes.ts
195
+ var ExitCode = {
196
+ Success: 0,
197
+ General: 1,
198
+ BadArgs: 2,
199
+ Auth: 3,
200
+ NotFound: 4,
201
+ Validation: 5
202
+ };
203
+
204
+ // src/errors/cli-error.ts
205
+ var import_effect2 = require("effect");
206
+ var CliError = class extends import_effect2.Data.TaggedError("CliError") {
207
+ };
208
+ var kindToExitCode = {
209
+ general: ExitCode.General,
210
+ "bad-args": ExitCode.BadArgs,
211
+ auth: ExitCode.Auth,
212
+ "not-found": ExitCode.NotFound,
213
+ validation: ExitCode.Validation
214
+ };
215
+ function mapErrorToExitCode(error) {
216
+ return kindToExitCode[error.kind];
217
+ }
218
+
219
+ // src/options/shared-options.ts
220
+ var import_cli = require("@effect/cli");
221
+ var import_effect3 = require("effect");
222
+ var formatOption = import_cli.Options.choice("format", ["csv", "json", "table"]).pipe(
223
+ import_cli.Options.optional,
224
+ import_cli.Options.withDescription("Output format (auto-detected if omitted: json for pipes, table for TTY)")
225
+ );
226
+ var jsonFlag = import_cli.Options.boolean("json").pipe(
227
+ import_cli.Options.withDefault(false),
228
+ import_cli.Options.withDescription("Output as JSON (shorthand for --format json)")
229
+ );
230
+ var outputFileOption = import_cli.Options.file("output", { exists: "either" }).pipe(
231
+ import_cli.Options.optional,
232
+ import_cli.Options.withAlias("o"),
233
+ import_cli.Options.withDescription("Write output to file instead of stdout")
234
+ );
235
+ var inputFileOption = import_cli.Options.file("input", { exists: "yes" }).pipe(
236
+ import_cli.Options.withAlias("i"),
237
+ import_cli.Options.withDescription("Input JSON file")
238
+ );
239
+ var verboseFlag = import_cli.Options.boolean("verbose").pipe(
240
+ import_cli.Options.withDefault(false),
241
+ import_cli.Options.withDescription("Enable verbose output")
242
+ );
243
+ function resolveFormat(format, json) {
244
+ const f = import_effect3.Option.getOrUndefined(format);
245
+ if (f) return f;
246
+ if (json) return "json";
247
+ return void 0;
248
+ }
249
+
250
+ // src/runner/run-cli.ts
251
+ var import_cli2 = require("@effect/cli");
252
+ var import_platform_node = require("@effect/platform-node");
253
+ var import_effect4 = require("effect");
254
+ function runCli(options) {
255
+ const app = import_cli2.Command.run(options.command, {
256
+ name: options.name,
257
+ version: options.version
258
+ });
259
+ const program = import_effect4.Effect.suspend(() => app(process.argv)).pipe(
260
+ import_effect4.Effect.catchAll(
261
+ (error) => import_effect4.Effect.sync(() => {
262
+ if (error instanceof CliError) {
263
+ const exitCode = mapErrorToExitCode(error);
264
+ if (!isTTY()) {
265
+ process.stderr.write(
266
+ formatJson(errorEnvelope(error.message, error.detail)) + "\n"
267
+ );
268
+ } else {
269
+ process.stderr.write(`Error: ${error.message}
270
+ `);
271
+ if (error.detail) {
272
+ process.stderr.write(` ${error.detail}
273
+ `);
274
+ }
275
+ }
276
+ process.exitCode = exitCode;
277
+ return;
278
+ }
279
+ const message = error instanceof Error ? error.message : String(error);
280
+ if (!isTTY()) {
281
+ process.stderr.write(
282
+ formatJson(errorEnvelope(message)) + "\n"
283
+ );
284
+ } else {
285
+ process.stderr.write(`Error: ${message}
286
+ `);
287
+ }
288
+ process.exitCode = ExitCode.General;
289
+ })
290
+ )
291
+ );
292
+ const layer = import_effect4.Layer.merge(import_platform_node.NodeContext.layer, options.layer);
293
+ const provided = import_effect4.Effect.provide(program, layer);
294
+ import_platform_node.NodeRuntime.runMain(provided);
295
+ }
296
+
297
+ // src/config/config-store.ts
298
+ var import_node_fs = require("fs");
299
+ var import_node_path = require("path");
300
+ var import_node_os = require("os");
301
+ function getConfigDir() {
302
+ const xdg = process.env["XDG_CONFIG_HOME"];
303
+ const base = xdg || (0, import_node_path.join)((0, import_node_os.homedir)(), ".config");
304
+ return (0, import_node_path.join)(base, "emisso");
305
+ }
306
+ function ensureDir(dir) {
307
+ if (!(0, import_node_fs.existsSync)(dir)) {
308
+ (0, import_node_fs.mkdirSync)(dir, { recursive: true });
309
+ }
310
+ }
311
+ function readConfig(namespace, key) {
312
+ const dir = getConfigDir();
313
+ const file = (0, import_node_path.join)(dir, `${namespace}.json`);
314
+ if (!(0, import_node_fs.existsSync)(file)) return void 0;
315
+ try {
316
+ const data = JSON.parse((0, import_node_fs.readFileSync)(file, "utf-8"));
317
+ return data[key];
318
+ } catch {
319
+ return void 0;
320
+ }
321
+ }
322
+ function writeConfig(namespace, key, value) {
323
+ const dir = getConfigDir();
324
+ ensureDir(dir);
325
+ const file = (0, import_node_path.join)(dir, `${namespace}.json`);
326
+ let data = {};
327
+ if ((0, import_node_fs.existsSync)(file)) {
328
+ try {
329
+ data = JSON.parse((0, import_node_fs.readFileSync)(file, "utf-8"));
330
+ } catch {
331
+ }
332
+ }
333
+ data[key] = value;
334
+ (0, import_node_fs.writeFileSync)(file, JSON.stringify(data, null, 2) + "\n", "utf-8");
335
+ }
336
+ function readAllConfig(namespace) {
337
+ const dir = getConfigDir();
338
+ const file = (0, import_node_path.join)(dir, `${namespace}.json`);
339
+ if (!(0, import_node_fs.existsSync)(file)) return void 0;
340
+ try {
341
+ return JSON.parse((0, import_node_fs.readFileSync)(file, "utf-8"));
342
+ } catch {
343
+ return void 0;
344
+ }
345
+ }
346
+ // Annotate the CommonJS export names for ESM import in node:
347
+ 0 && (module.exports = {
348
+ CliError,
349
+ ExitCode,
350
+ OutputRenderer,
351
+ OutputRendererLive,
352
+ detectFormat,
353
+ errorEnvelope,
354
+ formatCsv,
355
+ formatJson,
356
+ formatOption,
357
+ formatTable,
358
+ inputFileOption,
359
+ isTTY,
360
+ jsonFlag,
361
+ makeTestRenderer,
362
+ mapErrorToExitCode,
363
+ outputFileOption,
364
+ readAllConfig,
365
+ readConfig,
366
+ resolveFormat,
367
+ runCli,
368
+ successEnvelope,
369
+ verboseFlag,
370
+ writeConfig
371
+ });
372
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts","../src/output/detect.ts","../src/output/json-envelope.ts","../src/output/table.ts","../src/output/csv.ts","../src/output/renderer.ts","../src/errors/exit-codes.ts","../src/errors/cli-error.ts","../src/options/shared-options.ts","../src/runner/run-cli.ts","../src/config/config-store.ts"],"sourcesContent":["// Output\nexport { type OutputFormat, detectFormat, isTTY } from \"./output/detect.js\";\nexport {\n type SuccessEnvelope,\n type ErrorEnvelope,\n type Envelope,\n successEnvelope,\n errorEnvelope,\n formatJson,\n} from \"./output/json-envelope.js\";\nexport { type Column, formatTable } from \"./output/table.js\";\nexport { type CsvColumn, formatCsv } from \"./output/csv.js\";\nexport {\n OutputRenderer,\n OutputRendererLive,\n makeTestRenderer,\n type OutputConfig,\n type RendererService,\n} from \"./output/renderer.js\";\n\n// Errors\nexport { ExitCode } from \"./errors/exit-codes.js\";\nexport {\n CliError,\n type CliErrorKind,\n mapErrorToExitCode,\n} from \"./errors/cli-error.js\";\n\n// Options\nexport {\n formatOption,\n jsonFlag,\n outputFileOption,\n inputFileOption,\n verboseFlag,\n resolveFormat,\n} from \"./options/shared-options.js\";\n\n// Runner\nexport { runCli, type RunCliOptions } from \"./runner/run-cli.js\";\n\n// Config\nexport {\n readConfig,\n writeConfig,\n readAllConfig,\n} from \"./config/config-store.js\";\n","/**\n * TTY detection and automatic format selection\n *\n * Non-TTY (piped/agent) → JSON envelope\n * TTY (terminal) → command-specific default (table or csv)\n */\n\nexport type OutputFormat = \"json\" | \"csv\" | \"table\";\n\n/**\n * Detect if stdout is a TTY\n */\nexport function isTTY(): boolean {\n return process.stdout.isTTY === true;\n}\n\n/**\n * Auto-detect output format based on TTY status.\n * Non-TTY defaults to JSON (agent-friendly).\n * TTY uses the provided default format.\n */\nexport function detectFormat(\n explicitFormat: OutputFormat | undefined,\n ttyDefault: OutputFormat = \"table\",\n): OutputFormat {\n if (explicitFormat) return explicitFormat;\n return isTTY() ? ttyDefault : \"json\";\n}\n","/**\n * JSON envelope wrappers for CLI output\n *\n * Success: { ok: true, data: T, meta?: { duration_ms: number } }\n * Error: { ok: false, error: { message: string, detail?: string } }\n */\n\nexport interface SuccessEnvelope<T> {\n ok: true;\n data: T;\n meta?: { duration_ms: number };\n}\n\nexport interface ErrorEnvelope {\n ok: false;\n error: { message: string; detail?: string };\n}\n\nexport type Envelope<T> = SuccessEnvelope<T> | ErrorEnvelope;\n\nexport function successEnvelope<T>(\n data: T,\n durationMs?: number,\n): SuccessEnvelope<T> {\n const envelope: SuccessEnvelope<T> = { ok: true, data };\n if (durationMs !== undefined) {\n envelope.meta = { duration_ms: Math.round(durationMs) };\n }\n return envelope;\n}\n\nexport function errorEnvelope(\n message: string,\n detail?: string,\n): ErrorEnvelope {\n return { ok: false, error: { message, ...(detail ? { detail } : {}) } };\n}\n\nexport function formatJson<T>(envelope: Envelope<T>): string {\n return JSON.stringify(envelope, null, 2);\n}\n","/**\n * Zero-dependency column-aligned TTY table renderer\n */\n\nexport interface Column {\n key: string;\n label: string;\n align?: \"left\" | \"right\";\n width?: number;\n}\n\n/**\n * Render rows as a column-aligned text table.\n *\n * Column widths auto-size to fit data unless explicitly set.\n */\nexport function formatTable(\n columns: Column[],\n rows: Record<string, unknown>[],\n): string {\n if (rows.length === 0) {\n return columns.map((c) => c.label).join(\" \") + \"\\n(no data)\";\n }\n\n // Calculate widths\n const widths = columns.map((col) => {\n if (col.width) return col.width;\n const dataMax = rows.reduce((max, row) => {\n const val = String(row[col.key] ?? \"\");\n return Math.max(max, val.length);\n }, 0);\n return Math.max(col.label.length, dataMax);\n });\n\n // Header\n const header = columns\n .map((col, i) => pad(col.label, widths[i], col.align ?? \"left\"))\n .join(\" \");\n\n const separator = widths.map((w) => \"─\".repeat(w)).join(\"──\");\n\n // Rows\n const body = rows.map((row) =>\n columns\n .map((col, i) => {\n const val = String(row[col.key] ?? \"\");\n return pad(val, widths[i], col.align ?? \"left\");\n })\n .join(\" \"),\n );\n\n return [header, separator, ...body].join(\"\\n\");\n}\n\nfunction pad(str: string, width: number, align: \"left\" | \"right\"): string {\n if (align === \"right\") return str.padStart(width);\n return str.padEnd(width);\n}\n","/**\n * RFC 4180 CSV serializer — zero dependencies\n */\n\nexport interface CsvColumn {\n key: string;\n label: string;\n}\n\n/**\n * Serialize rows as RFC 4180 CSV (quoted fields, CRLF line endings).\n */\nexport function formatCsv(\n columns: CsvColumn[],\n rows: Record<string, unknown>[],\n): string {\n const header = columns.map((c) => escapeField(c.label)).join(\",\");\n const body = rows.map((row) =>\n columns.map((c) => escapeField(String(row[c.key] ?? \"\"))).join(\",\"),\n );\n return [header, ...body].join(\"\\r\\n\");\n}\n\nfunction escapeField(value: string): string {\n if (value.includes(\",\") || value.includes('\"') || value.includes(\"\\n\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n","/**\n * OutputRenderer — orchestrates format selection and output\n *\n * Provides a unified interface for commands to emit structured output\n * without worrying about format details.\n */\n\nimport { Context, Effect, Layer } from \"effect\";\nimport type { OutputFormat } from \"./detect.js\";\nimport { detectFormat } from \"./detect.js\";\nimport {\n successEnvelope,\n errorEnvelope,\n formatJson,\n} from \"./json-envelope.js\";\nimport { formatTable, type Column } from \"./table.js\";\nimport { formatCsv, type CsvColumn } from \"./csv.js\";\n\nexport interface OutputConfig {\n columns: Column[];\n ttyDefault?: OutputFormat;\n}\n\nexport interface RendererService {\n /**\n * Render structured data to stdout in the resolved format.\n * Returns the formatted string (for testing).\n */\n readonly render: (\n rows: Record<string, unknown>[],\n config: OutputConfig,\n opts?: { format?: OutputFormat; durationMs?: number },\n ) => Effect.Effect<string>;\n\n /**\n * Render a success envelope (JSON mode only convenience).\n */\n readonly renderSuccess: <T>(\n data: T,\n durationMs?: number,\n ) => Effect.Effect<string>;\n\n /**\n * Render an error envelope to stderr.\n */\n readonly renderError: (\n message: string,\n detail?: string,\n ) => Effect.Effect<string>;\n}\n\nexport class OutputRenderer extends Context.Tag(\"OutputRenderer\")<\n OutputRenderer,\n RendererService\n>() {}\n\n/**\n * Live implementation that writes to process.stdout / process.stderr\n */\nexport const OutputRendererLive = Layer.succeed(OutputRenderer, {\n render: (rows, config, opts) =>\n Effect.sync(() => {\n const format = detectFormat(opts?.format, config.ttyDefault);\n let output: string;\n\n switch (format) {\n case \"json\":\n output = formatJson(successEnvelope(rows, opts?.durationMs));\n break;\n case \"csv\":\n output = formatCsv(\n config.columns.map(\n (c): CsvColumn => ({ key: c.key, label: c.label }),\n ),\n rows,\n );\n break;\n case \"table\":\n output = formatTable(config.columns, rows);\n break;\n }\n\n process.stdout.write(output + \"\\n\");\n return output;\n }),\n\n renderSuccess: (data, durationMs) =>\n Effect.sync(() => {\n const output = formatJson(successEnvelope(data, durationMs));\n process.stdout.write(output + \"\\n\");\n return output;\n }),\n\n renderError: (message, detail) =>\n Effect.sync(() => {\n const output = formatJson(errorEnvelope(message, detail));\n process.stderr.write(output + \"\\n\");\n return output;\n }),\n});\n\n/**\n * Test implementation that captures output into arrays for assertions\n */\nexport function makeTestRenderer(): {\n stdout: string[];\n stderr: string[];\n layer: Layer.Layer<OutputRenderer>;\n} {\n const stdout: string[] = [];\n const stderr: string[] = [];\n\n const layer = Layer.succeed(OutputRenderer, {\n render: (rows, config, opts) =>\n Effect.sync(() => {\n const format = detectFormat(opts?.format, config.ttyDefault);\n let output: string;\n\n switch (format) {\n case \"json\":\n output = formatJson(successEnvelope(rows, opts?.durationMs));\n break;\n case \"csv\":\n output = formatCsv(\n config.columns.map(\n (c): CsvColumn => ({ key: c.key, label: c.label }),\n ),\n rows,\n );\n break;\n case \"table\":\n output = formatTable(config.columns, rows);\n break;\n }\n\n stdout.push(output);\n return output;\n }),\n\n renderSuccess: (data, durationMs) =>\n Effect.sync(() => {\n const output = formatJson(successEnvelope(data, durationMs));\n stdout.push(output);\n return output;\n }),\n\n renderError: (message, detail) =>\n Effect.sync(() => {\n const output = formatJson(errorEnvelope(message, detail));\n stderr.push(output);\n return output;\n }),\n });\n\n return { stdout, stderr, layer };\n}\n","/**\n * Standardized CLI exit codes for Emisso CLIs\n */\n\nexport const ExitCode = {\n Success: 0,\n General: 1,\n BadArgs: 2,\n Auth: 3,\n NotFound: 4,\n Validation: 5,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n","/**\n * Typed CLI error with exit code mapping\n */\n\nimport { Data } from \"effect\";\nimport { ExitCode } from \"./exit-codes.js\";\n\nexport type CliErrorKind =\n | \"general\"\n | \"bad-args\"\n | \"auth\"\n | \"not-found\"\n | \"validation\";\n\nexport class CliError extends Data.TaggedError(\"CliError\")<{\n readonly kind: CliErrorKind;\n readonly message: string;\n readonly detail?: string;\n readonly cause?: unknown;\n}> {}\n\nconst kindToExitCode: Record<CliErrorKind, ExitCode> = {\n general: ExitCode.General,\n \"bad-args\": ExitCode.BadArgs,\n auth: ExitCode.Auth,\n \"not-found\": ExitCode.NotFound,\n validation: ExitCode.Validation,\n};\n\nexport function mapErrorToExitCode(error: CliError): ExitCode {\n return kindToExitCode[error.kind];\n}\n","/**\n * Reusable CLI option definitions for @effect/cli\n */\n\nimport { Options } from \"@effect/cli\";\nimport { Option } from \"effect\";\nimport type { OutputFormat } from \"../output/detect.js\";\n\n/**\n * --format csv|json|table\n * Returns Option<OutputFormat> — use resolveFormat() to unwrap.\n */\nexport const formatOption = Options.choice(\"format\", [\"csv\", \"json\", \"table\"]).pipe(\n Options.optional,\n Options.withDescription(\"Output format (auto-detected if omitted: json for pipes, table for TTY)\"),\n);\n\n/**\n * --json — shorthand for --format json\n */\nexport const jsonFlag = Options.boolean(\"json\").pipe(\n Options.withDefault(false),\n Options.withDescription(\"Output as JSON (shorthand for --format json)\"),\n);\n\n/**\n * -o / --output <file>\n */\nexport const outputFileOption = Options.file(\"output\", { exists: \"either\" }).pipe(\n Options.optional,\n Options.withAlias(\"o\"),\n Options.withDescription(\"Write output to file instead of stdout\"),\n);\n\n/**\n * -i / --input <file>\n */\nexport const inputFileOption = Options.file(\"input\", { exists: \"yes\" }).pipe(\n Options.withAlias(\"i\"),\n Options.withDescription(\"Input JSON file\"),\n);\n\n/**\n * --verbose\n */\nexport const verboseFlag = Options.boolean(\"verbose\").pipe(\n Options.withDefault(false),\n Options.withDescription(\"Enable verbose output\"),\n);\n\n/**\n * Resolve the effective format from --format Option and --json flag.\n * Unwraps the Effect Option type into OutputFormat | undefined.\n */\nexport function resolveFormat(\n format: Option.Option<\"csv\" | \"json\" | \"table\">,\n json: boolean,\n): OutputFormat | undefined {\n const f = Option.getOrUndefined(format);\n if (f) return f;\n if (json) return \"json\";\n return undefined;\n}\n","/**\n * CLI runner — wraps @effect/cli Command.run with error→exit code mapping,\n * timing, and layer provision.\n */\n\nimport { Command } from \"@effect/cli\";\nimport { NodeContext, NodeRuntime } from \"@effect/platform-node\";\nimport { Effect, Layer } from \"effect\";\nimport { CliError, mapErrorToExitCode } from \"../errors/cli-error.js\";\nimport { ExitCode } from \"../errors/exit-codes.js\";\nimport { errorEnvelope, formatJson } from \"../output/json-envelope.js\";\nimport { isTTY } from \"../output/detect.js\";\n\nexport interface RunCliOptions<R, E, Name extends string = string> {\n /** The root @effect/cli Command */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n command: Command.Command<Name, R, E, any>;\n /** Layer that provides all required services (R) */\n layer: Layer.Layer<R>;\n /** CLI name for help text */\n name: string;\n /** CLI version */\n version: string;\n}\n\n/**\n * Run the CLI with standardized error handling:\n * - CliError → mapped exit code + formatted error output\n * - Unknown errors → exit 1 + error message\n */\nexport function runCli<R, E, Name extends string>(options: RunCliOptions<R, E, Name>): void {\n const app = Command.run(options.command, {\n name: options.name,\n version: options.version,\n });\n\n const program = Effect.suspend(() => app(process.argv)).pipe(\n Effect.catchAll((error: unknown) =>\n Effect.sync(() => {\n if (error instanceof CliError) {\n const exitCode = mapErrorToExitCode(error);\n if (!isTTY()) {\n process.stderr.write(\n formatJson(errorEnvelope(error.message, error.detail)) + \"\\n\",\n );\n } else {\n process.stderr.write(`Error: ${error.message}\\n`);\n if (error.detail) {\n process.stderr.write(` ${error.detail}\\n`);\n }\n }\n process.exitCode = exitCode;\n return;\n }\n\n const message =\n error instanceof Error ? error.message : String(error);\n if (!isTTY()) {\n process.stderr.write(\n formatJson(errorEnvelope(message)) + \"\\n\",\n );\n } else {\n process.stderr.write(`Error: ${message}\\n`);\n }\n process.exitCode = ExitCode.General;\n }),\n ),\n );\n\n const layer = Layer.merge(NodeContext.layer, options.layer);\n const provided = Effect.provide(program, layer);\n\n NodeRuntime.runMain(provided as Effect.Effect<void>);\n}\n","/**\n * XDG-compliant config store at ~/.config/emisso/\n *\n * Reads/writes JSON config files. No encryption (that's credential-store).\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nfunction getConfigDir(): string {\n const xdg = process.env[\"XDG_CONFIG_HOME\"];\n const base = xdg || join(homedir(), \".config\");\n return join(base, \"emisso\");\n}\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\n/**\n * Read a config value from ~/.config/emisso/<namespace>.json\n */\nexport function readConfig<T>(namespace: string, key: string): T | undefined {\n const dir = getConfigDir();\n const file = join(dir, `${namespace}.json`);\n if (!existsSync(file)) return undefined;\n\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n return data[key] as T | undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write a config value to ~/.config/emisso/<namespace>.json\n */\nexport function writeConfig(\n namespace: string,\n key: string,\n value: unknown,\n): void {\n const dir = getConfigDir();\n ensureDir(dir);\n const file = join(dir, `${namespace}.json`);\n\n let data: Record<string, unknown> = {};\n if (existsSync(file)) {\n try {\n data = JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n // Corrupted file — start fresh\n }\n }\n\n data[key] = value;\n writeFileSync(file, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Read all config from ~/.config/emisso/<namespace>.json\n */\nexport function readAllConfig(\n namespace: string,\n): Record<string, unknown> | undefined {\n const dir = getConfigDir();\n const file = join(dir, `${namespace}.json`);\n if (!existsSync(file)) return undefined;\n\n try {\n return JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACYO,SAAS,QAAiB;AAC/B,SAAO,QAAQ,OAAO,UAAU;AAClC;AAOO,SAAS,aACd,gBACA,aAA2B,SACb;AACd,MAAI,eAAgB,QAAO;AAC3B,SAAO,MAAM,IAAI,aAAa;AAChC;;;ACPO,SAAS,gBACd,MACA,YACoB;AACpB,QAAM,WAA+B,EAAE,IAAI,MAAM,KAAK;AACtD,MAAI,eAAe,QAAW;AAC5B,aAAS,OAAO,EAAE,aAAa,KAAK,MAAM,UAAU,EAAE;AAAA,EACxD;AACA,SAAO;AACT;AAEO,SAAS,cACd,SACA,QACe;AACf,SAAO,EAAE,IAAI,OAAO,OAAO,EAAE,SAAS,GAAI,SAAS,EAAE,OAAO,IAAI,CAAC,EAAG,EAAE;AACxE;AAEO,SAAS,WAAc,UAA+B;AAC3D,SAAO,KAAK,UAAU,UAAU,MAAM,CAAC;AACzC;;;ACxBO,SAAS,YACd,SACA,MACQ;AACR,MAAI,KAAK,WAAW,GAAG;AACrB,WAAO,QAAQ,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,KAAK,IAAI,IAAI;AAAA,EAClD;AAGA,QAAM,SAAS,QAAQ,IAAI,CAAC,QAAQ;AAClC,QAAI,IAAI,MAAO,QAAO,IAAI;AAC1B,UAAM,UAAU,KAAK,OAAO,CAAC,KAAK,QAAQ;AACxC,YAAM,MAAM,OAAO,IAAI,IAAI,GAAG,KAAK,EAAE;AACrC,aAAO,KAAK,IAAI,KAAK,IAAI,MAAM;AAAA,IACjC,GAAG,CAAC;AACJ,WAAO,KAAK,IAAI,IAAI,MAAM,QAAQ,OAAO;AAAA,EAC3C,CAAC;AAGD,QAAM,SAAS,QACZ,IAAI,CAAC,KAAK,MAAM,IAAI,IAAI,OAAO,OAAO,CAAC,GAAG,IAAI,SAAS,MAAM,CAAC,EAC9D,KAAK,IAAI;AAEZ,QAAM,YAAY,OAAO,IAAI,CAAC,MAAM,SAAI,OAAO,CAAC,CAAC,EAAE,KAAK,cAAI;AAG5D,QAAM,OAAO,KAAK;AAAA,IAAI,CAAC,QACrB,QACG,IAAI,CAAC,KAAK,MAAM;AACf,YAAM,MAAM,OAAO,IAAI,IAAI,GAAG,KAAK,EAAE;AACrC,aAAO,IAAI,KAAK,OAAO,CAAC,GAAG,IAAI,SAAS,MAAM;AAAA,IAChD,CAAC,EACA,KAAK,IAAI;AAAA,EACd;AAEA,SAAO,CAAC,QAAQ,WAAW,GAAG,IAAI,EAAE,KAAK,IAAI;AAC/C;AAEA,SAAS,IAAI,KAAa,OAAe,OAAiC;AACxE,MAAI,UAAU,QAAS,QAAO,IAAI,SAAS,KAAK;AAChD,SAAO,IAAI,OAAO,KAAK;AACzB;;;AC7CO,SAAS,UACd,SACA,MACQ;AACR,QAAM,SAAS,QAAQ,IAAI,CAAC,MAAM,YAAY,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG;AAChE,QAAM,OAAO,KAAK;AAAA,IAAI,CAAC,QACrB,QAAQ,IAAI,CAAC,MAAM,YAAY,OAAO,IAAI,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,GAAG;AAAA,EACpE;AACA,SAAO,CAAC,QAAQ,GAAG,IAAI,EAAE,KAAK,MAAM;AACtC;AAEA,SAAS,YAAY,OAAuB;AAC1C,MAAI,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,GAAG,KAAK,MAAM,SAAS,IAAI,GAAG;AACtE,WAAO,IAAI,MAAM,QAAQ,MAAM,IAAI,CAAC;AAAA,EACtC;AACA,SAAO;AACT;;;ACrBA,oBAAuC;AA4ChC,IAAM,iBAAN,cAA6B,sBAAQ,IAAI,gBAAgB,EAG9D,EAAE;AAAC;AAKE,IAAM,qBAAqB,oBAAM,QAAQ,gBAAgB;AAAA,EAC9D,QAAQ,CAAC,MAAM,QAAQ,SACrB,qBAAO,KAAK,MAAM;AAChB,UAAM,SAAS,aAAa,MAAM,QAAQ,OAAO,UAAU;AAC3D,QAAI;AAEJ,YAAQ,QAAQ;AAAA,MACd,KAAK;AACH,iBAAS,WAAW,gBAAgB,MAAM,MAAM,UAAU,CAAC;AAC3D;AAAA,MACF,KAAK;AACH,iBAAS;AAAA,UACP,OAAO,QAAQ;AAAA,YACb,CAAC,OAAkB,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM;AAAA,UAClD;AAAA,UACA;AAAA,QACF;AACA;AAAA,MACF,KAAK;AACH,iBAAS,YAAY,OAAO,SAAS,IAAI;AACzC;AAAA,IACJ;AAEA,YAAQ,OAAO,MAAM,SAAS,IAAI;AAClC,WAAO;AAAA,EACT,CAAC;AAAA,EAEH,eAAe,CAAC,MAAM,eACpB,qBAAO,KAAK,MAAM;AAChB,UAAM,SAAS,WAAW,gBAAgB,MAAM,UAAU,CAAC;AAC3D,YAAQ,OAAO,MAAM,SAAS,IAAI;AAClC,WAAO;AAAA,EACT,CAAC;AAAA,EAEH,aAAa,CAAC,SAAS,WACrB,qBAAO,KAAK,MAAM;AAChB,UAAM,SAAS,WAAW,cAAc,SAAS,MAAM,CAAC;AACxD,YAAQ,OAAO,MAAM,SAAS,IAAI;AAClC,WAAO;AAAA,EACT,CAAC;AACL,CAAC;AAKM,SAAS,mBAId;AACA,QAAM,SAAmB,CAAC;AAC1B,QAAM,SAAmB,CAAC;AAE1B,QAAM,QAAQ,oBAAM,QAAQ,gBAAgB;AAAA,IAC1C,QAAQ,CAAC,MAAM,QAAQ,SACrB,qBAAO,KAAK,MAAM;AAChB,YAAM,SAAS,aAAa,MAAM,QAAQ,OAAO,UAAU;AAC3D,UAAI;AAEJ,cAAQ,QAAQ;AAAA,QACd,KAAK;AACH,mBAAS,WAAW,gBAAgB,MAAM,MAAM,UAAU,CAAC;AAC3D;AAAA,QACF,KAAK;AACH,mBAAS;AAAA,YACP,OAAO,QAAQ;AAAA,cACb,CAAC,OAAkB,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM;AAAA,YAClD;AAAA,YACA;AAAA,UACF;AACA;AAAA,QACF,KAAK;AACH,mBAAS,YAAY,OAAO,SAAS,IAAI;AACzC;AAAA,MACJ;AAEA,aAAO,KAAK,MAAM;AAClB,aAAO;AAAA,IACT,CAAC;AAAA,IAEH,eAAe,CAAC,MAAM,eACpB,qBAAO,KAAK,MAAM;AAChB,YAAM,SAAS,WAAW,gBAAgB,MAAM,UAAU,CAAC;AAC3D,aAAO,KAAK,MAAM;AAClB,aAAO;AAAA,IACT,CAAC;AAAA,IAEH,aAAa,CAAC,SAAS,WACrB,qBAAO,KAAK,MAAM;AAChB,YAAM,SAAS,WAAW,cAAc,SAAS,MAAM,CAAC;AACxD,aAAO,KAAK,MAAM;AAClB,aAAO;AAAA,IACT,CAAC;AAAA,EACL,CAAC;AAED,SAAO,EAAE,QAAQ,QAAQ,MAAM;AACjC;;;ACvJO,IAAM,WAAW;AAAA,EACtB,SAAS;AAAA,EACT,SAAS;AAAA,EACT,SAAS;AAAA,EACT,MAAM;AAAA,EACN,UAAU;AAAA,EACV,YAAY;AACd;;;ACPA,IAAAA,iBAAqB;AAUd,IAAM,WAAN,cAAuB,oBAAK,YAAY,UAAU,EAKtD;AAAC;AAEJ,IAAM,iBAAiD;AAAA,EACrD,SAAS,SAAS;AAAA,EAClB,YAAY,SAAS;AAAA,EACrB,MAAM,SAAS;AAAA,EACf,aAAa,SAAS;AAAA,EACtB,YAAY,SAAS;AACvB;AAEO,SAAS,mBAAmB,OAA2B;AAC5D,SAAO,eAAe,MAAM,IAAI;AAClC;;;AC3BA,iBAAwB;AACxB,IAAAC,iBAAuB;AAOhB,IAAM,eAAe,mBAAQ,OAAO,UAAU,CAAC,OAAO,QAAQ,OAAO,CAAC,EAAE;AAAA,EAC7E,mBAAQ;AAAA,EACR,mBAAQ,gBAAgB,yEAAyE;AACnG;AAKO,IAAM,WAAW,mBAAQ,QAAQ,MAAM,EAAE;AAAA,EAC9C,mBAAQ,YAAY,KAAK;AAAA,EACzB,mBAAQ,gBAAgB,8CAA8C;AACxE;AAKO,IAAM,mBAAmB,mBAAQ,KAAK,UAAU,EAAE,QAAQ,SAAS,CAAC,EAAE;AAAA,EAC3E,mBAAQ;AAAA,EACR,mBAAQ,UAAU,GAAG;AAAA,EACrB,mBAAQ,gBAAgB,wCAAwC;AAClE;AAKO,IAAM,kBAAkB,mBAAQ,KAAK,SAAS,EAAE,QAAQ,MAAM,CAAC,EAAE;AAAA,EACtE,mBAAQ,UAAU,GAAG;AAAA,EACrB,mBAAQ,gBAAgB,iBAAiB;AAC3C;AAKO,IAAM,cAAc,mBAAQ,QAAQ,SAAS,EAAE;AAAA,EACpD,mBAAQ,YAAY,KAAK;AAAA,EACzB,mBAAQ,gBAAgB,uBAAuB;AACjD;AAMO,SAAS,cACd,QACA,MAC0B;AAC1B,QAAM,IAAI,sBAAO,eAAe,MAAM;AACtC,MAAI,EAAG,QAAO;AACd,MAAI,KAAM,QAAO;AACjB,SAAO;AACT;;;ACzDA,IAAAC,cAAwB;AACxB,2BAAyC;AACzC,IAAAC,iBAA8B;AAuBvB,SAAS,OAAkC,SAA0C;AAC1F,QAAM,MAAM,oBAAQ,IAAI,QAAQ,SAAS;AAAA,IACvC,MAAM,QAAQ;AAAA,IACd,SAAS,QAAQ;AAAA,EACnB,CAAC;AAED,QAAM,UAAU,sBAAO,QAAQ,MAAM,IAAI,QAAQ,IAAI,CAAC,EAAE;AAAA,IACtD,sBAAO;AAAA,MAAS,CAAC,UACf,sBAAO,KAAK,MAAM;AAChB,YAAI,iBAAiB,UAAU;AAC7B,gBAAM,WAAW,mBAAmB,KAAK;AACzC,cAAI,CAAC,MAAM,GAAG;AACZ,oBAAQ,OAAO;AAAA,cACb,WAAW,cAAc,MAAM,SAAS,MAAM,MAAM,CAAC,IAAI;AAAA,YAC3D;AAAA,UACF,OAAO;AACL,oBAAQ,OAAO,MAAM,UAAU,MAAM,OAAO;AAAA,CAAI;AAChD,gBAAI,MAAM,QAAQ;AAChB,sBAAQ,OAAO,MAAM,KAAK,MAAM,MAAM;AAAA,CAAI;AAAA,YAC5C;AAAA,UACF;AACA,kBAAQ,WAAW;AACnB;AAAA,QACF;AAEA,cAAM,UACJ,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK;AACvD,YAAI,CAAC,MAAM,GAAG;AACZ,kBAAQ,OAAO;AAAA,YACb,WAAW,cAAc,OAAO,CAAC,IAAI;AAAA,UACvC;AAAA,QACF,OAAO;AACL,kBAAQ,OAAO,MAAM,UAAU,OAAO;AAAA,CAAI;AAAA,QAC5C;AACA,gBAAQ,WAAW,SAAS;AAAA,MAC9B,CAAC;AAAA,IACH;AAAA,EACF;AAEA,QAAM,QAAQ,qBAAM,MAAM,iCAAY,OAAO,QAAQ,KAAK;AAC1D,QAAM,WAAW,sBAAO,QAAQ,SAAS,KAAK;AAE9C,mCAAY,QAAQ,QAA+B;AACrD;;;ACnEA,qBAAmE;AACnE,uBAAqB;AACrB,qBAAwB;AAExB,SAAS,eAAuB;AAC9B,QAAM,MAAM,QAAQ,IAAI,iBAAiB;AACzC,QAAM,OAAO,WAAO,2BAAK,wBAAQ,GAAG,SAAS;AAC7C,aAAO,uBAAK,MAAM,QAAQ;AAC5B;AAEA,SAAS,UAAU,KAAmB;AACpC,MAAI,KAAC,2BAAW,GAAG,GAAG;AACpB,kCAAU,KAAK,EAAE,WAAW,KAAK,CAAC;AAAA,EACpC;AACF;AAKO,SAAS,WAAc,WAAmB,KAA4B;AAC3E,QAAM,MAAM,aAAa;AACzB,QAAM,WAAO,uBAAK,KAAK,GAAG,SAAS,OAAO;AAC1C,MAAI,KAAC,2BAAW,IAAI,EAAG,QAAO;AAE9B,MAAI;AACF,UAAM,OAAO,KAAK,UAAM,6BAAa,MAAM,OAAO,CAAC;AACnD,WAAO,KAAK,GAAG;AAAA,EACjB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAKO,SAAS,YACd,WACA,KACA,OACM;AACN,QAAM,MAAM,aAAa;AACzB,YAAU,GAAG;AACb,QAAM,WAAO,uBAAK,KAAK,GAAG,SAAS,OAAO;AAE1C,MAAI,OAAgC,CAAC;AACrC,UAAI,2BAAW,IAAI,GAAG;AACpB,QAAI;AACF,aAAO,KAAK,UAAM,6BAAa,MAAM,OAAO,CAAC;AAAA,IAC/C,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,OAAK,GAAG,IAAI;AACZ,oCAAc,MAAM,KAAK,UAAU,MAAM,MAAM,CAAC,IAAI,MAAM,OAAO;AACnE;AAKO,SAAS,cACd,WACqC;AACrC,QAAM,MAAM,aAAa;AACzB,QAAM,WAAO,uBAAK,KAAK,GAAG,SAAS,OAAO;AAC1C,MAAI,KAAC,2BAAW,IAAI,EAAG,QAAO;AAE9B,MAAI;AACF,WAAO,KAAK,UAAM,6BAAa,MAAM,OAAO,CAAC;AAAA,EAC/C,QAAQ;AACN,WAAO;AAAA,EACT;AACF;","names":["import_effect","import_effect","import_cli","import_effect"]}
@@ -0,0 +1,219 @@
1
+ import { Context, Effect, Layer, Option } from 'effect';
2
+ import * as effect_Cause from 'effect/Cause';
3
+ import * as effect_Types from 'effect/Types';
4
+ import { Options, Command } from '@effect/cli';
5
+
6
+ /**
7
+ * TTY detection and automatic format selection
8
+ *
9
+ * Non-TTY (piped/agent) → JSON envelope
10
+ * TTY (terminal) → command-specific default (table or csv)
11
+ */
12
+ type OutputFormat = "json" | "csv" | "table";
13
+ /**
14
+ * Detect if stdout is a TTY
15
+ */
16
+ declare function isTTY(): boolean;
17
+ /**
18
+ * Auto-detect output format based on TTY status.
19
+ * Non-TTY defaults to JSON (agent-friendly).
20
+ * TTY uses the provided default format.
21
+ */
22
+ declare function detectFormat(explicitFormat: OutputFormat | undefined, ttyDefault?: OutputFormat): OutputFormat;
23
+
24
+ /**
25
+ * JSON envelope wrappers for CLI output
26
+ *
27
+ * Success: { ok: true, data: T, meta?: { duration_ms: number } }
28
+ * Error: { ok: false, error: { message: string, detail?: string } }
29
+ */
30
+ interface SuccessEnvelope<T> {
31
+ ok: true;
32
+ data: T;
33
+ meta?: {
34
+ duration_ms: number;
35
+ };
36
+ }
37
+ interface ErrorEnvelope {
38
+ ok: false;
39
+ error: {
40
+ message: string;
41
+ detail?: string;
42
+ };
43
+ }
44
+ type Envelope<T> = SuccessEnvelope<T> | ErrorEnvelope;
45
+ declare function successEnvelope<T>(data: T, durationMs?: number): SuccessEnvelope<T>;
46
+ declare function errorEnvelope(message: string, detail?: string): ErrorEnvelope;
47
+ declare function formatJson<T>(envelope: Envelope<T>): string;
48
+
49
+ /**
50
+ * Zero-dependency column-aligned TTY table renderer
51
+ */
52
+ interface Column {
53
+ key: string;
54
+ label: string;
55
+ align?: "left" | "right";
56
+ width?: number;
57
+ }
58
+ /**
59
+ * Render rows as a column-aligned text table.
60
+ *
61
+ * Column widths auto-size to fit data unless explicitly set.
62
+ */
63
+ declare function formatTable(columns: Column[], rows: Record<string, unknown>[]): string;
64
+
65
+ /**
66
+ * RFC 4180 CSV serializer — zero dependencies
67
+ */
68
+ interface CsvColumn {
69
+ key: string;
70
+ label: string;
71
+ }
72
+ /**
73
+ * Serialize rows as RFC 4180 CSV (quoted fields, CRLF line endings).
74
+ */
75
+ declare function formatCsv(columns: CsvColumn[], rows: Record<string, unknown>[]): string;
76
+
77
+ /**
78
+ * OutputRenderer — orchestrates format selection and output
79
+ *
80
+ * Provides a unified interface for commands to emit structured output
81
+ * without worrying about format details.
82
+ */
83
+
84
+ interface OutputConfig {
85
+ columns: Column[];
86
+ ttyDefault?: OutputFormat;
87
+ }
88
+ interface RendererService {
89
+ /**
90
+ * Render structured data to stdout in the resolved format.
91
+ * Returns the formatted string (for testing).
92
+ */
93
+ readonly render: (rows: Record<string, unknown>[], config: OutputConfig, opts?: {
94
+ format?: OutputFormat;
95
+ durationMs?: number;
96
+ }) => Effect.Effect<string>;
97
+ /**
98
+ * Render a success envelope (JSON mode only convenience).
99
+ */
100
+ readonly renderSuccess: <T>(data: T, durationMs?: number) => Effect.Effect<string>;
101
+ /**
102
+ * Render an error envelope to stderr.
103
+ */
104
+ readonly renderError: (message: string, detail?: string) => Effect.Effect<string>;
105
+ }
106
+ declare const OutputRenderer_base: Context.TagClass<OutputRenderer, "OutputRenderer", RendererService>;
107
+ declare class OutputRenderer extends OutputRenderer_base {
108
+ }
109
+ /**
110
+ * Live implementation that writes to process.stdout / process.stderr
111
+ */
112
+ declare const OutputRendererLive: Layer.Layer<OutputRenderer, never, never>;
113
+ /**
114
+ * Test implementation that captures output into arrays for assertions
115
+ */
116
+ declare function makeTestRenderer(): {
117
+ stdout: string[];
118
+ stderr: string[];
119
+ layer: Layer.Layer<OutputRenderer>;
120
+ };
121
+
122
+ /**
123
+ * Standardized CLI exit codes for Emisso CLIs
124
+ */
125
+ declare const ExitCode: {
126
+ readonly Success: 0;
127
+ readonly General: 1;
128
+ readonly BadArgs: 2;
129
+ readonly Auth: 3;
130
+ readonly NotFound: 4;
131
+ readonly Validation: 5;
132
+ };
133
+ type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
134
+
135
+ type CliErrorKind = "general" | "bad-args" | "auth" | "not-found" | "validation";
136
+ declare const CliError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
137
+ readonly _tag: "CliError";
138
+ } & Readonly<A>;
139
+ declare class CliError extends CliError_base<{
140
+ readonly kind: CliErrorKind;
141
+ readonly message: string;
142
+ readonly detail?: string;
143
+ readonly cause?: unknown;
144
+ }> {
145
+ }
146
+ declare function mapErrorToExitCode(error: CliError): ExitCode;
147
+
148
+ /**
149
+ * Reusable CLI option definitions for @effect/cli
150
+ */
151
+
152
+ /**
153
+ * --format csv|json|table
154
+ * Returns Option<OutputFormat> — use resolveFormat() to unwrap.
155
+ */
156
+ declare const formatOption: Options.Options<Option.Option<"json" | "csv" | "table">>;
157
+ /**
158
+ * --json — shorthand for --format json
159
+ */
160
+ declare const jsonFlag: Options.Options<boolean>;
161
+ /**
162
+ * -o / --output <file>
163
+ */
164
+ declare const outputFileOption: Options.Options<Option.Option<string>>;
165
+ /**
166
+ * -i / --input <file>
167
+ */
168
+ declare const inputFileOption: Options.Options<string>;
169
+ /**
170
+ * --verbose
171
+ */
172
+ declare const verboseFlag: Options.Options<boolean>;
173
+ /**
174
+ * Resolve the effective format from --format Option and --json flag.
175
+ * Unwraps the Effect Option type into OutputFormat | undefined.
176
+ */
177
+ declare function resolveFormat(format: Option.Option<"csv" | "json" | "table">, json: boolean): OutputFormat | undefined;
178
+
179
+ /**
180
+ * CLI runner — wraps @effect/cli Command.run with error→exit code mapping,
181
+ * timing, and layer provision.
182
+ */
183
+
184
+ interface RunCliOptions<R, E, Name extends string = string> {
185
+ /** The root @effect/cli Command */
186
+ command: Command.Command<Name, R, E, any>;
187
+ /** Layer that provides all required services (R) */
188
+ layer: Layer.Layer<R>;
189
+ /** CLI name for help text */
190
+ name: string;
191
+ /** CLI version */
192
+ version: string;
193
+ }
194
+ /**
195
+ * Run the CLI with standardized error handling:
196
+ * - CliError → mapped exit code + formatted error output
197
+ * - Unknown errors → exit 1 + error message
198
+ */
199
+ declare function runCli<R, E, Name extends string>(options: RunCliOptions<R, E, Name>): void;
200
+
201
+ /**
202
+ * XDG-compliant config store at ~/.config/emisso/
203
+ *
204
+ * Reads/writes JSON config files. No encryption (that's credential-store).
205
+ */
206
+ /**
207
+ * Read a config value from ~/.config/emisso/<namespace>.json
208
+ */
209
+ declare function readConfig<T>(namespace: string, key: string): T | undefined;
210
+ /**
211
+ * Write a config value to ~/.config/emisso/<namespace>.json
212
+ */
213
+ declare function writeConfig(namespace: string, key: string, value: unknown): void;
214
+ /**
215
+ * Read all config from ~/.config/emisso/<namespace>.json
216
+ */
217
+ declare function readAllConfig(namespace: string): Record<string, unknown> | undefined;
218
+
219
+ export { CliError, type CliErrorKind, type Column, type CsvColumn, type Envelope, type ErrorEnvelope, ExitCode, type OutputConfig, type OutputFormat, OutputRenderer, OutputRendererLive, type RendererService, type RunCliOptions, type SuccessEnvelope, detectFormat, errorEnvelope, formatCsv, formatJson, formatOption, formatTable, inputFileOption, isTTY, jsonFlag, makeTestRenderer, mapErrorToExitCode, outputFileOption, readAllConfig, readConfig, resolveFormat, runCli, successEnvelope, verboseFlag, writeConfig };
@@ -0,0 +1,219 @@
1
+ import { Context, Effect, Layer, Option } from 'effect';
2
+ import * as effect_Cause from 'effect/Cause';
3
+ import * as effect_Types from 'effect/Types';
4
+ import { Options, Command } from '@effect/cli';
5
+
6
+ /**
7
+ * TTY detection and automatic format selection
8
+ *
9
+ * Non-TTY (piped/agent) → JSON envelope
10
+ * TTY (terminal) → command-specific default (table or csv)
11
+ */
12
+ type OutputFormat = "json" | "csv" | "table";
13
+ /**
14
+ * Detect if stdout is a TTY
15
+ */
16
+ declare function isTTY(): boolean;
17
+ /**
18
+ * Auto-detect output format based on TTY status.
19
+ * Non-TTY defaults to JSON (agent-friendly).
20
+ * TTY uses the provided default format.
21
+ */
22
+ declare function detectFormat(explicitFormat: OutputFormat | undefined, ttyDefault?: OutputFormat): OutputFormat;
23
+
24
+ /**
25
+ * JSON envelope wrappers for CLI output
26
+ *
27
+ * Success: { ok: true, data: T, meta?: { duration_ms: number } }
28
+ * Error: { ok: false, error: { message: string, detail?: string } }
29
+ */
30
+ interface SuccessEnvelope<T> {
31
+ ok: true;
32
+ data: T;
33
+ meta?: {
34
+ duration_ms: number;
35
+ };
36
+ }
37
+ interface ErrorEnvelope {
38
+ ok: false;
39
+ error: {
40
+ message: string;
41
+ detail?: string;
42
+ };
43
+ }
44
+ type Envelope<T> = SuccessEnvelope<T> | ErrorEnvelope;
45
+ declare function successEnvelope<T>(data: T, durationMs?: number): SuccessEnvelope<T>;
46
+ declare function errorEnvelope(message: string, detail?: string): ErrorEnvelope;
47
+ declare function formatJson<T>(envelope: Envelope<T>): string;
48
+
49
+ /**
50
+ * Zero-dependency column-aligned TTY table renderer
51
+ */
52
+ interface Column {
53
+ key: string;
54
+ label: string;
55
+ align?: "left" | "right";
56
+ width?: number;
57
+ }
58
+ /**
59
+ * Render rows as a column-aligned text table.
60
+ *
61
+ * Column widths auto-size to fit data unless explicitly set.
62
+ */
63
+ declare function formatTable(columns: Column[], rows: Record<string, unknown>[]): string;
64
+
65
+ /**
66
+ * RFC 4180 CSV serializer — zero dependencies
67
+ */
68
+ interface CsvColumn {
69
+ key: string;
70
+ label: string;
71
+ }
72
+ /**
73
+ * Serialize rows as RFC 4180 CSV (quoted fields, CRLF line endings).
74
+ */
75
+ declare function formatCsv(columns: CsvColumn[], rows: Record<string, unknown>[]): string;
76
+
77
+ /**
78
+ * OutputRenderer — orchestrates format selection and output
79
+ *
80
+ * Provides a unified interface for commands to emit structured output
81
+ * without worrying about format details.
82
+ */
83
+
84
+ interface OutputConfig {
85
+ columns: Column[];
86
+ ttyDefault?: OutputFormat;
87
+ }
88
+ interface RendererService {
89
+ /**
90
+ * Render structured data to stdout in the resolved format.
91
+ * Returns the formatted string (for testing).
92
+ */
93
+ readonly render: (rows: Record<string, unknown>[], config: OutputConfig, opts?: {
94
+ format?: OutputFormat;
95
+ durationMs?: number;
96
+ }) => Effect.Effect<string>;
97
+ /**
98
+ * Render a success envelope (JSON mode only convenience).
99
+ */
100
+ readonly renderSuccess: <T>(data: T, durationMs?: number) => Effect.Effect<string>;
101
+ /**
102
+ * Render an error envelope to stderr.
103
+ */
104
+ readonly renderError: (message: string, detail?: string) => Effect.Effect<string>;
105
+ }
106
+ declare const OutputRenderer_base: Context.TagClass<OutputRenderer, "OutputRenderer", RendererService>;
107
+ declare class OutputRenderer extends OutputRenderer_base {
108
+ }
109
+ /**
110
+ * Live implementation that writes to process.stdout / process.stderr
111
+ */
112
+ declare const OutputRendererLive: Layer.Layer<OutputRenderer, never, never>;
113
+ /**
114
+ * Test implementation that captures output into arrays for assertions
115
+ */
116
+ declare function makeTestRenderer(): {
117
+ stdout: string[];
118
+ stderr: string[];
119
+ layer: Layer.Layer<OutputRenderer>;
120
+ };
121
+
122
+ /**
123
+ * Standardized CLI exit codes for Emisso CLIs
124
+ */
125
+ declare const ExitCode: {
126
+ readonly Success: 0;
127
+ readonly General: 1;
128
+ readonly BadArgs: 2;
129
+ readonly Auth: 3;
130
+ readonly NotFound: 4;
131
+ readonly Validation: 5;
132
+ };
133
+ type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];
134
+
135
+ type CliErrorKind = "general" | "bad-args" | "auth" | "not-found" | "validation";
136
+ declare const CliError_base: new <A extends Record<string, any> = {}>(args: effect_Types.Equals<A, {}> extends true ? void : { readonly [P in keyof A as P extends "_tag" ? never : P]: A[P]; }) => effect_Cause.YieldableError & {
137
+ readonly _tag: "CliError";
138
+ } & Readonly<A>;
139
+ declare class CliError extends CliError_base<{
140
+ readonly kind: CliErrorKind;
141
+ readonly message: string;
142
+ readonly detail?: string;
143
+ readonly cause?: unknown;
144
+ }> {
145
+ }
146
+ declare function mapErrorToExitCode(error: CliError): ExitCode;
147
+
148
+ /**
149
+ * Reusable CLI option definitions for @effect/cli
150
+ */
151
+
152
+ /**
153
+ * --format csv|json|table
154
+ * Returns Option<OutputFormat> — use resolveFormat() to unwrap.
155
+ */
156
+ declare const formatOption: Options.Options<Option.Option<"json" | "csv" | "table">>;
157
+ /**
158
+ * --json — shorthand for --format json
159
+ */
160
+ declare const jsonFlag: Options.Options<boolean>;
161
+ /**
162
+ * -o / --output <file>
163
+ */
164
+ declare const outputFileOption: Options.Options<Option.Option<string>>;
165
+ /**
166
+ * -i / --input <file>
167
+ */
168
+ declare const inputFileOption: Options.Options<string>;
169
+ /**
170
+ * --verbose
171
+ */
172
+ declare const verboseFlag: Options.Options<boolean>;
173
+ /**
174
+ * Resolve the effective format from --format Option and --json flag.
175
+ * Unwraps the Effect Option type into OutputFormat | undefined.
176
+ */
177
+ declare function resolveFormat(format: Option.Option<"csv" | "json" | "table">, json: boolean): OutputFormat | undefined;
178
+
179
+ /**
180
+ * CLI runner — wraps @effect/cli Command.run with error→exit code mapping,
181
+ * timing, and layer provision.
182
+ */
183
+
184
+ interface RunCliOptions<R, E, Name extends string = string> {
185
+ /** The root @effect/cli Command */
186
+ command: Command.Command<Name, R, E, any>;
187
+ /** Layer that provides all required services (R) */
188
+ layer: Layer.Layer<R>;
189
+ /** CLI name for help text */
190
+ name: string;
191
+ /** CLI version */
192
+ version: string;
193
+ }
194
+ /**
195
+ * Run the CLI with standardized error handling:
196
+ * - CliError → mapped exit code + formatted error output
197
+ * - Unknown errors → exit 1 + error message
198
+ */
199
+ declare function runCli<R, E, Name extends string>(options: RunCliOptions<R, E, Name>): void;
200
+
201
+ /**
202
+ * XDG-compliant config store at ~/.config/emisso/
203
+ *
204
+ * Reads/writes JSON config files. No encryption (that's credential-store).
205
+ */
206
+ /**
207
+ * Read a config value from ~/.config/emisso/<namespace>.json
208
+ */
209
+ declare function readConfig<T>(namespace: string, key: string): T | undefined;
210
+ /**
211
+ * Write a config value to ~/.config/emisso/<namespace>.json
212
+ */
213
+ declare function writeConfig(namespace: string, key: string, value: unknown): void;
214
+ /**
215
+ * Read all config from ~/.config/emisso/<namespace>.json
216
+ */
217
+ declare function readAllConfig(namespace: string): Record<string, unknown> | undefined;
218
+
219
+ export { CliError, type CliErrorKind, type Column, type CsvColumn, type Envelope, type ErrorEnvelope, ExitCode, type OutputConfig, type OutputFormat, OutputRenderer, OutputRendererLive, type RendererService, type RunCliOptions, type SuccessEnvelope, detectFormat, errorEnvelope, formatCsv, formatJson, formatOption, formatTable, inputFileOption, isTTY, jsonFlag, makeTestRenderer, mapErrorToExitCode, outputFileOption, readAllConfig, readConfig, resolveFormat, runCli, successEnvelope, verboseFlag, writeConfig };
package/dist/index.js ADDED
@@ -0,0 +1,323 @@
1
+ // src/output/detect.ts
2
+ function isTTY() {
3
+ return process.stdout.isTTY === true;
4
+ }
5
+ function detectFormat(explicitFormat, ttyDefault = "table") {
6
+ if (explicitFormat) return explicitFormat;
7
+ return isTTY() ? ttyDefault : "json";
8
+ }
9
+
10
+ // src/output/json-envelope.ts
11
+ function successEnvelope(data, durationMs) {
12
+ const envelope = { ok: true, data };
13
+ if (durationMs !== void 0) {
14
+ envelope.meta = { duration_ms: Math.round(durationMs) };
15
+ }
16
+ return envelope;
17
+ }
18
+ function errorEnvelope(message, detail) {
19
+ return { ok: false, error: { message, ...detail ? { detail } : {} } };
20
+ }
21
+ function formatJson(envelope) {
22
+ return JSON.stringify(envelope, null, 2);
23
+ }
24
+
25
+ // src/output/table.ts
26
+ function formatTable(columns, rows) {
27
+ if (rows.length === 0) {
28
+ return columns.map((c) => c.label).join(" ") + "\n(no data)";
29
+ }
30
+ const widths = columns.map((col) => {
31
+ if (col.width) return col.width;
32
+ const dataMax = rows.reduce((max, row) => {
33
+ const val = String(row[col.key] ?? "");
34
+ return Math.max(max, val.length);
35
+ }, 0);
36
+ return Math.max(col.label.length, dataMax);
37
+ });
38
+ const header = columns.map((col, i) => pad(col.label, widths[i], col.align ?? "left")).join(" ");
39
+ const separator = widths.map((w) => "\u2500".repeat(w)).join("\u2500\u2500");
40
+ const body = rows.map(
41
+ (row) => columns.map((col, i) => {
42
+ const val = String(row[col.key] ?? "");
43
+ return pad(val, widths[i], col.align ?? "left");
44
+ }).join(" ")
45
+ );
46
+ return [header, separator, ...body].join("\n");
47
+ }
48
+ function pad(str, width, align) {
49
+ if (align === "right") return str.padStart(width);
50
+ return str.padEnd(width);
51
+ }
52
+
53
+ // src/output/csv.ts
54
+ function formatCsv(columns, rows) {
55
+ const header = columns.map((c) => escapeField(c.label)).join(",");
56
+ const body = rows.map(
57
+ (row) => columns.map((c) => escapeField(String(row[c.key] ?? ""))).join(",")
58
+ );
59
+ return [header, ...body].join("\r\n");
60
+ }
61
+ function escapeField(value) {
62
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
63
+ return `"${value.replace(/"/g, '""')}"`;
64
+ }
65
+ return value;
66
+ }
67
+
68
+ // src/output/renderer.ts
69
+ import { Context, Effect, Layer } from "effect";
70
+ var OutputRenderer = class extends Context.Tag("OutputRenderer")() {
71
+ };
72
+ var OutputRendererLive = Layer.succeed(OutputRenderer, {
73
+ render: (rows, config, opts) => Effect.sync(() => {
74
+ const format = detectFormat(opts?.format, config.ttyDefault);
75
+ let output;
76
+ switch (format) {
77
+ case "json":
78
+ output = formatJson(successEnvelope(rows, opts?.durationMs));
79
+ break;
80
+ case "csv":
81
+ output = formatCsv(
82
+ config.columns.map(
83
+ (c) => ({ key: c.key, label: c.label })
84
+ ),
85
+ rows
86
+ );
87
+ break;
88
+ case "table":
89
+ output = formatTable(config.columns, rows);
90
+ break;
91
+ }
92
+ process.stdout.write(output + "\n");
93
+ return output;
94
+ }),
95
+ renderSuccess: (data, durationMs) => Effect.sync(() => {
96
+ const output = formatJson(successEnvelope(data, durationMs));
97
+ process.stdout.write(output + "\n");
98
+ return output;
99
+ }),
100
+ renderError: (message, detail) => Effect.sync(() => {
101
+ const output = formatJson(errorEnvelope(message, detail));
102
+ process.stderr.write(output + "\n");
103
+ return output;
104
+ })
105
+ });
106
+ function makeTestRenderer() {
107
+ const stdout = [];
108
+ const stderr = [];
109
+ const layer = Layer.succeed(OutputRenderer, {
110
+ render: (rows, config, opts) => Effect.sync(() => {
111
+ const format = detectFormat(opts?.format, config.ttyDefault);
112
+ let output;
113
+ switch (format) {
114
+ case "json":
115
+ output = formatJson(successEnvelope(rows, opts?.durationMs));
116
+ break;
117
+ case "csv":
118
+ output = formatCsv(
119
+ config.columns.map(
120
+ (c) => ({ key: c.key, label: c.label })
121
+ ),
122
+ rows
123
+ );
124
+ break;
125
+ case "table":
126
+ output = formatTable(config.columns, rows);
127
+ break;
128
+ }
129
+ stdout.push(output);
130
+ return output;
131
+ }),
132
+ renderSuccess: (data, durationMs) => Effect.sync(() => {
133
+ const output = formatJson(successEnvelope(data, durationMs));
134
+ stdout.push(output);
135
+ return output;
136
+ }),
137
+ renderError: (message, detail) => Effect.sync(() => {
138
+ const output = formatJson(errorEnvelope(message, detail));
139
+ stderr.push(output);
140
+ return output;
141
+ })
142
+ });
143
+ return { stdout, stderr, layer };
144
+ }
145
+
146
+ // src/errors/exit-codes.ts
147
+ var ExitCode = {
148
+ Success: 0,
149
+ General: 1,
150
+ BadArgs: 2,
151
+ Auth: 3,
152
+ NotFound: 4,
153
+ Validation: 5
154
+ };
155
+
156
+ // src/errors/cli-error.ts
157
+ import { Data } from "effect";
158
+ var CliError = class extends Data.TaggedError("CliError") {
159
+ };
160
+ var kindToExitCode = {
161
+ general: ExitCode.General,
162
+ "bad-args": ExitCode.BadArgs,
163
+ auth: ExitCode.Auth,
164
+ "not-found": ExitCode.NotFound,
165
+ validation: ExitCode.Validation
166
+ };
167
+ function mapErrorToExitCode(error) {
168
+ return kindToExitCode[error.kind];
169
+ }
170
+
171
+ // src/options/shared-options.ts
172
+ import { Options } from "@effect/cli";
173
+ import { Option } from "effect";
174
+ var formatOption = Options.choice("format", ["csv", "json", "table"]).pipe(
175
+ Options.optional,
176
+ Options.withDescription("Output format (auto-detected if omitted: json for pipes, table for TTY)")
177
+ );
178
+ var jsonFlag = Options.boolean("json").pipe(
179
+ Options.withDefault(false),
180
+ Options.withDescription("Output as JSON (shorthand for --format json)")
181
+ );
182
+ var outputFileOption = Options.file("output", { exists: "either" }).pipe(
183
+ Options.optional,
184
+ Options.withAlias("o"),
185
+ Options.withDescription("Write output to file instead of stdout")
186
+ );
187
+ var inputFileOption = Options.file("input", { exists: "yes" }).pipe(
188
+ Options.withAlias("i"),
189
+ Options.withDescription("Input JSON file")
190
+ );
191
+ var verboseFlag = Options.boolean("verbose").pipe(
192
+ Options.withDefault(false),
193
+ Options.withDescription("Enable verbose output")
194
+ );
195
+ function resolveFormat(format, json) {
196
+ const f = Option.getOrUndefined(format);
197
+ if (f) return f;
198
+ if (json) return "json";
199
+ return void 0;
200
+ }
201
+
202
+ // src/runner/run-cli.ts
203
+ import { Command } from "@effect/cli";
204
+ import { NodeContext, NodeRuntime } from "@effect/platform-node";
205
+ import { Effect as Effect2, Layer as Layer2 } from "effect";
206
+ function runCli(options) {
207
+ const app = Command.run(options.command, {
208
+ name: options.name,
209
+ version: options.version
210
+ });
211
+ const program = Effect2.suspend(() => app(process.argv)).pipe(
212
+ Effect2.catchAll(
213
+ (error) => Effect2.sync(() => {
214
+ if (error instanceof CliError) {
215
+ const exitCode = mapErrorToExitCode(error);
216
+ if (!isTTY()) {
217
+ process.stderr.write(
218
+ formatJson(errorEnvelope(error.message, error.detail)) + "\n"
219
+ );
220
+ } else {
221
+ process.stderr.write(`Error: ${error.message}
222
+ `);
223
+ if (error.detail) {
224
+ process.stderr.write(` ${error.detail}
225
+ `);
226
+ }
227
+ }
228
+ process.exitCode = exitCode;
229
+ return;
230
+ }
231
+ const message = error instanceof Error ? error.message : String(error);
232
+ if (!isTTY()) {
233
+ process.stderr.write(
234
+ formatJson(errorEnvelope(message)) + "\n"
235
+ );
236
+ } else {
237
+ process.stderr.write(`Error: ${message}
238
+ `);
239
+ }
240
+ process.exitCode = ExitCode.General;
241
+ })
242
+ )
243
+ );
244
+ const layer = Layer2.merge(NodeContext.layer, options.layer);
245
+ const provided = Effect2.provide(program, layer);
246
+ NodeRuntime.runMain(provided);
247
+ }
248
+
249
+ // src/config/config-store.ts
250
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
251
+ import { join } from "path";
252
+ import { homedir } from "os";
253
+ function getConfigDir() {
254
+ const xdg = process.env["XDG_CONFIG_HOME"];
255
+ const base = xdg || join(homedir(), ".config");
256
+ return join(base, "emisso");
257
+ }
258
+ function ensureDir(dir) {
259
+ if (!existsSync(dir)) {
260
+ mkdirSync(dir, { recursive: true });
261
+ }
262
+ }
263
+ function readConfig(namespace, key) {
264
+ const dir = getConfigDir();
265
+ const file = join(dir, `${namespace}.json`);
266
+ if (!existsSync(file)) return void 0;
267
+ try {
268
+ const data = JSON.parse(readFileSync(file, "utf-8"));
269
+ return data[key];
270
+ } catch {
271
+ return void 0;
272
+ }
273
+ }
274
+ function writeConfig(namespace, key, value) {
275
+ const dir = getConfigDir();
276
+ ensureDir(dir);
277
+ const file = join(dir, `${namespace}.json`);
278
+ let data = {};
279
+ if (existsSync(file)) {
280
+ try {
281
+ data = JSON.parse(readFileSync(file, "utf-8"));
282
+ } catch {
283
+ }
284
+ }
285
+ data[key] = value;
286
+ writeFileSync(file, JSON.stringify(data, null, 2) + "\n", "utf-8");
287
+ }
288
+ function readAllConfig(namespace) {
289
+ const dir = getConfigDir();
290
+ const file = join(dir, `${namespace}.json`);
291
+ if (!existsSync(file)) return void 0;
292
+ try {
293
+ return JSON.parse(readFileSync(file, "utf-8"));
294
+ } catch {
295
+ return void 0;
296
+ }
297
+ }
298
+ export {
299
+ CliError,
300
+ ExitCode,
301
+ OutputRenderer,
302
+ OutputRendererLive,
303
+ detectFormat,
304
+ errorEnvelope,
305
+ formatCsv,
306
+ formatJson,
307
+ formatOption,
308
+ formatTable,
309
+ inputFileOption,
310
+ isTTY,
311
+ jsonFlag,
312
+ makeTestRenderer,
313
+ mapErrorToExitCode,
314
+ outputFileOption,
315
+ readAllConfig,
316
+ readConfig,
317
+ resolveFormat,
318
+ runCli,
319
+ successEnvelope,
320
+ verboseFlag,
321
+ writeConfig
322
+ };
323
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/output/detect.ts","../src/output/json-envelope.ts","../src/output/table.ts","../src/output/csv.ts","../src/output/renderer.ts","../src/errors/exit-codes.ts","../src/errors/cli-error.ts","../src/options/shared-options.ts","../src/runner/run-cli.ts","../src/config/config-store.ts"],"sourcesContent":["/**\n * TTY detection and automatic format selection\n *\n * Non-TTY (piped/agent) → JSON envelope\n * TTY (terminal) → command-specific default (table or csv)\n */\n\nexport type OutputFormat = \"json\" | \"csv\" | \"table\";\n\n/**\n * Detect if stdout is a TTY\n */\nexport function isTTY(): boolean {\n return process.stdout.isTTY === true;\n}\n\n/**\n * Auto-detect output format based on TTY status.\n * Non-TTY defaults to JSON (agent-friendly).\n * TTY uses the provided default format.\n */\nexport function detectFormat(\n explicitFormat: OutputFormat | undefined,\n ttyDefault: OutputFormat = \"table\",\n): OutputFormat {\n if (explicitFormat) return explicitFormat;\n return isTTY() ? ttyDefault : \"json\";\n}\n","/**\n * JSON envelope wrappers for CLI output\n *\n * Success: { ok: true, data: T, meta?: { duration_ms: number } }\n * Error: { ok: false, error: { message: string, detail?: string } }\n */\n\nexport interface SuccessEnvelope<T> {\n ok: true;\n data: T;\n meta?: { duration_ms: number };\n}\n\nexport interface ErrorEnvelope {\n ok: false;\n error: { message: string; detail?: string };\n}\n\nexport type Envelope<T> = SuccessEnvelope<T> | ErrorEnvelope;\n\nexport function successEnvelope<T>(\n data: T,\n durationMs?: number,\n): SuccessEnvelope<T> {\n const envelope: SuccessEnvelope<T> = { ok: true, data };\n if (durationMs !== undefined) {\n envelope.meta = { duration_ms: Math.round(durationMs) };\n }\n return envelope;\n}\n\nexport function errorEnvelope(\n message: string,\n detail?: string,\n): ErrorEnvelope {\n return { ok: false, error: { message, ...(detail ? { detail } : {}) } };\n}\n\nexport function formatJson<T>(envelope: Envelope<T>): string {\n return JSON.stringify(envelope, null, 2);\n}\n","/**\n * Zero-dependency column-aligned TTY table renderer\n */\n\nexport interface Column {\n key: string;\n label: string;\n align?: \"left\" | \"right\";\n width?: number;\n}\n\n/**\n * Render rows as a column-aligned text table.\n *\n * Column widths auto-size to fit data unless explicitly set.\n */\nexport function formatTable(\n columns: Column[],\n rows: Record<string, unknown>[],\n): string {\n if (rows.length === 0) {\n return columns.map((c) => c.label).join(\" \") + \"\\n(no data)\";\n }\n\n // Calculate widths\n const widths = columns.map((col) => {\n if (col.width) return col.width;\n const dataMax = rows.reduce((max, row) => {\n const val = String(row[col.key] ?? \"\");\n return Math.max(max, val.length);\n }, 0);\n return Math.max(col.label.length, dataMax);\n });\n\n // Header\n const header = columns\n .map((col, i) => pad(col.label, widths[i], col.align ?? \"left\"))\n .join(\" \");\n\n const separator = widths.map((w) => \"─\".repeat(w)).join(\"──\");\n\n // Rows\n const body = rows.map((row) =>\n columns\n .map((col, i) => {\n const val = String(row[col.key] ?? \"\");\n return pad(val, widths[i], col.align ?? \"left\");\n })\n .join(\" \"),\n );\n\n return [header, separator, ...body].join(\"\\n\");\n}\n\nfunction pad(str: string, width: number, align: \"left\" | \"right\"): string {\n if (align === \"right\") return str.padStart(width);\n return str.padEnd(width);\n}\n","/**\n * RFC 4180 CSV serializer — zero dependencies\n */\n\nexport interface CsvColumn {\n key: string;\n label: string;\n}\n\n/**\n * Serialize rows as RFC 4180 CSV (quoted fields, CRLF line endings).\n */\nexport function formatCsv(\n columns: CsvColumn[],\n rows: Record<string, unknown>[],\n): string {\n const header = columns.map((c) => escapeField(c.label)).join(\",\");\n const body = rows.map((row) =>\n columns.map((c) => escapeField(String(row[c.key] ?? \"\"))).join(\",\"),\n );\n return [header, ...body].join(\"\\r\\n\");\n}\n\nfunction escapeField(value: string): string {\n if (value.includes(\",\") || value.includes('\"') || value.includes(\"\\n\")) {\n return `\"${value.replace(/\"/g, '\"\"')}\"`;\n }\n return value;\n}\n","/**\n * OutputRenderer — orchestrates format selection and output\n *\n * Provides a unified interface for commands to emit structured output\n * without worrying about format details.\n */\n\nimport { Context, Effect, Layer } from \"effect\";\nimport type { OutputFormat } from \"./detect.js\";\nimport { detectFormat } from \"./detect.js\";\nimport {\n successEnvelope,\n errorEnvelope,\n formatJson,\n} from \"./json-envelope.js\";\nimport { formatTable, type Column } from \"./table.js\";\nimport { formatCsv, type CsvColumn } from \"./csv.js\";\n\nexport interface OutputConfig {\n columns: Column[];\n ttyDefault?: OutputFormat;\n}\n\nexport interface RendererService {\n /**\n * Render structured data to stdout in the resolved format.\n * Returns the formatted string (for testing).\n */\n readonly render: (\n rows: Record<string, unknown>[],\n config: OutputConfig,\n opts?: { format?: OutputFormat; durationMs?: number },\n ) => Effect.Effect<string>;\n\n /**\n * Render a success envelope (JSON mode only convenience).\n */\n readonly renderSuccess: <T>(\n data: T,\n durationMs?: number,\n ) => Effect.Effect<string>;\n\n /**\n * Render an error envelope to stderr.\n */\n readonly renderError: (\n message: string,\n detail?: string,\n ) => Effect.Effect<string>;\n}\n\nexport class OutputRenderer extends Context.Tag(\"OutputRenderer\")<\n OutputRenderer,\n RendererService\n>() {}\n\n/**\n * Live implementation that writes to process.stdout / process.stderr\n */\nexport const OutputRendererLive = Layer.succeed(OutputRenderer, {\n render: (rows, config, opts) =>\n Effect.sync(() => {\n const format = detectFormat(opts?.format, config.ttyDefault);\n let output: string;\n\n switch (format) {\n case \"json\":\n output = formatJson(successEnvelope(rows, opts?.durationMs));\n break;\n case \"csv\":\n output = formatCsv(\n config.columns.map(\n (c): CsvColumn => ({ key: c.key, label: c.label }),\n ),\n rows,\n );\n break;\n case \"table\":\n output = formatTable(config.columns, rows);\n break;\n }\n\n process.stdout.write(output + \"\\n\");\n return output;\n }),\n\n renderSuccess: (data, durationMs) =>\n Effect.sync(() => {\n const output = formatJson(successEnvelope(data, durationMs));\n process.stdout.write(output + \"\\n\");\n return output;\n }),\n\n renderError: (message, detail) =>\n Effect.sync(() => {\n const output = formatJson(errorEnvelope(message, detail));\n process.stderr.write(output + \"\\n\");\n return output;\n }),\n});\n\n/**\n * Test implementation that captures output into arrays for assertions\n */\nexport function makeTestRenderer(): {\n stdout: string[];\n stderr: string[];\n layer: Layer.Layer<OutputRenderer>;\n} {\n const stdout: string[] = [];\n const stderr: string[] = [];\n\n const layer = Layer.succeed(OutputRenderer, {\n render: (rows, config, opts) =>\n Effect.sync(() => {\n const format = detectFormat(opts?.format, config.ttyDefault);\n let output: string;\n\n switch (format) {\n case \"json\":\n output = formatJson(successEnvelope(rows, opts?.durationMs));\n break;\n case \"csv\":\n output = formatCsv(\n config.columns.map(\n (c): CsvColumn => ({ key: c.key, label: c.label }),\n ),\n rows,\n );\n break;\n case \"table\":\n output = formatTable(config.columns, rows);\n break;\n }\n\n stdout.push(output);\n return output;\n }),\n\n renderSuccess: (data, durationMs) =>\n Effect.sync(() => {\n const output = formatJson(successEnvelope(data, durationMs));\n stdout.push(output);\n return output;\n }),\n\n renderError: (message, detail) =>\n Effect.sync(() => {\n const output = formatJson(errorEnvelope(message, detail));\n stderr.push(output);\n return output;\n }),\n });\n\n return { stdout, stderr, layer };\n}\n","/**\n * Standardized CLI exit codes for Emisso CLIs\n */\n\nexport const ExitCode = {\n Success: 0,\n General: 1,\n BadArgs: 2,\n Auth: 3,\n NotFound: 4,\n Validation: 5,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n","/**\n * Typed CLI error with exit code mapping\n */\n\nimport { Data } from \"effect\";\nimport { ExitCode } from \"./exit-codes.js\";\n\nexport type CliErrorKind =\n | \"general\"\n | \"bad-args\"\n | \"auth\"\n | \"not-found\"\n | \"validation\";\n\nexport class CliError extends Data.TaggedError(\"CliError\")<{\n readonly kind: CliErrorKind;\n readonly message: string;\n readonly detail?: string;\n readonly cause?: unknown;\n}> {}\n\nconst kindToExitCode: Record<CliErrorKind, ExitCode> = {\n general: ExitCode.General,\n \"bad-args\": ExitCode.BadArgs,\n auth: ExitCode.Auth,\n \"not-found\": ExitCode.NotFound,\n validation: ExitCode.Validation,\n};\n\nexport function mapErrorToExitCode(error: CliError): ExitCode {\n return kindToExitCode[error.kind];\n}\n","/**\n * Reusable CLI option definitions for @effect/cli\n */\n\nimport { Options } from \"@effect/cli\";\nimport { Option } from \"effect\";\nimport type { OutputFormat } from \"../output/detect.js\";\n\n/**\n * --format csv|json|table\n * Returns Option<OutputFormat> — use resolveFormat() to unwrap.\n */\nexport const formatOption = Options.choice(\"format\", [\"csv\", \"json\", \"table\"]).pipe(\n Options.optional,\n Options.withDescription(\"Output format (auto-detected if omitted: json for pipes, table for TTY)\"),\n);\n\n/**\n * --json — shorthand for --format json\n */\nexport const jsonFlag = Options.boolean(\"json\").pipe(\n Options.withDefault(false),\n Options.withDescription(\"Output as JSON (shorthand for --format json)\"),\n);\n\n/**\n * -o / --output <file>\n */\nexport const outputFileOption = Options.file(\"output\", { exists: \"either\" }).pipe(\n Options.optional,\n Options.withAlias(\"o\"),\n Options.withDescription(\"Write output to file instead of stdout\"),\n);\n\n/**\n * -i / --input <file>\n */\nexport const inputFileOption = Options.file(\"input\", { exists: \"yes\" }).pipe(\n Options.withAlias(\"i\"),\n Options.withDescription(\"Input JSON file\"),\n);\n\n/**\n * --verbose\n */\nexport const verboseFlag = Options.boolean(\"verbose\").pipe(\n Options.withDefault(false),\n Options.withDescription(\"Enable verbose output\"),\n);\n\n/**\n * Resolve the effective format from --format Option and --json flag.\n * Unwraps the Effect Option type into OutputFormat | undefined.\n */\nexport function resolveFormat(\n format: Option.Option<\"csv\" | \"json\" | \"table\">,\n json: boolean,\n): OutputFormat | undefined {\n const f = Option.getOrUndefined(format);\n if (f) return f;\n if (json) return \"json\";\n return undefined;\n}\n","/**\n * CLI runner — wraps @effect/cli Command.run with error→exit code mapping,\n * timing, and layer provision.\n */\n\nimport { Command } from \"@effect/cli\";\nimport { NodeContext, NodeRuntime } from \"@effect/platform-node\";\nimport { Effect, Layer } from \"effect\";\nimport { CliError, mapErrorToExitCode } from \"../errors/cli-error.js\";\nimport { ExitCode } from \"../errors/exit-codes.js\";\nimport { errorEnvelope, formatJson } from \"../output/json-envelope.js\";\nimport { isTTY } from \"../output/detect.js\";\n\nexport interface RunCliOptions<R, E, Name extends string = string> {\n /** The root @effect/cli Command */\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n command: Command.Command<Name, R, E, any>;\n /** Layer that provides all required services (R) */\n layer: Layer.Layer<R>;\n /** CLI name for help text */\n name: string;\n /** CLI version */\n version: string;\n}\n\n/**\n * Run the CLI with standardized error handling:\n * - CliError → mapped exit code + formatted error output\n * - Unknown errors → exit 1 + error message\n */\nexport function runCli<R, E, Name extends string>(options: RunCliOptions<R, E, Name>): void {\n const app = Command.run(options.command, {\n name: options.name,\n version: options.version,\n });\n\n const program = Effect.suspend(() => app(process.argv)).pipe(\n Effect.catchAll((error: unknown) =>\n Effect.sync(() => {\n if (error instanceof CliError) {\n const exitCode = mapErrorToExitCode(error);\n if (!isTTY()) {\n process.stderr.write(\n formatJson(errorEnvelope(error.message, error.detail)) + \"\\n\",\n );\n } else {\n process.stderr.write(`Error: ${error.message}\\n`);\n if (error.detail) {\n process.stderr.write(` ${error.detail}\\n`);\n }\n }\n process.exitCode = exitCode;\n return;\n }\n\n const message =\n error instanceof Error ? error.message : String(error);\n if (!isTTY()) {\n process.stderr.write(\n formatJson(errorEnvelope(message)) + \"\\n\",\n );\n } else {\n process.stderr.write(`Error: ${message}\\n`);\n }\n process.exitCode = ExitCode.General;\n }),\n ),\n );\n\n const layer = Layer.merge(NodeContext.layer, options.layer);\n const provided = Effect.provide(program, layer);\n\n NodeRuntime.runMain(provided as Effect.Effect<void>);\n}\n","/**\n * XDG-compliant config store at ~/.config/emisso/\n *\n * Reads/writes JSON config files. No encryption (that's credential-store).\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nfunction getConfigDir(): string {\n const xdg = process.env[\"XDG_CONFIG_HOME\"];\n const base = xdg || join(homedir(), \".config\");\n return join(base, \"emisso\");\n}\n\nfunction ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true });\n }\n}\n\n/**\n * Read a config value from ~/.config/emisso/<namespace>.json\n */\nexport function readConfig<T>(namespace: string, key: string): T | undefined {\n const dir = getConfigDir();\n const file = join(dir, `${namespace}.json`);\n if (!existsSync(file)) return undefined;\n\n try {\n const data = JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n return data[key] as T | undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write a config value to ~/.config/emisso/<namespace>.json\n */\nexport function writeConfig(\n namespace: string,\n key: string,\n value: unknown,\n): void {\n const dir = getConfigDir();\n ensureDir(dir);\n const file = join(dir, `${namespace}.json`);\n\n let data: Record<string, unknown> = {};\n if (existsSync(file)) {\n try {\n data = JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n // Corrupted file — start fresh\n }\n }\n\n data[key] = value;\n writeFileSync(file, JSON.stringify(data, null, 2) + \"\\n\", \"utf-8\");\n}\n\n/**\n * Read all config from ~/.config/emisso/<namespace>.json\n */\nexport function readAllConfig(\n namespace: string,\n): Record<string, unknown> | undefined {\n const dir = getConfigDir();\n const file = join(dir, `${namespace}.json`);\n if (!existsSync(file)) return undefined;\n\n try {\n return JSON.parse(readFileSync(file, \"utf-8\")) as Record<string, unknown>;\n } catch {\n return undefined;\n }\n}\n"],"mappings":"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","names":["Effect","Layer","Effect","Layer"]}
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@emisso/cli-core",
3
+ "version": "0.1.0",
4
+ "description": "Shared CLI infrastructure for Emisso SDK command-line tools",
5
+ "type": "module",
6
+ "main": "./dist/index.cjs",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": ["dist"],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "dev": "tsup --watch",
20
+ "test": "vitest",
21
+ "test:run": "vitest --run",
22
+ "lint": "tsc --noEmit",
23
+ "prepublishOnly": "npm run build"
24
+ },
25
+ "keywords": [
26
+ "cli", "emisso", "effect", "output", "sdk", "typescript"
27
+ ],
28
+ "author": "Emisso <hello@emisso.ai>",
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "https://github.com/emisso-ai/emisso-cli-core"
33
+ },
34
+ "publishConfig": {
35
+ "access": "public"
36
+ },
37
+ "engines": {
38
+ "node": ">=18"
39
+ },
40
+ "dependencies": {
41
+ "effect": "^3.19.16",
42
+ "@effect/cli": "^0.73.0",
43
+ "@effect/platform": "^0.94.3",
44
+ "@effect/platform-node": "^0.104.0",
45
+ "@effect/printer": "^0.47.0",
46
+ "@effect/printer-ansi": "^0.47.0"
47
+ },
48
+ "devDependencies": {
49
+ "@types/node": "^22.0.0",
50
+ "tsup": "^8.3.0",
51
+ "typescript": "^5.7.0",
52
+ "vitest": "^2.1.0"
53
+ }
54
+ }