@eide/foir-cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/auth/credentials.d.ts +29 -0
- package/dist/auth/credentials.d.ts.map +1 -0
- package/dist/auth/credentials.js +79 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +74 -0
- package/dist/commands/api-keys.d.ts +4 -0
- package/dist/commands/api-keys.d.ts.map +1 -0
- package/dist/commands/api-keys.js +127 -0
- package/dist/commands/context.d.ts +4 -0
- package/dist/commands/context.d.ts.map +1 -0
- package/dist/commands/context.js +92 -0
- package/dist/commands/customers.d.ts +4 -0
- package/dist/commands/customers.d.ts.map +1 -0
- package/dist/commands/customers.js +120 -0
- package/dist/commands/experiments.d.ts +4 -0
- package/dist/commands/experiments.d.ts.map +1 -0
- package/dist/commands/experiments.js +177 -0
- package/dist/commands/extensions.d.ts +4 -0
- package/dist/commands/extensions.d.ts.map +1 -0
- package/dist/commands/extensions.js +94 -0
- package/dist/commands/files.d.ts +4 -0
- package/dist/commands/files.d.ts.map +1 -0
- package/dist/commands/files.js +143 -0
- package/dist/commands/locales.d.ts +4 -0
- package/dist/commands/locales.d.ts.map +1 -0
- package/dist/commands/locales.js +126 -0
- package/dist/commands/login.d.ts +4 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +125 -0
- package/dist/commands/logout.d.ts +4 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +16 -0
- package/dist/commands/media.d.ts +4 -0
- package/dist/commands/media.d.ts.map +1 -0
- package/dist/commands/media.js +44 -0
- package/dist/commands/models.d.ts +4 -0
- package/dist/commands/models.d.ts.map +1 -0
- package/dist/commands/models.js +155 -0
- package/dist/commands/notes.d.ts +4 -0
- package/dist/commands/notes.d.ts.map +1 -0
- package/dist/commands/notes.js +120 -0
- package/dist/commands/notifications.d.ts +4 -0
- package/dist/commands/notifications.d.ts.map +1 -0
- package/dist/commands/notifications.js +73 -0
- package/dist/commands/operations.d.ts +4 -0
- package/dist/commands/operations.d.ts.map +1 -0
- package/dist/commands/operations.js +161 -0
- package/dist/commands/records.d.ts +4 -0
- package/dist/commands/records.d.ts.map +1 -0
- package/dist/commands/records.js +216 -0
- package/dist/commands/schedules.d.ts +4 -0
- package/dist/commands/schedules.d.ts.map +1 -0
- package/dist/commands/schedules.js +150 -0
- package/dist/commands/search.d.ts +4 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +60 -0
- package/dist/commands/segments.d.ts +4 -0
- package/dist/commands/segments.d.ts.map +1 -0
- package/dist/commands/segments.js +143 -0
- package/dist/commands/select-project.d.ts +4 -0
- package/dist/commands/select-project.d.ts.map +1 -0
- package/dist/commands/select-project.js +144 -0
- package/dist/commands/settings.d.ts +4 -0
- package/dist/commands/settings.d.ts.map +1 -0
- package/dist/commands/settings.js +113 -0
- package/dist/commands/variant-catalog.d.ts +4 -0
- package/dist/commands/variant-catalog.d.ts.map +1 -0
- package/dist/commands/variant-catalog.js +111 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +50 -0
- package/dist/graphql/queries.d.ts +101 -0
- package/dist/graphql/queries.d.ts.map +1 -0
- package/dist/graphql/queries.js +373 -0
- package/dist/lib/client.d.ts +17 -0
- package/dist/lib/client.d.ts.map +1 -0
- package/dist/lib/client.js +61 -0
- package/dist/lib/config.d.ts +12 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +8 -0
- package/dist/lib/errors.d.ts +6 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +62 -0
- package/dist/lib/input.d.ts +36 -0
- package/dist/lib/input.d.ts.map +1 -0
- package/dist/lib/input.js +106 -0
- package/dist/lib/output.d.ts +31 -0
- package/dist/lib/output.d.ts.map +1 -0
- package/dist/lib/output.js +107 -0
- package/package.json +59 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/**
|
|
3
|
+
* Wrap an async command action with standardized error handling.
|
|
4
|
+
*/
|
|
5
|
+
export function withErrorHandler(optsFn, fn) {
|
|
6
|
+
return async (...args) => {
|
|
7
|
+
try {
|
|
8
|
+
await fn(...args);
|
|
9
|
+
}
|
|
10
|
+
catch (error) {
|
|
11
|
+
const opts = optsFn();
|
|
12
|
+
const gqlErr = error;
|
|
13
|
+
// Extract GraphQL error details
|
|
14
|
+
const gqlErrors = gqlErr?.response?.errors;
|
|
15
|
+
if (gqlErrors && gqlErrors.length > 0) {
|
|
16
|
+
const first = gqlErrors[0];
|
|
17
|
+
const code = first.extensions?.code;
|
|
18
|
+
if (opts?.json || opts?.jsonl) {
|
|
19
|
+
console.error(JSON.stringify({
|
|
20
|
+
error: {
|
|
21
|
+
message: first.message,
|
|
22
|
+
code: code ?? 'GRAPHQL_ERROR',
|
|
23
|
+
},
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
console.error(chalk.red('Error:'), first.message);
|
|
28
|
+
if (code === 'UNAUTHENTICATED') {
|
|
29
|
+
console.error(chalk.gray('Hint: Run `foir login` to authenticate.'));
|
|
30
|
+
}
|
|
31
|
+
else if (code === 'FORBIDDEN') {
|
|
32
|
+
console.error(chalk.gray('Hint: You may not have permission. Check your API key scopes.'));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
process.exit(1);
|
|
36
|
+
}
|
|
37
|
+
// HTTP-level errors
|
|
38
|
+
const status = gqlErr?.response?.status;
|
|
39
|
+
if (status === 401 || status === 403) {
|
|
40
|
+
if (opts?.json || opts?.jsonl) {
|
|
41
|
+
console.error(JSON.stringify({
|
|
42
|
+
error: { message: 'Authentication failed', code: 'AUTH_ERROR' },
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
console.error(chalk.red('Error:'), 'Authentication failed.');
|
|
47
|
+
console.error(chalk.gray('Hint: Run `foir login` or set FOIR_API_KEY.'));
|
|
48
|
+
}
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
// Generic errors
|
|
52
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
53
|
+
if (opts?.json || opts?.jsonl) {
|
|
54
|
+
console.error(JSON.stringify({ error: { message } }));
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
console.error(chalk.red('Error:'), message);
|
|
58
|
+
}
|
|
59
|
+
process.exit(1);
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parse input data from --data, --file, or stdin.
|
|
3
|
+
*/
|
|
4
|
+
export declare function parseInputData(opts: {
|
|
5
|
+
data?: string;
|
|
6
|
+
file?: string;
|
|
7
|
+
}): Promise<Record<string, unknown>>;
|
|
8
|
+
/**
|
|
9
|
+
* Detect whether a string is a UUID or CUID (→ ID lookup) vs a natural key.
|
|
10
|
+
*/
|
|
11
|
+
export declare function isUUID(value: string): boolean;
|
|
12
|
+
interface ParsedFilter {
|
|
13
|
+
field: string;
|
|
14
|
+
operator: string;
|
|
15
|
+
value: unknown;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parse filter string: "status=active,name~%hello%"
|
|
19
|
+
*/
|
|
20
|
+
export declare function parseFilters(filterStr: string): ParsedFilter[];
|
|
21
|
+
interface ParsedSort {
|
|
22
|
+
field: string;
|
|
23
|
+
direction: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Parse sort string: "createdAt:desc"
|
|
27
|
+
*/
|
|
28
|
+
export declare function parseSort(sortStr: string): ParsedSort | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Prompt for confirmation unless --confirm flag is set.
|
|
31
|
+
*/
|
|
32
|
+
export declare function confirmAction(message: string, opts?: {
|
|
33
|
+
confirm?: boolean;
|
|
34
|
+
}): Promise<boolean>;
|
|
35
|
+
export {};
|
|
36
|
+
//# sourceMappingURL=input.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.d.ts","sourceRoot":"","sources":["../../src/lib/input.ts"],"names":[],"mappings":"AAGA;;GAEG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE;IACzC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;CACf,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAyBnC;AAED;;GAEG;AACH,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAM7C;AAED,UAAU,YAAY;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,OAAO,CAAC;CAChB;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,YAAY,EAAE,CA8B9D;AAWD,UAAU,UAAU;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS,CAQjE;AAED;;GAEG;AACH,wBAAsB,aAAa,CACjC,OAAO,EAAE,MAAM,EACf,IAAI,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAC3B,OAAO,CAAC,OAAO,CAAC,CAalB"}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { promises as fs } from 'fs';
|
|
2
|
+
import inquirer from 'inquirer';
|
|
3
|
+
/**
|
|
4
|
+
* Parse input data from --data, --file, or stdin.
|
|
5
|
+
*/
|
|
6
|
+
export async function parseInputData(opts) {
|
|
7
|
+
if (opts.data) {
|
|
8
|
+
return JSON.parse(opts.data);
|
|
9
|
+
}
|
|
10
|
+
if (opts.file) {
|
|
11
|
+
const content = await fs.readFile(opts.file, 'utf-8');
|
|
12
|
+
return JSON.parse(content);
|
|
13
|
+
}
|
|
14
|
+
// Check for stdin pipe
|
|
15
|
+
if (!process.stdin.isTTY) {
|
|
16
|
+
const chunks = [];
|
|
17
|
+
for await (const chunk of process.stdin) {
|
|
18
|
+
chunks.push(chunk);
|
|
19
|
+
}
|
|
20
|
+
const stdinContent = Buffer.concat(chunks).toString('utf-8').trim();
|
|
21
|
+
if (stdinContent) {
|
|
22
|
+
return JSON.parse(stdinContent);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
throw new Error('No input data provided. Use --data, --file, or pipe via stdin.');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Detect whether a string is a UUID or CUID (→ ID lookup) vs a natural key.
|
|
29
|
+
*/
|
|
30
|
+
export function isUUID(value) {
|
|
31
|
+
return (/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value) || /^c[a-z0-9]{24,}$/.test(value));
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Parse filter string: "status=active,name~%hello%"
|
|
35
|
+
*/
|
|
36
|
+
export function parseFilters(filterStr) {
|
|
37
|
+
if (!filterStr)
|
|
38
|
+
return [];
|
|
39
|
+
return filterStr.split(',').map((part) => {
|
|
40
|
+
const trimmed = part.trim();
|
|
41
|
+
// Order matters: check multi-char operators first
|
|
42
|
+
for (const [op, gqlOp] of [
|
|
43
|
+
['!=', 'NE'],
|
|
44
|
+
['>=', 'GTE'],
|
|
45
|
+
['<=', 'LTE'],
|
|
46
|
+
['>', 'GT'],
|
|
47
|
+
['<', 'LT'],
|
|
48
|
+
['~', 'LIKE'],
|
|
49
|
+
['=', 'EQ'],
|
|
50
|
+
]) {
|
|
51
|
+
const idx = trimmed.indexOf(op);
|
|
52
|
+
if (idx > 0) {
|
|
53
|
+
const field = trimmed.slice(0, idx);
|
|
54
|
+
const rawValue = trimmed.slice(idx + op.length);
|
|
55
|
+
return {
|
|
56
|
+
field,
|
|
57
|
+
operator: gqlOp,
|
|
58
|
+
value: parseFilterValue(rawValue),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
throw new Error(`Invalid filter expression: "${trimmed}"`);
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
function parseFilterValue(raw) {
|
|
66
|
+
if (raw === 'true')
|
|
67
|
+
return true;
|
|
68
|
+
if (raw === 'false')
|
|
69
|
+
return false;
|
|
70
|
+
if (raw === 'null')
|
|
71
|
+
return null;
|
|
72
|
+
const num = Number(raw);
|
|
73
|
+
if (!isNaN(num) && raw !== '')
|
|
74
|
+
return num;
|
|
75
|
+
return raw;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Parse sort string: "createdAt:desc"
|
|
79
|
+
*/
|
|
80
|
+
export function parseSort(sortStr) {
|
|
81
|
+
if (!sortStr)
|
|
82
|
+
return undefined;
|
|
83
|
+
const [field, dir] = sortStr.split(':');
|
|
84
|
+
if (!field)
|
|
85
|
+
return undefined;
|
|
86
|
+
return {
|
|
87
|
+
field,
|
|
88
|
+
direction: (dir ?? 'asc').toUpperCase(),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Prompt for confirmation unless --confirm flag is set.
|
|
93
|
+
*/
|
|
94
|
+
export async function confirmAction(message, opts) {
|
|
95
|
+
if (opts?.confirm)
|
|
96
|
+
return true;
|
|
97
|
+
const { confirmed } = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'confirm',
|
|
100
|
+
name: 'confirmed',
|
|
101
|
+
message,
|
|
102
|
+
default: false,
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
return confirmed;
|
|
106
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { GlobalOptions } from './config.js';
|
|
2
|
+
export interface ColumnDef {
|
|
3
|
+
key: string;
|
|
4
|
+
header: string;
|
|
5
|
+
width?: number;
|
|
6
|
+
format?: (value: unknown) => string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Format a single object for output.
|
|
10
|
+
*/
|
|
11
|
+
export declare function formatOutput(data: unknown, options?: GlobalOptions): void;
|
|
12
|
+
/**
|
|
13
|
+
* Format a list of items as a table or structured output.
|
|
14
|
+
*/
|
|
15
|
+
export declare function formatList(items: Record<string, unknown>[], options: GlobalOptions | undefined, config: {
|
|
16
|
+
columns: ColumnDef[];
|
|
17
|
+
total?: number;
|
|
18
|
+
}): void;
|
|
19
|
+
/**
|
|
20
|
+
* Format a relative time string ("2h ago", "3d ago", etc.)
|
|
21
|
+
*/
|
|
22
|
+
export declare function timeAgo(dateStr: string | null | undefined): string;
|
|
23
|
+
/**
|
|
24
|
+
* Print a success message.
|
|
25
|
+
*/
|
|
26
|
+
export declare function success(message: string): void;
|
|
27
|
+
/**
|
|
28
|
+
* Print a warning message.
|
|
29
|
+
*/
|
|
30
|
+
export declare function warn(message: string): void;
|
|
31
|
+
//# sourceMappingURL=output.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../../src/lib/output.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAEjD,MAAM,WAAW,SAAS;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,KAAK,MAAM,CAAC;CACrC;AAED;;GAEG;AACH,wBAAgB,YAAY,CAAC,IAAI,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,IAAI,CAYzE;AAED;;GAEG;AACH,wBAAgB,UAAU,CACxB,KAAK,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,EAChC,OAAO,EAAE,aAAa,GAAG,SAAS,EAClC,MAAM,EAAE;IAAE,OAAO,EAAE,SAAS,EAAE,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAE,GAC/C,IAAI,CAkDN;AASD;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAmBlE;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE7C;AAED;;GAEG;AACH,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE1C"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import chalk from 'chalk';
|
|
2
|
+
/**
|
|
3
|
+
* Format a single object for output.
|
|
4
|
+
*/
|
|
5
|
+
export function formatOutput(data, options) {
|
|
6
|
+
if (options?.quiet)
|
|
7
|
+
return;
|
|
8
|
+
if (options?.json) {
|
|
9
|
+
console.log(JSON.stringify(data, null, 2));
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
if (options?.jsonl) {
|
|
13
|
+
console.log(JSON.stringify(data));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Human-readable: pretty-print the object
|
|
17
|
+
console.log(JSON.stringify(data, null, 2));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Format a list of items as a table or structured output.
|
|
21
|
+
*/
|
|
22
|
+
export function formatList(items, options, config) {
|
|
23
|
+
if (options?.quiet) {
|
|
24
|
+
for (const item of items) {
|
|
25
|
+
console.log(item['id'] ?? '');
|
|
26
|
+
}
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
if (options?.json) {
|
|
30
|
+
console.log(JSON.stringify(items, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (options?.jsonl) {
|
|
34
|
+
for (const item of items) {
|
|
35
|
+
console.log(JSON.stringify(item));
|
|
36
|
+
}
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
// Human-readable table
|
|
40
|
+
if (items.length === 0) {
|
|
41
|
+
console.log(chalk.gray('No results.'));
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
const { columns } = config;
|
|
45
|
+
// Print header
|
|
46
|
+
const headerLine = columns
|
|
47
|
+
.map((col) => chalk.bold(pad(col.header, col.width ?? 20)))
|
|
48
|
+
.join(' ');
|
|
49
|
+
console.log(headerLine);
|
|
50
|
+
console.log(columns.map((col) => '─'.repeat(col.width ?? 20)).join('──'));
|
|
51
|
+
// Print rows
|
|
52
|
+
for (const item of items) {
|
|
53
|
+
const row = columns
|
|
54
|
+
.map((col) => {
|
|
55
|
+
const raw = item[col.key];
|
|
56
|
+
const formatted = col.format ? col.format(raw) : String(raw ?? '');
|
|
57
|
+
return pad(formatted, col.width ?? 20);
|
|
58
|
+
})
|
|
59
|
+
.join(' ');
|
|
60
|
+
console.log(row);
|
|
61
|
+
}
|
|
62
|
+
if (config.total !== undefined) {
|
|
63
|
+
console.log(chalk.gray(`\n${items.length} of ${config.total} shown`));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function pad(str, width) {
|
|
67
|
+
if (str.length > width) {
|
|
68
|
+
return str.slice(0, width - 1) + '…';
|
|
69
|
+
}
|
|
70
|
+
return str.padEnd(width);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Format a relative time string ("2h ago", "3d ago", etc.)
|
|
74
|
+
*/
|
|
75
|
+
export function timeAgo(dateStr) {
|
|
76
|
+
if (!dateStr)
|
|
77
|
+
return '—';
|
|
78
|
+
const date = new Date(dateStr);
|
|
79
|
+
const now = Date.now();
|
|
80
|
+
const diffMs = now - date.getTime();
|
|
81
|
+
if (diffMs < 0)
|
|
82
|
+
return 'future';
|
|
83
|
+
const minutes = Math.floor(diffMs / 60000);
|
|
84
|
+
if (minutes < 1)
|
|
85
|
+
return 'just now';
|
|
86
|
+
if (minutes < 60)
|
|
87
|
+
return `${minutes}m ago`;
|
|
88
|
+
const hours = Math.floor(minutes / 60);
|
|
89
|
+
if (hours < 24)
|
|
90
|
+
return `${hours}h ago`;
|
|
91
|
+
const days = Math.floor(hours / 24);
|
|
92
|
+
if (days < 30)
|
|
93
|
+
return `${days}d ago`;
|
|
94
|
+
return date.toLocaleDateString();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Print a success message.
|
|
98
|
+
*/
|
|
99
|
+
export function success(message) {
|
|
100
|
+
console.log(chalk.green(`✓ ${message}`));
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Print a warning message.
|
|
104
|
+
*/
|
|
105
|
+
export function warn(message) {
|
|
106
|
+
console.log(chalk.yellow(`⚠ ${message}`));
|
|
107
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@eide/foir-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Universal platform CLI for EIDE — scriptable, composable resource management",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"publishConfig": {
|
|
7
|
+
"access": "public"
|
|
8
|
+
},
|
|
9
|
+
"bin": {
|
|
10
|
+
"foir": "./dist/cli.js"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"types": "./dist/cli.js",
|
|
15
|
+
"import": "./dist/cli.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"dist",
|
|
20
|
+
"README.md"
|
|
21
|
+
],
|
|
22
|
+
"scripts": {
|
|
23
|
+
"build": "tsc",
|
|
24
|
+
"dev:cli": "tsx src/cli.ts",
|
|
25
|
+
"check-types": "tsc --noEmit",
|
|
26
|
+
"test": "vitest run",
|
|
27
|
+
"test:watch": "vitest watch"
|
|
28
|
+
},
|
|
29
|
+
"keywords": [
|
|
30
|
+
"foir",
|
|
31
|
+
"eide",
|
|
32
|
+
"cms",
|
|
33
|
+
"cli",
|
|
34
|
+
"graphql",
|
|
35
|
+
"platform"
|
|
36
|
+
],
|
|
37
|
+
"author": "EIDE Team",
|
|
38
|
+
"license": "UNLICENSED",
|
|
39
|
+
"dependencies": {
|
|
40
|
+
"chalk": "^5.3.0",
|
|
41
|
+
"commander": "^12.1.0",
|
|
42
|
+
"dotenv": "^16.4.5",
|
|
43
|
+
"graphql-request": "^7.1.2",
|
|
44
|
+
"inquirer": "^9.2.12",
|
|
45
|
+
"open": "^10.1.0",
|
|
46
|
+
"ora": "^8.1.1"
|
|
47
|
+
},
|
|
48
|
+
"devDependencies": {
|
|
49
|
+
"@foir/config": "workspace:*",
|
|
50
|
+
"@types/inquirer": "^9.0.7",
|
|
51
|
+
"@types/node": "^22.5.0",
|
|
52
|
+
"tsx": "^4.20.0",
|
|
53
|
+
"typescript": "5.9.2",
|
|
54
|
+
"vitest": "^3.2.4"
|
|
55
|
+
},
|
|
56
|
+
"engines": {
|
|
57
|
+
"node": ">=18.0.0"
|
|
58
|
+
}
|
|
59
|
+
}
|