@forjio/suppuo-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/bin/cli.js +2 -0
- package/dist/commands/auth.d.ts +9 -0
- package/dist/commands/auth.js +31 -0
- package/dist/commands/auth.js.map +1 -0
- package/dist/commands/tickets.d.ts +2 -0
- package/dist/commands/tickets.js +122 -0
- package/dist/commands/tickets.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/api.d.ts +23 -0
- package/dist/lib/api.js +78 -0
- package/dist/lib/api.js.map +1 -0
- package/dist/lib/session.d.ts +25 -0
- package/dist/lib/session.js +36 -0
- package/dist/lib/session.js.map +1 -0
- package/package.json +35 -0
package/bin/cli.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
/**
|
|
3
|
+
* `auth login` / `auth whoami` / `auth logout` — OIDC device flow.
|
|
4
|
+
*
|
|
5
|
+
* Real implementation once Huudis M1 lands. For the template, stubs
|
|
6
|
+
* print clear "not yet wired" messages so cargo-culting into a product
|
|
7
|
+
* doesn't accidentally ship a fake login.
|
|
8
|
+
*/
|
|
9
|
+
export declare const auth: Command;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
/**
|
|
4
|
+
* `auth login` / `auth whoami` / `auth logout` — OIDC device flow.
|
|
5
|
+
*
|
|
6
|
+
* Real implementation once Huudis M1 lands. For the template, stubs
|
|
7
|
+
* print clear "not yet wired" messages so cargo-culting into a product
|
|
8
|
+
* doesn't accidentally ship a fake login.
|
|
9
|
+
*/
|
|
10
|
+
export const auth = new Command('auth').description('Authenticate against Huudis');
|
|
11
|
+
auth
|
|
12
|
+
.command('login')
|
|
13
|
+
.description('Sign in via OIDC device flow')
|
|
14
|
+
.action(async () => {
|
|
15
|
+
const issuer = process.env.FORJIO_OIDC_ISSUER ?? 'https://huudis.com';
|
|
16
|
+
console.log(chalk.dim(`Would initiate device flow against ${issuer}.`));
|
|
17
|
+
console.log(chalk.yellow('Not yet wired — implement once Huudis ships M1 (device flow endpoints).'));
|
|
18
|
+
});
|
|
19
|
+
auth
|
|
20
|
+
.command('whoami')
|
|
21
|
+
.description('Show the currently signed-in identity')
|
|
22
|
+
.action(async () => {
|
|
23
|
+
console.log(chalk.yellow('Not signed in. Run `auth login` after Huudis M1 ships.'));
|
|
24
|
+
});
|
|
25
|
+
auth
|
|
26
|
+
.command('logout')
|
|
27
|
+
.description('Clear the local session')
|
|
28
|
+
.action(async () => {
|
|
29
|
+
console.log(chalk.dim('No session to clear.'));
|
|
30
|
+
});
|
|
31
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/commands/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAE1B;;;;;;GAMG;AAEH,MAAM,CAAC,MAAM,IAAI,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,WAAW,CAAC,6BAA6B,CAAC,CAAC;AAEnF,IAAI;KACD,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,8BAA8B,CAAC;KAC3C,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,oBAAoB,CAAC;IACtE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sCAAsC,MAAM,GAAG,CAAC,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,yEAAyE,CAAC,CAAC,CAAC;AACvG,CAAC,CAAC,CAAC;AAEL,IAAI;KACD,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,uCAAuC,CAAC;KACpD,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,wDAAwD,CAAC,CAAC,CAAC;AACtF,CAAC,CAAC,CAAC;AAEL,IAAI;KACD,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,yBAAyB,CAAC;KACtC,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,CAAC,CAAC,CAAC;AACjD,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import chalk from 'chalk';
|
|
3
|
+
import { apiRequest, CliApiError } from '../lib/api.js';
|
|
4
|
+
const STATUS_COLORS = {
|
|
5
|
+
open: chalk.green,
|
|
6
|
+
pending: chalk.yellow,
|
|
7
|
+
resolved: chalk.blue,
|
|
8
|
+
closed: chalk.dim,
|
|
9
|
+
};
|
|
10
|
+
function paintStatus(status) {
|
|
11
|
+
return (STATUS_COLORS[status] ?? chalk.white)(status);
|
|
12
|
+
}
|
|
13
|
+
function fail(e) {
|
|
14
|
+
if (e instanceof CliApiError) {
|
|
15
|
+
console.error(chalk.red(`error [${e.code}]`), e.message);
|
|
16
|
+
if (e.requestId)
|
|
17
|
+
console.error(chalk.dim(`requestId: ${e.requestId}`));
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
console.error(chalk.red('error'), e instanceof Error ? e.message : String(e));
|
|
21
|
+
}
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
24
|
+
export const tickets = new Command('tickets').description('Manage helpdesk tickets');
|
|
25
|
+
tickets
|
|
26
|
+
.command('list')
|
|
27
|
+
.description('List tickets (newest activity first)')
|
|
28
|
+
.option('--status <status>', 'filter: open|pending|resolved|closed|all')
|
|
29
|
+
.option('--limit <n>', 'max tickets to return (1-100)', (v) => parseInt(v, 10))
|
|
30
|
+
.action(async (opts) => {
|
|
31
|
+
try {
|
|
32
|
+
const { tickets: rows, counts } = await apiRequest('GET', '/api/v1/tickets', {
|
|
33
|
+
query: { status: opts.status, limit: opts.limit },
|
|
34
|
+
});
|
|
35
|
+
if (rows.length === 0) {
|
|
36
|
+
console.log(chalk.dim('No tickets.'));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
for (const t of rows) {
|
|
40
|
+
const requester = t.requesterName ?? t.requesterEmail ?? '—';
|
|
41
|
+
console.log([
|
|
42
|
+
chalk.bold(`#${t.number}`.padEnd(6)),
|
|
43
|
+
paintStatus(t.status).padEnd(18), // padded incl. color codes
|
|
44
|
+
chalk.dim(t.priority.padEnd(7)),
|
|
45
|
+
t.subject,
|
|
46
|
+
chalk.dim(`(${requester}, ${t.id})`),
|
|
47
|
+
].join(' '));
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const summary = Object.entries(counts)
|
|
51
|
+
.map(([s, n]) => `${s}: ${n}`)
|
|
52
|
+
.join(' ');
|
|
53
|
+
if (summary)
|
|
54
|
+
console.log(chalk.dim(`\n${summary}`));
|
|
55
|
+
}
|
|
56
|
+
catch (e) {
|
|
57
|
+
fail(e);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
tickets
|
|
61
|
+
.command('show <id>')
|
|
62
|
+
.description('Show a ticket with its full message thread')
|
|
63
|
+
.action(async (id) => {
|
|
64
|
+
try {
|
|
65
|
+
const t = await apiRequest('GET', `/api/v1/tickets/${encodeURIComponent(id)}`);
|
|
66
|
+
console.log(chalk.bold(`#${t.number} ${t.subject}`));
|
|
67
|
+
console.log(`${paintStatus(t.status)} · ${t.priority} · ${t.channel} · ${chalk.dim(t.id)}`);
|
|
68
|
+
const requester = t.requesterName
|
|
69
|
+
? `${t.requesterName} <${t.requesterEmail ?? '—'}>`
|
|
70
|
+
: (t.requesterEmail ?? '—');
|
|
71
|
+
console.log(`requester: ${requester}`);
|
|
72
|
+
if (t.assigneeSub)
|
|
73
|
+
console.log(`assignee: ${t.assigneeSub}`);
|
|
74
|
+
console.log(chalk.dim(`created: ${t.createdAt}`));
|
|
75
|
+
for (const m of t.messages) {
|
|
76
|
+
const who = m.authorName ?? m.authorType;
|
|
77
|
+
const tag = m.isInternal ? chalk.yellow(' [internal]') : '';
|
|
78
|
+
console.log(`\n${chalk.bold(who)} ${chalk.dim(`(${m.authorType}, ${m.createdAt})`)}${tag}`);
|
|
79
|
+
console.log(m.body);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch (e) {
|
|
83
|
+
fail(e);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
tickets
|
|
87
|
+
.command('reply <id>')
|
|
88
|
+
.description('Reply to a ticket (public by default)')
|
|
89
|
+
.requiredOption('--message <text>', 'reply body')
|
|
90
|
+
.option('--internal', 'post as an internal note (not visible to the requester)')
|
|
91
|
+
.option('--author-name <name>', 'display name shown to the requester')
|
|
92
|
+
.action(async (id, opts) => {
|
|
93
|
+
try {
|
|
94
|
+
const out = await apiRequest('POST', `/api/v1/tickets/${encodeURIComponent(id)}/messages`, {
|
|
95
|
+
body: {
|
|
96
|
+
body: opts.message,
|
|
97
|
+
...(opts.internal ? { isInternal: true } : {}),
|
|
98
|
+
...(opts.authorName ? { authorName: opts.authorName } : {}),
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
const kind = opts.internal ? 'Internal note' : 'Reply';
|
|
102
|
+
console.log(chalk.green(`${kind} posted`), chalk.dim(`(${out.message.id})`), `— ticket is now ${paintStatus(out.status)}`);
|
|
103
|
+
}
|
|
104
|
+
catch (e) {
|
|
105
|
+
fail(e);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
tickets
|
|
109
|
+
.command('close <id>')
|
|
110
|
+
.description('Close a ticket')
|
|
111
|
+
.action(async (id) => {
|
|
112
|
+
try {
|
|
113
|
+
const t = await apiRequest('PATCH', `/api/v1/tickets/${encodeURIComponent(id)}`, {
|
|
114
|
+
body: { status: 'closed' },
|
|
115
|
+
});
|
|
116
|
+
console.log(chalk.green(`Ticket #${t.number} closed.`));
|
|
117
|
+
}
|
|
118
|
+
catch (e) {
|
|
119
|
+
fail(e);
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
//# sourceMappingURL=tickets.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tickets.js","sourceRoot":"","sources":["../../src/commands/tickets.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { auth } from './commands/auth.js';
|
|
3
|
+
import { tickets } from './commands/tickets.js';
|
|
4
|
+
const brand = process.env.SUPPUO ?? 'suppuo';
|
|
5
|
+
const program = new Command()
|
|
6
|
+
.name(brand)
|
|
7
|
+
.description(`CLI for ${brand} — part of the Forjio commerce suite.`)
|
|
8
|
+
.version('0.1.0');
|
|
9
|
+
program.addCommand(auth);
|
|
10
|
+
program.addCommand(tickets);
|
|
11
|
+
program.parseAsync(process.argv).catch((e) => {
|
|
12
|
+
console.error(e);
|
|
13
|
+
process.exit(1);
|
|
14
|
+
});
|
|
15
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAC;AAC1C,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AAEhD,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,QAAQ,CAAC;AAE7C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE;KAC1B,IAAI,CAAC,KAAK,CAAC;KACX,WAAW,CAAC,WAAW,KAAK,uCAAuC,CAAC;KACpE,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AACzB,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,CAAC;AAE5B,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;IAC3C,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IACjB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Thin Bearer-auth API helper for CLI commands.
|
|
3
|
+
*
|
|
4
|
+
* Token resolution order:
|
|
5
|
+
* 1. `SUPPUO_TOKEN` env (explicit override, CI-friendly)
|
|
6
|
+
* 2. the Huudis access token stored by `auth login`
|
|
7
|
+
* (~/.suppuo/session.json) — used as Bearer against suppuo.com.
|
|
8
|
+
*
|
|
9
|
+
* Unwraps the Forjio `{ data, error, meta }` envelope and throws
|
|
10
|
+
* `CliApiError` carrying the envelope's `error.code`.
|
|
11
|
+
*/
|
|
12
|
+
export declare class CliApiError extends Error {
|
|
13
|
+
readonly status: number;
|
|
14
|
+
readonly code: string;
|
|
15
|
+
readonly requestId: string | undefined;
|
|
16
|
+
constructor(status: number, code: string, message: string, requestId?: string);
|
|
17
|
+
}
|
|
18
|
+
export declare function baseUrl(): string;
|
|
19
|
+
export declare function resolveToken(): string;
|
|
20
|
+
export declare function apiRequest<T>(method: 'GET' | 'POST' | 'PATCH' | 'DELETE', path: string, opts?: {
|
|
21
|
+
body?: unknown;
|
|
22
|
+
query?: Record<string, string | number | undefined>;
|
|
23
|
+
}): Promise<T>;
|
package/dist/lib/api.js
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { loadSession, isAccessTokenStale } from './session.js';
|
|
2
|
+
/**
|
|
3
|
+
* Thin Bearer-auth API helper for CLI commands.
|
|
4
|
+
*
|
|
5
|
+
* Token resolution order:
|
|
6
|
+
* 1. `SUPPUO_TOKEN` env (explicit override, CI-friendly)
|
|
7
|
+
* 2. the Huudis access token stored by `auth login`
|
|
8
|
+
* (~/.suppuo/session.json) — used as Bearer against suppuo.com.
|
|
9
|
+
*
|
|
10
|
+
* Unwraps the Forjio `{ data, error, meta }` envelope and throws
|
|
11
|
+
* `CliApiError` carrying the envelope's `error.code`.
|
|
12
|
+
*/
|
|
13
|
+
export class CliApiError extends Error {
|
|
14
|
+
status;
|
|
15
|
+
code;
|
|
16
|
+
requestId;
|
|
17
|
+
constructor(status, code, message, requestId) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'CliApiError';
|
|
20
|
+
this.status = status;
|
|
21
|
+
this.code = code;
|
|
22
|
+
this.requestId = requestId;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
export function baseUrl() {
|
|
26
|
+
return (process.env.SUPPUO_BASE_URL ?? 'https://suppuo.com').replace(/\/+$/, '');
|
|
27
|
+
}
|
|
28
|
+
export function resolveToken() {
|
|
29
|
+
const envToken = process.env.SUPPUO_TOKEN;
|
|
30
|
+
if (envToken)
|
|
31
|
+
return envToken;
|
|
32
|
+
const session = loadSession();
|
|
33
|
+
if (!session) {
|
|
34
|
+
throw new CliApiError(0, 'AUTH_REQUIRED', 'Not signed in. Run `suppuo auth login` or set SUPPUO_TOKEN.');
|
|
35
|
+
}
|
|
36
|
+
if (isAccessTokenStale(session)) {
|
|
37
|
+
throw new CliApiError(0, 'TOKEN_EXPIRED', 'Session expired. Run `suppuo auth login` again (or set SUPPUO_TOKEN).');
|
|
38
|
+
}
|
|
39
|
+
return session.accessToken;
|
|
40
|
+
}
|
|
41
|
+
export async function apiRequest(method, path, opts = {}) {
|
|
42
|
+
const token = resolveToken();
|
|
43
|
+
const url = new URL(baseUrl() + path);
|
|
44
|
+
for (const [k, v] of Object.entries(opts.query ?? {})) {
|
|
45
|
+
if (v !== undefined)
|
|
46
|
+
url.searchParams.set(k, String(v));
|
|
47
|
+
}
|
|
48
|
+
const headers = {
|
|
49
|
+
Accept: 'application/json',
|
|
50
|
+
Authorization: `Bearer ${token}`,
|
|
51
|
+
};
|
|
52
|
+
if (opts.body !== undefined)
|
|
53
|
+
headers['Content-Type'] = 'application/json';
|
|
54
|
+
let res;
|
|
55
|
+
try {
|
|
56
|
+
res = await fetch(url, {
|
|
57
|
+
method,
|
|
58
|
+
headers,
|
|
59
|
+
body: opts.body !== undefined ? JSON.stringify(opts.body) : undefined,
|
|
60
|
+
signal: AbortSignal.timeout(30_000),
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
catch (e) {
|
|
64
|
+
throw new CliApiError(0, 'NETWORK_ERROR', e instanceof Error ? e.message : String(e));
|
|
65
|
+
}
|
|
66
|
+
let envelope;
|
|
67
|
+
try {
|
|
68
|
+
envelope = (await res.json());
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new CliApiError(res.status, 'INVALID_RESPONSE', `non-JSON response (HTTP ${res.status})`);
|
|
72
|
+
}
|
|
73
|
+
if (!res.ok || envelope.error) {
|
|
74
|
+
throw new CliApiError(res.status, envelope.error?.code ?? 'UNKNOWN', envelope.error?.message ?? `HTTP ${res.status}`, envelope.meta?.requestId);
|
|
75
|
+
}
|
|
76
|
+
return envelope.data;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=api.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"api.js","sourceRoot":"","sources":["../../src/lib/api.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* On-disk session store for the CLI. Ported from saas-huudis.
|
|
3
|
+
*
|
|
4
|
+
* Lives at `~/.SUPPUO/session.json` with 0600 perms. Holds the
|
|
5
|
+
* tokens from `auth login` so subsequent commands don't re-prompt.
|
|
6
|
+
*
|
|
7
|
+
* The actual wiring into `auth login/whoami/logout` lands once Huudis
|
|
8
|
+
* M1 ships the device-flow endpoints — until then this is a pure
|
|
9
|
+
* utility waiting for its caller.
|
|
10
|
+
*/
|
|
11
|
+
export interface StoredSession {
|
|
12
|
+
accessToken: string;
|
|
13
|
+
refreshToken: string;
|
|
14
|
+
idToken: string;
|
|
15
|
+
accessTokenExpiresAt: string;
|
|
16
|
+
scope: string;
|
|
17
|
+
issuer: string;
|
|
18
|
+
clientId: string;
|
|
19
|
+
}
|
|
20
|
+
export declare function saveSession(s: StoredSession): void;
|
|
21
|
+
export declare function loadSession(): StoredSession | null;
|
|
22
|
+
export declare function clearSession(): void;
|
|
23
|
+
/** Treat "stale" as <60s until expiry — gives the refresh call
|
|
24
|
+
* headroom before the access token is actually rejected upstream. */
|
|
25
|
+
export declare function isAccessTokenStale(s: StoredSession): boolean;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
function brand() {
|
|
5
|
+
return process.env.SUPPUO ?? 'suppuo';
|
|
6
|
+
}
|
|
7
|
+
function sessionPath() {
|
|
8
|
+
return path.join(os.homedir(), `.${brand()}`, 'session.json');
|
|
9
|
+
}
|
|
10
|
+
export function saveSession(s) {
|
|
11
|
+
const file = sessionPath();
|
|
12
|
+
fs.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
13
|
+
fs.writeFileSync(file, JSON.stringify(s, null, 2), { mode: 0o600 });
|
|
14
|
+
}
|
|
15
|
+
export function loadSession() {
|
|
16
|
+
const file = sessionPath();
|
|
17
|
+
if (!fs.existsSync(file))
|
|
18
|
+
return null;
|
|
19
|
+
try {
|
|
20
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return null;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export function clearSession() {
|
|
27
|
+
const file = sessionPath();
|
|
28
|
+
if (fs.existsSync(file))
|
|
29
|
+
fs.unlinkSync(file);
|
|
30
|
+
}
|
|
31
|
+
/** Treat "stale" as <60s until expiry — gives the refresh call
|
|
32
|
+
* headroom before the access token is actually rejected upstream. */
|
|
33
|
+
export function isAccessTokenStale(s) {
|
|
34
|
+
return new Date(s.accessTokenExpiresAt).getTime() - Date.now() < 60_000;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/lib/session.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAuB7B,SAAS,KAAK;IACZ,OAAO,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,QAAQ,CAAC;AACxC,CAAC;AAED,SAAS,WAAW;IAClB,OAAO,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,OAAO,EAAE,EAAE,IAAI,KAAK,EAAE,EAAE,EAAE,cAAc,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,UAAU,WAAW,CAAC,CAAgB;IAC1C,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,EAAE,CAAC,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACnE,EAAE,CAAC,aAAa,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IACtC,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAkB,CAAC;IACpE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;IAC3B,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC;QAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;AAC/C,CAAC;AAED;sEACsE;AACtE,MAAM,UAAU,kBAAkB,CAAC,CAAgB;IACjD,OAAO,IAAI,IAAI,CAAC,CAAC,CAAC,oBAAoB,CAAC,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,MAAM,CAAC;AAC1E,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@forjio/suppuo-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "CLI for Suppuo (part of the Forjio commerce suite).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"suppuo": "bin/cli.js"
|
|
9
|
+
},
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"dev": "tsc --watch",
|
|
13
|
+
"test": "vitest run --passWithNoTests",
|
|
14
|
+
"lint": "tsc --noEmit",
|
|
15
|
+
"type-check": "tsc --noEmit",
|
|
16
|
+
"prepublishOnly": "npm run build"
|
|
17
|
+
},
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@forjio/sdk": "^0.9.0",
|
|
20
|
+
"chalk": "^5.3.0",
|
|
21
|
+
"commander": "^12.1.0"
|
|
22
|
+
},
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@types/node": "^22.0.0",
|
|
25
|
+
"typescript": "^5.6.0",
|
|
26
|
+
"vitest": "^2.1.0"
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"dist",
|
|
30
|
+
"bin"
|
|
31
|
+
],
|
|
32
|
+
"engines": {
|
|
33
|
+
"node": ">=20.0.0"
|
|
34
|
+
}
|
|
35
|
+
}
|