@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 ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/index.js';
@@ -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,2 @@
1
+ import { Command } from 'commander';
2
+ export declare const tickets: Command;
@@ -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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"}
@@ -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>;
@@ -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":"AAAA,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAE/D;;;;;;;;;;GAUG;AAEH,MAAM,OAAO,WAAY,SAAQ,KAAK;IAC3B,MAAM,CAAS;IACf,IAAI,CAAS;IACb,SAAS,CAAqB;IAEvC,YAAY,MAAc,EAAE,IAAY,EAAE,OAAe,EAAE,SAAkB;QAC3E,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,aAAa,CAAC;QAC1B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;QACrB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;IAC7B,CAAC;CACF;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,oBAAoB,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;AACnF,CAAC;AAED,MAAM,UAAU,YAAY;IAC1B,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;IAC1C,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAC;IAC9B,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,WAAW,CACnB,CAAC,EACD,eAAe,EACf,6DAA6D,CAC9D,CAAC;IACJ,CAAC;IACD,IAAI,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;QAChC,MAAM,IAAI,WAAW,CACnB,CAAC,EACD,eAAe,EACf,uEAAuE,CACxE,CAAC;IACJ,CAAC;IACD,OAAO,OAAO,CAAC,WAAW,CAAC;AAC7B,CAAC;AAQD,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,MAA2C,EAC3C,IAAY,EACZ,OAAgF,EAAE;IAElF,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;IAC7B,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,EAAE,GAAG,IAAI,CAAC,CAAC;IACtC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QACtD,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1D,CAAC;IAED,MAAM,OAAO,GAA2B;QACtC,MAAM,EAAE,kBAAkB;QAC1B,aAAa,EAAE,UAAU,KAAK,EAAE;KACjC,CAAC;IACF,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,CAAC,cAAc,CAAC,GAAG,kBAAkB,CAAC;IAE1E,IAAI,GAAa,CAAC;IAClB,IAAI,CAAC;QACH,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,EAAE;YACrB,MAAM;YACN,OAAO;YACP,IAAI,EAAE,IAAI,CAAC,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;YACrE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;SACpC,CAAC,CAAC;IACL,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,MAAM,IAAI,WAAW,CAAC,CAAC,EAAE,eAAe,EAAE,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC;IACxF,CAAC;IAED,IAAI,QAAqB,CAAC;IAC1B,IAAI,CAAC;QACH,QAAQ,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAgB,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,WAAW,CAAC,GAAG,CAAC,MAAM,EAAE,kBAAkB,EAAE,2BAA2B,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC;IAClG,CAAC;IAED,IAAI,CAAC,GAAG,CAAC,EAAE,IAAI,QAAQ,CAAC,KAAK,EAAE,CAAC;QAC9B,MAAM,IAAI,WAAW,CACnB,GAAG,CAAC,MAAM,EACV,QAAQ,CAAC,KAAK,EAAE,IAAI,IAAI,SAAS,EACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,IAAI,QAAQ,GAAG,CAAC,MAAM,EAAE,EAC/C,QAAQ,CAAC,IAAI,EAAE,SAAS,CACzB,CAAC;IACJ,CAAC;IACD,OAAO,QAAQ,CAAC,IAAS,CAAC;AAC5B,CAAC"}
@@ -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
+ }