@outfitter/cli 0.1.0-rc.1 → 0.1.0-rc.2
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/actions.d.ts +1 -12
- package/dist/actions.js +3 -170
- package/dist/cli.d.ts +2 -103
- package/dist/cli.js +4 -51
- package/dist/command.d.ts +2 -73
- package/dist/command.js +4 -42
- package/dist/index.d.ts +7 -606
- package/dist/index.js +11 -11
- package/dist/input.d.ts +2 -277
- package/dist/input.js +11 -426
- package/dist/output.d.ts +2 -68
- package/dist/output.js +4 -150
- package/dist/pagination.d.ts +2 -95
- package/dist/pagination.js +6 -84
- package/dist/render/colors.d.ts +2 -0
- package/dist/render/colors.js +17 -0
- package/dist/render/date.d.ts +2 -0
- package/dist/render/date.js +12 -0
- package/dist/render/format-relative.d.ts +2 -0
- package/dist/render/format-relative.js +8 -0
- package/dist/render/format.d.ts +2 -0
- package/dist/render/format.js +10 -0
- package/dist/render/index.d.ts +13 -0
- package/dist/render/index.js +101 -0
- package/dist/render/json.d.ts +2 -0
- package/dist/render/json.js +10 -0
- package/dist/render/list.d.ts +2 -0
- package/dist/render/list.js +8 -0
- package/dist/render/markdown.d.ts +2 -0
- package/dist/render/markdown.js +10 -0
- package/dist/render/progress.d.ts +2 -0
- package/dist/render/progress.js +8 -0
- package/dist/render/shapes.d.ts +2 -0
- package/dist/render/shapes.js +34 -0
- package/dist/render/table.d.ts +2 -0
- package/dist/render/table.js +11 -0
- package/dist/render/text.d.ts +2 -0
- package/dist/render/text.js +24 -0
- package/dist/render/tree.d.ts +2 -0
- package/dist/render/tree.js +8 -0
- package/dist/shared/@outfitter/cli-2vs2gxa8.js +429 -0
- package/dist/shared/@outfitter/cli-2y3kxew8.d.ts +58 -0
- package/dist/shared/@outfitter/cli-2yq94zst.d.ts +39 -0
- package/dist/shared/@outfitter/cli-33e97cjs.d.ts +42 -0
- package/dist/shared/@outfitter/cli-3hp8qwx3.js +11 -0
- package/dist/shared/@outfitter/cli-6xc869x1.js +26 -0
- package/dist/shared/@outfitter/cli-72kg550t.d.ts +53 -0
- package/dist/shared/@outfitter/cli-7km1e58p.js +85 -0
- package/dist/shared/@outfitter/cli-7na6p4fs.d.ts +59 -0
- package/dist/shared/@outfitter/cli-8aa1vhdn.d.ts +119 -0
- package/dist/shared/@outfitter/cli-8j5k6mr3.js +71 -0
- package/dist/shared/@outfitter/cli-8r0bnyyx.js +43 -0
- package/dist/shared/@outfitter/cli-a4q87517.d.ts +64 -0
- package/dist/shared/@outfitter/cli-afecwfrn.d.ts +13 -0
- package/dist/shared/@outfitter/cli-ag0w4pk0.js +89 -0
- package/dist/shared/@outfitter/cli-azzk8a1d.js +59 -0
- package/dist/shared/@outfitter/cli-bc17qeh2.js +19 -0
- package/dist/shared/@outfitter/cli-bt423m6y.js +4 -0
- package/dist/shared/@outfitter/cli-c8q4f71g.js +144 -0
- package/dist/shared/@outfitter/cli-c9knfqn5.d.ts +30 -0
- package/dist/shared/@outfitter/cli-cf2s94s1.d.ts +42 -0
- package/dist/shared/@outfitter/cli-d4fegbad.d.ts +66 -0
- package/dist/shared/@outfitter/cli-dds0qqvm.d.ts +145 -0
- package/dist/shared/@outfitter/cli-e0ecw3x1.js +64 -0
- package/dist/shared/@outfitter/cli-efy6jfcj.js +52 -0
- package/dist/shared/@outfitter/cli-evx7qcp1.d.ts +300 -0
- package/dist/shared/@outfitter/cli-fheaaa6b.js +25 -0
- package/dist/shared/@outfitter/cli-j19a91ck.js +30 -0
- package/dist/shared/@outfitter/cli-j361wp3g.d.ts +41 -0
- package/dist/shared/@outfitter/cli-jbj78ac5.js +70 -0
- package/dist/shared/@outfitter/cli-mhamvbty.d.ts +34 -0
- package/dist/shared/@outfitter/cli-n51t773m.d.ts +208 -0
- package/dist/shared/@outfitter/cli-p0m2fc51.js +172 -0
- package/dist/shared/@outfitter/cli-ttt7r0j7.d.ts +253 -0
- package/dist/shared/@outfitter/cli-tyajr8qa.d.ts +63 -0
- package/dist/shared/@outfitter/cli-v9mjsvjh.js +118 -0
- package/dist/shared/@outfitter/cli-wqc652mx.js +135 -0
- package/dist/shared/@outfitter/cli-zact3325.js +152 -0
- package/dist/shared/@outfitter/cli-zs6jy1am.d.ts +164 -0
- package/dist/shared/@outfitter/cli-zx598p8q.d.ts +26 -0
- package/dist/terminal/detection.d.ts +2 -0
- package/dist/terminal/detection.js +20 -0
- package/dist/terminal/index.d.ts +2 -0
- package/dist/terminal/index.js +20 -0
- package/dist/types.d.ts +1 -252
- package/dist/types.js +1 -1
- package/package.json +98 -4
- package/dist/shared/@outfitter/cli-4yy82cmp.js +0 -20
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/pagination.ts
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import os from "os";
|
|
5
|
+
import path from "path";
|
|
6
|
+
var UNSAFE_PATH_PATTERN = /(?:^|[\\/])\.\.(?:[\\/]|$)|^[\\/]|^[a-zA-Z]:/;
|
|
7
|
+
function validatePathComponent(component, name) {
|
|
8
|
+
if (UNSAFE_PATH_PATTERN.test(component)) {
|
|
9
|
+
throw new Error(`Security: path traversal detected in ${name}`);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function getDefaultStateHome() {
|
|
13
|
+
const home = os.homedir();
|
|
14
|
+
switch (process.platform) {
|
|
15
|
+
case "darwin":
|
|
16
|
+
return path.join(home, "Library", "Application Support");
|
|
17
|
+
case "win32":
|
|
18
|
+
return process.env["LOCALAPPDATA"] ?? path.join(home, "AppData", "Local");
|
|
19
|
+
default:
|
|
20
|
+
return path.join(home, ".local", "state");
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function getStatePath(options) {
|
|
24
|
+
validatePathComponent(options.command, "command");
|
|
25
|
+
validatePathComponent(options.toolName, "toolName");
|
|
26
|
+
if (options.context) {
|
|
27
|
+
validatePathComponent(options.context, "context");
|
|
28
|
+
}
|
|
29
|
+
const xdgState = process.env["XDG_STATE_HOME"] ?? getDefaultStateHome();
|
|
30
|
+
const parts = [xdgState, options.toolName, "cursors", options.command];
|
|
31
|
+
if (options.context) {
|
|
32
|
+
parts.push(options.context);
|
|
33
|
+
}
|
|
34
|
+
return path.join(...parts, "cursor.json");
|
|
35
|
+
}
|
|
36
|
+
function loadCursor(options) {
|
|
37
|
+
const statePath = getStatePath(options);
|
|
38
|
+
if (!fs.existsSync(statePath)) {
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const content = fs.readFileSync(statePath, "utf-8");
|
|
43
|
+
const parsed = JSON.parse(content);
|
|
44
|
+
if (typeof parsed !== "object" || parsed === null || typeof parsed["cursor"] !== "string") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const state = parsed;
|
|
48
|
+
if (options.maxAgeMs !== undefined) {
|
|
49
|
+
if (typeof state.timestamp !== "number") {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
const ageMs = Date.now() - state.timestamp;
|
|
53
|
+
if (ageMs > options.maxAgeMs) {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return state;
|
|
58
|
+
} catch {
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function saveCursor(cursor, options) {
|
|
63
|
+
const statePath = getStatePath(options);
|
|
64
|
+
const stateDir = path.dirname(statePath);
|
|
65
|
+
fs.mkdirSync(stateDir, { recursive: true });
|
|
66
|
+
const state = {
|
|
67
|
+
cursor,
|
|
68
|
+
command: options.command,
|
|
69
|
+
timestamp: Date.now(),
|
|
70
|
+
hasMore: options.hasMore ?? true,
|
|
71
|
+
...options.context && { context: options.context },
|
|
72
|
+
...options.total !== undefined && { total: options.total }
|
|
73
|
+
};
|
|
74
|
+
fs.writeFileSync(statePath, JSON.stringify(state), "utf-8");
|
|
75
|
+
}
|
|
76
|
+
function clearCursor(options) {
|
|
77
|
+
const statePath = getStatePath(options);
|
|
78
|
+
try {
|
|
79
|
+
if (fs.existsSync(statePath)) {
|
|
80
|
+
fs.unlinkSync(statePath);
|
|
81
|
+
}
|
|
82
|
+
} catch {}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export { loadCursor, saveCursor, clearCursor };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Progress bar rendering utilities.
|
|
3
|
+
*
|
|
4
|
+
* Renders progress bars with filled and empty segments.
|
|
5
|
+
*
|
|
6
|
+
* @packageDocumentation
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Configuration options for {@link renderProgress}.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```typescript
|
|
13
|
+
* const options: ProgressOptions = {
|
|
14
|
+
* current: 75,
|
|
15
|
+
* total: 100,
|
|
16
|
+
* width: 20,
|
|
17
|
+
* showPercent: true,
|
|
18
|
+
* };
|
|
19
|
+
* // Renders: [###############.....] 75%
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
interface ProgressOptions {
|
|
23
|
+
/** Current progress value */
|
|
24
|
+
current: number;
|
|
25
|
+
/** Total value (100% when current equals total) */
|
|
26
|
+
total: number;
|
|
27
|
+
/** Width of the progress bar in characters (default: 20) */
|
|
28
|
+
width?: number;
|
|
29
|
+
/** Whether to show percentage after the bar (default: false) */
|
|
30
|
+
showPercent?: boolean;
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Renders a progress bar with filled and empty segments.
|
|
34
|
+
*
|
|
35
|
+
* Uses unicode block characters: filled segments, empty segments.
|
|
36
|
+
* Optionally displays percentage after the bar.
|
|
37
|
+
*
|
|
38
|
+
* Handles edge cases:
|
|
39
|
+
* - `total <= 0`: Returns empty bar with 0%
|
|
40
|
+
* - `current > total`: Caps at 100%
|
|
41
|
+
* - `current < 0`: Floors at 0%
|
|
42
|
+
*
|
|
43
|
+
* @param options - Progress bar configuration
|
|
44
|
+
* @returns Formatted progress bar string
|
|
45
|
+
*
|
|
46
|
+
* @example
|
|
47
|
+
* ```typescript
|
|
48
|
+
* renderProgress({ current: 50, total: 100 });
|
|
49
|
+
* // [##########..........]
|
|
50
|
+
*
|
|
51
|
+
* renderProgress({ current: 75, total: 100, showPercent: true });
|
|
52
|
+
* // [###############.....] 75%
|
|
53
|
+
*
|
|
54
|
+
* renderProgress({ current: 30, total: 100, width: 10 });
|
|
55
|
+
* // [###.......]
|
|
56
|
+
* ```
|
|
57
|
+
*/
|
|
58
|
+
declare function renderProgress(options: ProgressOptions): string;
|
|
59
|
+
export { ProgressOptions, renderProgress };
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Options for terminal detection functions.
|
|
3
|
+
*
|
|
4
|
+
* These options allow overriding automatic detection for testing
|
|
5
|
+
* or specific environments.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```typescript
|
|
9
|
+
* // Force TTY mode for testing
|
|
10
|
+
* supportsColor({ isTTY: true });
|
|
11
|
+
*
|
|
12
|
+
* // Simulate CI environment
|
|
13
|
+
* isInteractive({ isTTY: true, isCI: true }); // returns false
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
interface TerminalOptions {
|
|
17
|
+
/** Override TTY detection (uses process.stdout.isTTY if not specified) */
|
|
18
|
+
isTTY?: boolean;
|
|
19
|
+
/** Override CI detection (uses CI env variable if not specified) */
|
|
20
|
+
isCI?: boolean;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Gets the value of an environment variable.
|
|
24
|
+
*
|
|
25
|
+
* @param key - Environment variable name
|
|
26
|
+
* @returns The value if set, undefined otherwise
|
|
27
|
+
*/
|
|
28
|
+
declare function getEnvValue(key: "NO_COLOR" | "FORCE_COLOR"): string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Checks if NO_COLOR environment variable is set.
|
|
31
|
+
*
|
|
32
|
+
* @returns `true` if NO_COLOR is set (even if empty)
|
|
33
|
+
*/
|
|
34
|
+
declare function hasNoColorEnv(): boolean;
|
|
35
|
+
/**
|
|
36
|
+
* Resolves the FORCE_COLOR environment variable.
|
|
37
|
+
*
|
|
38
|
+
* @returns `true` if colors should be forced, `false` if explicitly disabled, `undefined` if not set
|
|
39
|
+
*/
|
|
40
|
+
declare function resolveForceColorEnv(): boolean | undefined;
|
|
41
|
+
/**
|
|
42
|
+
* Resolves color environment variables with configurable priority.
|
|
43
|
+
*
|
|
44
|
+
* @param options - Options for resolution priority
|
|
45
|
+
* @returns `true` if colors enabled, `false` if disabled, `undefined` if not determined by env
|
|
46
|
+
*/
|
|
47
|
+
declare function resolveColorEnv(options?: {
|
|
48
|
+
forceColorFirst?: boolean;
|
|
49
|
+
}): boolean | undefined;
|
|
50
|
+
/**
|
|
51
|
+
* Checks if the current environment supports ANSI colors.
|
|
52
|
+
*
|
|
53
|
+
* Detection priority:
|
|
54
|
+
* 1. `NO_COLOR` env variable - if set (even empty), returns false (per no-color.org)
|
|
55
|
+
* 2. `FORCE_COLOR` env variable - if set, returns true (numeric levels > 0)
|
|
56
|
+
* 3. Falls back to TTY detection via `process.stdout.isTTY`
|
|
57
|
+
*
|
|
58
|
+
* @param options - Optional overrides for TTY detection
|
|
59
|
+
* @returns `true` if colors are supported, `false` otherwise
|
|
60
|
+
*
|
|
61
|
+
* @example
|
|
62
|
+
* ```typescript
|
|
63
|
+
* if (supportsColor()) {
|
|
64
|
+
* console.log("\x1b[32mGreen text\x1b[0m");
|
|
65
|
+
* } else {
|
|
66
|
+
* console.log("Plain text");
|
|
67
|
+
* }
|
|
68
|
+
*
|
|
69
|
+
* // With explicit TTY override for testing
|
|
70
|
+
* supportsColor({ isTTY: true }); // true (unless NO_COLOR is set)
|
|
71
|
+
* supportsColor({ isTTY: false }); // false (unless FORCE_COLOR is set)
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
declare function supportsColor(options?: TerminalOptions): boolean;
|
|
75
|
+
/**
|
|
76
|
+
* Gets the terminal width in columns.
|
|
77
|
+
*
|
|
78
|
+
* Returns `process.stdout.columns` when in a TTY, or 80 as a fallback
|
|
79
|
+
* for non-TTY environments (pipes, CI, etc.).
|
|
80
|
+
*
|
|
81
|
+
* @param options - Optional overrides for TTY detection
|
|
82
|
+
* @returns Terminal width in columns (default: 80 if not TTY)
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```typescript
|
|
86
|
+
* const width = getTerminalWidth();
|
|
87
|
+
* console.log(`Terminal is ${width} columns wide`);
|
|
88
|
+
*
|
|
89
|
+
* // Force non-TTY mode (always returns 80)
|
|
90
|
+
* getTerminalWidth({ isTTY: false }); // 80
|
|
91
|
+
* ```
|
|
92
|
+
*/
|
|
93
|
+
declare function getTerminalWidth(options?: TerminalOptions): number;
|
|
94
|
+
/**
|
|
95
|
+
* Checks if the terminal is interactive.
|
|
96
|
+
*
|
|
97
|
+
* A terminal is considered interactive when:
|
|
98
|
+
* - It is a TTY (process.stdout.isTTY is true)
|
|
99
|
+
* - It is NOT running in a CI environment (CI env variable is not set)
|
|
100
|
+
*
|
|
101
|
+
* Use this to decide whether to show interactive prompts or use
|
|
102
|
+
* non-interactive fallbacks.
|
|
103
|
+
*
|
|
104
|
+
* @param options - Optional overrides for TTY and CI detection
|
|
105
|
+
* @returns `true` if interactive prompts are safe to use
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* if (isInteractive()) {
|
|
110
|
+
* // Safe to use interactive prompts
|
|
111
|
+
* const answer = await prompt("Continue?");
|
|
112
|
+
* } else {
|
|
113
|
+
* // Use non-interactive defaults
|
|
114
|
+
* console.log("Running in non-interactive mode");
|
|
115
|
+
* }
|
|
116
|
+
* ```
|
|
117
|
+
*/
|
|
118
|
+
declare function isInteractive(options?: TerminalOptions): boolean;
|
|
119
|
+
export { TerminalOptions, getEnvValue, hasNoColorEnv, resolveForceColorEnv, resolveColorEnv, supportsColor, getTerminalWidth, isInteractive };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/render/format-relative.ts
|
|
3
|
+
function formatRelative(date) {
|
|
4
|
+
let timestamp;
|
|
5
|
+
if (date instanceof Date) {
|
|
6
|
+
timestamp = date.getTime();
|
|
7
|
+
} else if (typeof date === "number") {
|
|
8
|
+
timestamp = date;
|
|
9
|
+
} else {
|
|
10
|
+
const parsed = Date.parse(date);
|
|
11
|
+
if (Number.isNaN(parsed)) {
|
|
12
|
+
return "invalid date";
|
|
13
|
+
}
|
|
14
|
+
timestamp = parsed;
|
|
15
|
+
}
|
|
16
|
+
if (!Number.isFinite(timestamp)) {
|
|
17
|
+
return "invalid date";
|
|
18
|
+
}
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const diffMs = now - timestamp;
|
|
21
|
+
const absDiffMs = Math.abs(diffMs);
|
|
22
|
+
const isFuture = diffMs < 0;
|
|
23
|
+
const SECOND = 1000;
|
|
24
|
+
const MINUTE = 60 * SECOND;
|
|
25
|
+
const HOUR = 60 * MINUTE;
|
|
26
|
+
const DAY = 24 * HOUR;
|
|
27
|
+
const MONTH = 30 * DAY;
|
|
28
|
+
const YEAR = 365 * DAY;
|
|
29
|
+
if (absDiffMs < 10 * SECOND) {
|
|
30
|
+
return "just now";
|
|
31
|
+
}
|
|
32
|
+
if (absDiffMs < MINUTE) {
|
|
33
|
+
const seconds = Math.floor(absDiffMs / SECOND);
|
|
34
|
+
return isFuture ? `in ${seconds} seconds` : `${seconds} seconds ago`;
|
|
35
|
+
}
|
|
36
|
+
if (absDiffMs < HOUR) {
|
|
37
|
+
const minutes = Math.floor(absDiffMs / MINUTE);
|
|
38
|
+
if (minutes === 1) {
|
|
39
|
+
return isFuture ? "in 1 minute" : "1 minute ago";
|
|
40
|
+
}
|
|
41
|
+
return isFuture ? `in ${minutes} minutes` : `${minutes} minutes ago`;
|
|
42
|
+
}
|
|
43
|
+
if (absDiffMs < DAY) {
|
|
44
|
+
const hours = Math.floor(absDiffMs / HOUR);
|
|
45
|
+
if (hours === 1) {
|
|
46
|
+
return isFuture ? "in 1 hour" : "1 hour ago";
|
|
47
|
+
}
|
|
48
|
+
return isFuture ? `in ${hours} hours` : `${hours} hours ago`;
|
|
49
|
+
}
|
|
50
|
+
if (absDiffMs < 2 * DAY) {
|
|
51
|
+
return isFuture ? "tomorrow" : "yesterday";
|
|
52
|
+
}
|
|
53
|
+
if (absDiffMs < MONTH) {
|
|
54
|
+
const days = Math.floor(absDiffMs / DAY);
|
|
55
|
+
return isFuture ? `in ${days} days` : `${days} days ago`;
|
|
56
|
+
}
|
|
57
|
+
if (absDiffMs < YEAR) {
|
|
58
|
+
const months = Math.floor(absDiffMs / MONTH);
|
|
59
|
+
if (months === 1) {
|
|
60
|
+
return isFuture ? "in 1 month" : "1 month ago";
|
|
61
|
+
}
|
|
62
|
+
return isFuture ? `in ${months} months` : `${months} months ago`;
|
|
63
|
+
}
|
|
64
|
+
const years = Math.floor(absDiffMs / YEAR);
|
|
65
|
+
if (years === 1) {
|
|
66
|
+
return isFuture ? "in 1 year" : "1 year ago";
|
|
67
|
+
}
|
|
68
|
+
return isFuture ? `in ${years} years` : `${years} years ago`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export { formatRelative };
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/command.ts
|
|
3
|
+
import { Command } from "commander";
|
|
4
|
+
|
|
5
|
+
class CommandBuilderImpl {
|
|
6
|
+
command;
|
|
7
|
+
constructor(name) {
|
|
8
|
+
this.command = new Command(name);
|
|
9
|
+
}
|
|
10
|
+
description(text) {
|
|
11
|
+
this.command.description(text);
|
|
12
|
+
return this;
|
|
13
|
+
}
|
|
14
|
+
option(flags, description, defaultValue) {
|
|
15
|
+
this.command.option(flags, description, defaultValue);
|
|
16
|
+
return this;
|
|
17
|
+
}
|
|
18
|
+
requiredOption(flags, description, defaultValue) {
|
|
19
|
+
this.command.requiredOption(flags, description, defaultValue);
|
|
20
|
+
return this;
|
|
21
|
+
}
|
|
22
|
+
alias(alias) {
|
|
23
|
+
this.command.alias(alias);
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
action(handler) {
|
|
27
|
+
this.command.action(async (...args) => {
|
|
28
|
+
const command = args.at(-1);
|
|
29
|
+
const flags = command.optsWithGlobals?.() ?? command.opts();
|
|
30
|
+
const positional = command.args;
|
|
31
|
+
await handler({ args: positional, flags, command });
|
|
32
|
+
});
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
build() {
|
|
36
|
+
return this.command;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function command(name) {
|
|
40
|
+
return new CommandBuilderImpl(name);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export { command };
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { Result, ValidationError } from "@outfitter/contracts";
|
|
2
|
+
/**
|
|
3
|
+
* Represents a date range with start and end dates.
|
|
4
|
+
*/
|
|
5
|
+
interface DateRange {
|
|
6
|
+
/** Start of the range (inclusive, at 00:00:00.000) */
|
|
7
|
+
start: Date;
|
|
8
|
+
/** End of the range (inclusive, at 23:59:59.999) */
|
|
9
|
+
end: Date;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Returns a new Date set to the start of the day (00:00:00.000).
|
|
13
|
+
*
|
|
14
|
+
* @param date - The date to adjust
|
|
15
|
+
* @returns A new Date object at 00:00:00.000 on the same day
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* ```typescript
|
|
19
|
+
* startOfDay(new Date("2024-06-15T14:30:00Z"))
|
|
20
|
+
* // Returns 2024-06-15T00:00:00.000 (local time)
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
declare function startOfDay(date: Date): Date;
|
|
24
|
+
/**
|
|
25
|
+
* Returns a new Date set to the end of the day (23:59:59.999).
|
|
26
|
+
*
|
|
27
|
+
* @param date - The date to adjust
|
|
28
|
+
* @returns A new Date object at 23:59:59.999 on the same day
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```typescript
|
|
32
|
+
* endOfDay(new Date("2024-06-15T14:30:00Z"))
|
|
33
|
+
* // Returns 2024-06-15T23:59:59.999 (local time)
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
declare function endOfDay(date: Date): Date;
|
|
37
|
+
/**
|
|
38
|
+
* Parses date range strings into structured DateRange.
|
|
39
|
+
*
|
|
40
|
+
* Supported formats:
|
|
41
|
+
* - `"today"` - Current day (00:00:00 to 23:59:59)
|
|
42
|
+
* - `"yesterday"` - Previous day
|
|
43
|
+
* - `"last week"` - Last 7 days
|
|
44
|
+
* - `"last month"` - Last 30 days
|
|
45
|
+
* - `"2024-01-01..2024-12-31"` - Explicit range
|
|
46
|
+
* - `"2024-01-01"` - Single day
|
|
47
|
+
*
|
|
48
|
+
* @param input - The date range string to parse
|
|
49
|
+
* @returns Result containing DateRange on success, or ValidationError on failure
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* parseDateRange("today")
|
|
54
|
+
* // Ok({ start: today 00:00, end: today 23:59:59 })
|
|
55
|
+
*
|
|
56
|
+
* parseDateRange("2024-01-01..2024-12-31")
|
|
57
|
+
* // Ok({ start: 2024-01-01 00:00, end: 2024-12-31 23:59:59 })
|
|
58
|
+
*
|
|
59
|
+
* parseDateRange("invalid")
|
|
60
|
+
* // Err(ValidationError)
|
|
61
|
+
* ```
|
|
62
|
+
*/
|
|
63
|
+
declare function parseDateRange(input: string): Result<DateRange, InstanceType<typeof ValidationError>>;
|
|
64
|
+
export { DateRange, startOfDay, endOfDay, parseDateRange };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { ActionRegistry, ActionSurface, AnyActionSpec, HandlerContext } from "@outfitter/contracts";
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
interface BuildCliCommandsOptions {
|
|
4
|
+
readonly createContext?: (input: {
|
|
5
|
+
action: AnyActionSpec;
|
|
6
|
+
args: readonly string[];
|
|
7
|
+
flags: Record<string, unknown>;
|
|
8
|
+
}) => HandlerContext;
|
|
9
|
+
readonly includeSurfaces?: readonly ActionSurface[];
|
|
10
|
+
}
|
|
11
|
+
type ActionSource = ActionRegistry | readonly AnyActionSpec[];
|
|
12
|
+
declare function buildCliCommands(source: ActionSource, options?: BuildCliCommandsOptions): Command[];
|
|
13
|
+
export { BuildCliCommandsOptions, buildCliCommands };
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
resolveColorEnv,
|
|
4
|
+
supportsColor
|
|
5
|
+
} from "./cli-jbj78ac5.js";
|
|
6
|
+
|
|
7
|
+
// packages/cli/src/render/colors.ts
|
|
8
|
+
var ANSI = {
|
|
9
|
+
reset: "\x1B[0m",
|
|
10
|
+
bold: "\x1B[1m",
|
|
11
|
+
dim: "\x1B[2m",
|
|
12
|
+
italic: "\x1B[3m",
|
|
13
|
+
green: "\x1B[32m",
|
|
14
|
+
yellow: "\x1B[33m",
|
|
15
|
+
red: "\x1B[31m",
|
|
16
|
+
blue: "\x1B[34m",
|
|
17
|
+
cyan: "\x1B[36m",
|
|
18
|
+
magenta: "\x1B[35m",
|
|
19
|
+
white: "\x1B[37m",
|
|
20
|
+
gray: "\x1B[90m"
|
|
21
|
+
};
|
|
22
|
+
function createTheme() {
|
|
23
|
+
const colorEnabled = supportsColor();
|
|
24
|
+
const colorFn = (ansiCode) => (text) => {
|
|
25
|
+
if (!colorEnabled) {
|
|
26
|
+
return text;
|
|
27
|
+
}
|
|
28
|
+
return `${ansiCode}${text}${ANSI.reset}`;
|
|
29
|
+
};
|
|
30
|
+
return {
|
|
31
|
+
success: colorFn(ANSI.green),
|
|
32
|
+
warning: colorFn(ANSI.yellow),
|
|
33
|
+
error: colorFn(ANSI.red),
|
|
34
|
+
info: colorFn(ANSI.blue),
|
|
35
|
+
primary: colorFn(""),
|
|
36
|
+
secondary: colorFn(ANSI.gray),
|
|
37
|
+
muted: colorFn(ANSI.dim)
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
function resolveTokenColorEnabled(options) {
|
|
41
|
+
if (options?.colorLevel === 0) {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (options?.forceColor === true) {
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
if (options?.colorLevel !== undefined) {
|
|
48
|
+
return options.colorLevel > 0;
|
|
49
|
+
}
|
|
50
|
+
const env = resolveColorEnv({ forceColorFirst: true });
|
|
51
|
+
if (env !== undefined) {
|
|
52
|
+
return env;
|
|
53
|
+
}
|
|
54
|
+
return process.stdout.isTTY ?? false;
|
|
55
|
+
}
|
|
56
|
+
function createTokens(options) {
|
|
57
|
+
const colorEnabled = resolveTokenColorEnabled(options);
|
|
58
|
+
if (!colorEnabled) {
|
|
59
|
+
return {
|
|
60
|
+
success: "",
|
|
61
|
+
warning: "",
|
|
62
|
+
error: "",
|
|
63
|
+
info: "",
|
|
64
|
+
muted: "",
|
|
65
|
+
accent: "",
|
|
66
|
+
primary: "",
|
|
67
|
+
secondary: ""
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
success: ANSI.green,
|
|
72
|
+
warning: ANSI.yellow,
|
|
73
|
+
error: ANSI.red,
|
|
74
|
+
info: ANSI.blue,
|
|
75
|
+
muted: ANSI.dim,
|
|
76
|
+
accent: ANSI.cyan,
|
|
77
|
+
primary: "",
|
|
78
|
+
secondary: ANSI.gray
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
function applyColor(text, color) {
|
|
82
|
+
if (!supportsColor()) {
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
85
|
+
const ansiCode = ANSI[color];
|
|
86
|
+
return `${ansiCode}${text}${ANSI.reset}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export { ANSI, createTheme, resolveTokenColorEnabled, createTokens, applyColor };
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
import {
|
|
3
|
+
ANSI
|
|
4
|
+
} from "./cli-ag0w4pk0.js";
|
|
5
|
+
import {
|
|
6
|
+
supportsColor
|
|
7
|
+
} from "./cli-jbj78ac5.js";
|
|
8
|
+
|
|
9
|
+
// packages/cli/src/render/markdown.ts
|
|
10
|
+
function renderMarkdown(markdown) {
|
|
11
|
+
const colorEnabled = supportsColor();
|
|
12
|
+
let result = markdown;
|
|
13
|
+
result = result.replace(/```(\w*)\n([\s\S]*?)```/g, (_match, _lang, code) => {
|
|
14
|
+
const trimmed = code.trimEnd();
|
|
15
|
+
if (colorEnabled) {
|
|
16
|
+
return `${ANSI.dim}${trimmed}${ANSI.reset}`;
|
|
17
|
+
}
|
|
18
|
+
return trimmed;
|
|
19
|
+
});
|
|
20
|
+
result = result.replace(/^(#{1,6})\s+(.+)$/gm, (_match, _hashes, text) => {
|
|
21
|
+
if (colorEnabled) {
|
|
22
|
+
return `${ANSI.bold}${text}${ANSI.reset}`;
|
|
23
|
+
}
|
|
24
|
+
return text;
|
|
25
|
+
});
|
|
26
|
+
result = result.replace(/\*\*(.+?)\*\*/g, (_match, text) => {
|
|
27
|
+
if (colorEnabled) {
|
|
28
|
+
return `${ANSI.bold}${text}${ANSI.reset}`;
|
|
29
|
+
}
|
|
30
|
+
return text;
|
|
31
|
+
});
|
|
32
|
+
result = result.replace(/__(.+?)__/g, (_match, text) => {
|
|
33
|
+
if (colorEnabled) {
|
|
34
|
+
return `${ANSI.bold}${text}${ANSI.reset}`;
|
|
35
|
+
}
|
|
36
|
+
return text;
|
|
37
|
+
});
|
|
38
|
+
result = result.replace(/(?<!\*)\*([^*]+)\*(?!\*)/g, (_match, text) => {
|
|
39
|
+
if (colorEnabled) {
|
|
40
|
+
return `${ANSI.italic}${text}${ANSI.reset}`;
|
|
41
|
+
}
|
|
42
|
+
return text;
|
|
43
|
+
});
|
|
44
|
+
result = result.replace(/(?<!_)_([^_]+)_(?!_)/g, (_match, text) => {
|
|
45
|
+
if (colorEnabled) {
|
|
46
|
+
return `${ANSI.italic}${text}${ANSI.reset}`;
|
|
47
|
+
}
|
|
48
|
+
return text;
|
|
49
|
+
});
|
|
50
|
+
result = result.replace(/`([^`]+)`/g, (_match, text) => {
|
|
51
|
+
if (colorEnabled) {
|
|
52
|
+
return `${ANSI.cyan}${text}${ANSI.reset}`;
|
|
53
|
+
}
|
|
54
|
+
return text;
|
|
55
|
+
});
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export { renderMarkdown };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// packages/cli/src/render/progress.ts
|
|
3
|
+
function renderProgress(options) {
|
|
4
|
+
const { current, total, width = 20, showPercent = false } = options;
|
|
5
|
+
if (total <= 0) {
|
|
6
|
+
const bar2 = "\u2591".repeat(width);
|
|
7
|
+
return showPercent ? `[${bar2}] 0%` : `[${bar2}]`;
|
|
8
|
+
}
|
|
9
|
+
const percent = Math.min(100, Math.max(0, current / total * 100));
|
|
10
|
+
const filled = Math.round(percent / 100 * width);
|
|
11
|
+
const empty = width - filled;
|
|
12
|
+
const bar = "\u2588".repeat(filled) + "\u2591".repeat(empty);
|
|
13
|
+
if (showPercent) {
|
|
14
|
+
return `[${bar}] ${Math.round(percent)}%`;
|
|
15
|
+
}
|
|
16
|
+
return `[${bar}]`;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export { renderProgress };
|