@parix/cli 0.1.8 → 0.1.9
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/cli.cjs +4296 -35
- package/package.json +2 -2
- package/dist/cli.d.cts +0 -2
- package/dist/cli.d.ts +0 -2
- package/dist/cli.js +0 -36
- package/dist/commands/api.cjs +0 -81
- package/dist/commands/api.d.cts +0 -2
- package/dist/commands/api.d.ts +0 -2
- package/dist/commands/api.js +0 -78
- package/dist/commands/auth.cjs +0 -131
- package/dist/commands/auth.d.cts +0 -2
- package/dist/commands/auth.d.ts +0 -2
- package/dist/commands/auth.js +0 -128
- package/dist/commands/database.cjs +0 -259
- package/dist/commands/database.d.cts +0 -2
- package/dist/commands/database.d.ts +0 -2
- package/dist/commands/database.js +0 -256
- package/dist/commands/skill-content.cjs +0 -77
- package/dist/commands/skill-content.d.cts +0 -1
- package/dist/commands/skill-content.d.ts +0 -1
- package/dist/commands/skill-content.js +0 -74
- package/dist/commands/skill.cjs +0 -10
- package/dist/commands/skill.d.cts +0 -2
- package/dist/commands/skill.d.ts +0 -2
- package/dist/commands/skill.js +0 -7
- package/dist/commands/tb.cjs +0 -254
- package/dist/commands/tb.d.cts +0 -2
- package/dist/commands/tb.d.ts +0 -2
- package/dist/commands/tb.js +0 -251
- package/dist/lib/api-client.cjs +0 -44
- package/dist/lib/api-client.d.cts +0 -10
- package/dist/lib/api-client.d.ts +0 -10
- package/dist/lib/api-client.js +0 -40
- package/dist/lib/config.cjs +0 -24
- package/dist/lib/config.d.cts +0 -10
- package/dist/lib/config.d.ts +0 -10
- package/dist/lib/config.js +0 -18
- package/dist/lib/loopback-server.cjs +0 -180
- package/dist/lib/loopback-server.d.cts +0 -20
- package/dist/lib/loopback-server.d.ts +0 -20
- package/dist/lib/loopback-server.js +0 -177
- package/dist/lib/oauth-api.cjs +0 -73
- package/dist/lib/oauth-api.d.cts +0 -31
- package/dist/lib/oauth-api.d.ts +0 -31
- package/dist/lib/oauth-api.js +0 -68
- package/dist/lib/oauth-session.cjs +0 -108
- package/dist/lib/oauth-session.d.cts +0 -32
- package/dist/lib/oauth-session.d.ts +0 -32
- package/dist/lib/oauth-session.js +0 -103
- package/dist/lib/oauth.cjs +0 -58
- package/dist/lib/oauth.d.cts +0 -19
- package/dist/lib/oauth.d.ts +0 -19
- package/dist/lib/oauth.js +0 -49
- package/dist/lib/open-url.cjs +0 -38
- package/dist/lib/open-url.d.cts +0 -1
- package/dist/lib/open-url.d.ts +0 -1
- package/dist/lib/open-url.js +0 -32
- package/dist/lib/output.cjs +0 -20
- package/dist/lib/output.d.cts +0 -6
- package/dist/lib/output.d.ts +0 -6
- package/dist/lib/output.js +0 -15
- package/dist/lib/parix-api.cjs +0 -59
- package/dist/lib/parix-api.d.cts +0 -12
- package/dist/lib/parix-api.d.ts +0 -12
- package/dist/lib/parix-api.js +0 -55
- package/dist/lib/session.cjs +0 -80
- package/dist/lib/session.d.cts +0 -28
- package/dist/lib/session.d.ts +0 -28
- package/dist/lib/session.js +0 -74
- package/dist/lib/tb-payloads.cjs +0 -258
- package/dist/lib/tb-payloads.d.cts +0 -66
- package/dist/lib/tb-payloads.d.ts +0 -66
- package/dist/lib/tb-payloads.js +0 -249
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@parix/cli",
|
|
3
3
|
"description": "Parix command line interface",
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.9",
|
|
6
6
|
"bin": {
|
|
7
7
|
"parix": "dist/cli.cjs"
|
|
8
8
|
},
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"node": ">=20"
|
|
30
30
|
},
|
|
31
31
|
"scripts": {
|
|
32
|
-
"build": "
|
|
32
|
+
"build": "bun build ./src/cli.ts --outfile=dist/cli.cjs --target=node --format=cjs && node scripts/normalize-package-bin.mjs && node scripts/ensure-cli-shebang.mjs && chmod +x dist/cli.cjs",
|
|
33
33
|
"build:link": "bun run build && bun link",
|
|
34
34
|
"fmt": "oxfmt --write src test scripts package.json tsconfig.json .oxlintrc.json .oxfmtrc.json commitlint.config.js",
|
|
35
35
|
"fmt:check": "oxfmt --check src test scripts package.json tsconfig.json .oxlintrc.json .oxfmtrc.json commitlint.config.js",
|
package/dist/cli.d.cts
DELETED
package/dist/cli.d.ts
DELETED
package/dist/cli.js
DELETED
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { log } from '@clack/prompts';
|
|
3
|
-
import { Command } from 'commander';
|
|
4
|
-
import { createRequire } from 'node:module';
|
|
5
|
-
import { createApiCommand } from "./commands/api.js";
|
|
6
|
-
import { createAuthCommand } from "./commands/auth.js";
|
|
7
|
-
import { createDatabaseCommand } from "./commands/database.js";
|
|
8
|
-
import { createSkillCommand } from "./commands/skill.js";
|
|
9
|
-
import { createTbCommand } from "./commands/tb.js";
|
|
10
|
-
const loadPackageJson = createRequire(import.meta.url);
|
|
11
|
-
const packageJson = loadPackageJson('../package.json');
|
|
12
|
-
async function main() {
|
|
13
|
-
const program = new Command();
|
|
14
|
-
program
|
|
15
|
-
.name('parix')
|
|
16
|
-
.version(packageJson.version, '-v, --version')
|
|
17
|
-
.description('Parix command line interface')
|
|
18
|
-
.showHelpAfterError()
|
|
19
|
-
.addCommand(createAuthCommand())
|
|
20
|
-
.addCommand(createApiCommand())
|
|
21
|
-
.addCommand(createDatabaseCommand())
|
|
22
|
-
.addCommand(createSkillCommand())
|
|
23
|
-
.addCommand(createTbCommand());
|
|
24
|
-
await program.parseAsync(process.argv);
|
|
25
|
-
}
|
|
26
|
-
main()
|
|
27
|
-
.then(() => {
|
|
28
|
-
// CLIs that open HTTP servers / use undici fetch can leave sockets that keep
|
|
29
|
-
// Node alive after the command is done. Exit explicitly once work is finished.
|
|
30
|
-
process.exit(process.exitCode ?? 0);
|
|
31
|
-
})
|
|
32
|
-
.catch((error) => {
|
|
33
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
34
|
-
log.error(message);
|
|
35
|
-
process.exit(1);
|
|
36
|
-
});
|
package/dist/commands/api.cjs
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createApiCommand = createApiCommand;
|
|
4
|
-
const prompts_1 = require("@clack/prompts");
|
|
5
|
-
const commander_1 = require("commander");
|
|
6
|
-
const api_client_1 = require("../lib/api-client.cjs");
|
|
7
|
-
const config_1 = require("../lib/config.cjs");
|
|
8
|
-
const session_1 = require("../lib/session.cjs");
|
|
9
|
-
function createApiCommand() {
|
|
10
|
-
return new commander_1.Command('api')
|
|
11
|
-
.description('Make an authenticated API request with the stored bearer token')
|
|
12
|
-
.argument('<path>', 'Relative API path or absolute URL')
|
|
13
|
-
.option('-X, --method <method>', 'HTTP method', 'GET')
|
|
14
|
-
.option('-d, --body <body>', 'Raw request body')
|
|
15
|
-
.option('-H, --header <header>', 'Additional header in key:value form', collectValues, [])
|
|
16
|
-
.option('-b, --base-url <url>', 'Override the stored base URL')
|
|
17
|
-
.action(async (requestPath, options) => {
|
|
18
|
-
await handleApiRequest(requestPath, options);
|
|
19
|
-
});
|
|
20
|
-
}
|
|
21
|
-
async function handleApiRequest(requestPath, options) {
|
|
22
|
-
const storedSession = await (0, session_1.readStoredSession)();
|
|
23
|
-
if (!storedSession) {
|
|
24
|
-
throw new Error('No local session found. Run `parix auth login` first.');
|
|
25
|
-
}
|
|
26
|
-
const baseUrl = (0, config_1.resolveBaseUrl)(options.baseUrl, storedSession);
|
|
27
|
-
const headers = parseHeaders(options.header ?? []);
|
|
28
|
-
const body = options.body ?? undefined;
|
|
29
|
-
if (body && !hasHeader(headers, 'content-type')) {
|
|
30
|
-
headers.set('content-type', 'application/json');
|
|
31
|
-
}
|
|
32
|
-
const client = new api_client_1.ApiClient({
|
|
33
|
-
session: {
|
|
34
|
-
...storedSession,
|
|
35
|
-
baseUrl,
|
|
36
|
-
},
|
|
37
|
-
});
|
|
38
|
-
const response = await client.request(requestPath, {
|
|
39
|
-
method: options.method ?? 'GET',
|
|
40
|
-
headers,
|
|
41
|
-
body,
|
|
42
|
-
});
|
|
43
|
-
const contentType = response.headers.get('content-type') ?? 'unknown';
|
|
44
|
-
const payload = await response.text();
|
|
45
|
-
(0, prompts_1.note)([`Status: ${response.status} ${response.statusText}`, `Content-Type: ${contentType}`].join('\n'), 'Response');
|
|
46
|
-
if (contentType.includes('application/json') && payload.length > 0) {
|
|
47
|
-
try {
|
|
48
|
-
prompts_1.log.message(JSON.stringify(JSON.parse(payload), null, 2));
|
|
49
|
-
(0, prompts_1.outro)('Request completed');
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
catch { }
|
|
53
|
-
}
|
|
54
|
-
if (payload.length > 0) {
|
|
55
|
-
prompts_1.log.message(payload);
|
|
56
|
-
}
|
|
57
|
-
(0, prompts_1.outro)('Request completed');
|
|
58
|
-
}
|
|
59
|
-
function collectValues(value, previous) {
|
|
60
|
-
previous.push(value);
|
|
61
|
-
return previous;
|
|
62
|
-
}
|
|
63
|
-
function parseHeaders(values) {
|
|
64
|
-
const headers = new Headers();
|
|
65
|
-
for (const value of values) {
|
|
66
|
-
const separatorIndex = value.indexOf(':');
|
|
67
|
-
if (separatorIndex <= 0) {
|
|
68
|
-
throw new Error(`Invalid header format: ${value}. Use key:value.`);
|
|
69
|
-
}
|
|
70
|
-
const key = value.slice(0, separatorIndex).trim();
|
|
71
|
-
const headerValue = value.slice(separatorIndex + 1).trim();
|
|
72
|
-
if (!key) {
|
|
73
|
-
throw new Error(`Invalid header name: ${value}`);
|
|
74
|
-
}
|
|
75
|
-
headers.set(key, headerValue);
|
|
76
|
-
}
|
|
77
|
-
return headers;
|
|
78
|
-
}
|
|
79
|
-
function hasHeader(headers, name) {
|
|
80
|
-
return headers.has(name) || headers.has(name.toLowerCase());
|
|
81
|
-
}
|
package/dist/commands/api.d.cts
DELETED
package/dist/commands/api.d.ts
DELETED
package/dist/commands/api.js
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
import { log, note, outro } from '@clack/prompts';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { ApiClient } from "../lib/api-client.js";
|
|
4
|
-
import { resolveBaseUrl } from "../lib/config.js";
|
|
5
|
-
import { readStoredSession } from "../lib/session.js";
|
|
6
|
-
export function createApiCommand() {
|
|
7
|
-
return new Command('api')
|
|
8
|
-
.description('Make an authenticated API request with the stored bearer token')
|
|
9
|
-
.argument('<path>', 'Relative API path or absolute URL')
|
|
10
|
-
.option('-X, --method <method>', 'HTTP method', 'GET')
|
|
11
|
-
.option('-d, --body <body>', 'Raw request body')
|
|
12
|
-
.option('-H, --header <header>', 'Additional header in key:value form', collectValues, [])
|
|
13
|
-
.option('-b, --base-url <url>', 'Override the stored base URL')
|
|
14
|
-
.action(async (requestPath, options) => {
|
|
15
|
-
await handleApiRequest(requestPath, options);
|
|
16
|
-
});
|
|
17
|
-
}
|
|
18
|
-
async function handleApiRequest(requestPath, options) {
|
|
19
|
-
const storedSession = await readStoredSession();
|
|
20
|
-
if (!storedSession) {
|
|
21
|
-
throw new Error('No local session found. Run `parix auth login` first.');
|
|
22
|
-
}
|
|
23
|
-
const baseUrl = resolveBaseUrl(options.baseUrl, storedSession);
|
|
24
|
-
const headers = parseHeaders(options.header ?? []);
|
|
25
|
-
const body = options.body ?? undefined;
|
|
26
|
-
if (body && !hasHeader(headers, 'content-type')) {
|
|
27
|
-
headers.set('content-type', 'application/json');
|
|
28
|
-
}
|
|
29
|
-
const client = new ApiClient({
|
|
30
|
-
session: {
|
|
31
|
-
...storedSession,
|
|
32
|
-
baseUrl,
|
|
33
|
-
},
|
|
34
|
-
});
|
|
35
|
-
const response = await client.request(requestPath, {
|
|
36
|
-
method: options.method ?? 'GET',
|
|
37
|
-
headers,
|
|
38
|
-
body,
|
|
39
|
-
});
|
|
40
|
-
const contentType = response.headers.get('content-type') ?? 'unknown';
|
|
41
|
-
const payload = await response.text();
|
|
42
|
-
note([`Status: ${response.status} ${response.statusText}`, `Content-Type: ${contentType}`].join('\n'), 'Response');
|
|
43
|
-
if (contentType.includes('application/json') && payload.length > 0) {
|
|
44
|
-
try {
|
|
45
|
-
log.message(JSON.stringify(JSON.parse(payload), null, 2));
|
|
46
|
-
outro('Request completed');
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
catch { }
|
|
50
|
-
}
|
|
51
|
-
if (payload.length > 0) {
|
|
52
|
-
log.message(payload);
|
|
53
|
-
}
|
|
54
|
-
outro('Request completed');
|
|
55
|
-
}
|
|
56
|
-
function collectValues(value, previous) {
|
|
57
|
-
previous.push(value);
|
|
58
|
-
return previous;
|
|
59
|
-
}
|
|
60
|
-
function parseHeaders(values) {
|
|
61
|
-
const headers = new Headers();
|
|
62
|
-
for (const value of values) {
|
|
63
|
-
const separatorIndex = value.indexOf(':');
|
|
64
|
-
if (separatorIndex <= 0) {
|
|
65
|
-
throw new Error(`Invalid header format: ${value}. Use key:value.`);
|
|
66
|
-
}
|
|
67
|
-
const key = value.slice(0, separatorIndex).trim();
|
|
68
|
-
const headerValue = value.slice(separatorIndex + 1).trim();
|
|
69
|
-
if (!key) {
|
|
70
|
-
throw new Error(`Invalid header name: ${value}`);
|
|
71
|
-
}
|
|
72
|
-
headers.set(key, headerValue);
|
|
73
|
-
}
|
|
74
|
-
return headers;
|
|
75
|
-
}
|
|
76
|
-
function hasHeader(headers, name) {
|
|
77
|
-
return headers.has(name) || headers.has(name.toLowerCase());
|
|
78
|
-
}
|
package/dist/commands/auth.cjs
DELETED
|
@@ -1,131 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createAuthCommand = createAuthCommand;
|
|
4
|
-
const prompts_1 = require("@clack/prompts");
|
|
5
|
-
const commander_1 = require("commander");
|
|
6
|
-
const config_1 = require("../lib/config.cjs");
|
|
7
|
-
const loopback_server_1 = require("../lib/loopback-server.cjs");
|
|
8
|
-
const oauth_1 = require("../lib/oauth.cjs");
|
|
9
|
-
const oauth_api_1 = require("../lib/oauth-api.cjs");
|
|
10
|
-
const oauth_session_1 = require("../lib/oauth-session.cjs");
|
|
11
|
-
const open_url_1 = require("../lib/open-url.cjs");
|
|
12
|
-
const session_1 = require("../lib/session.cjs");
|
|
13
|
-
const DEFAULT_OIDC_PROMPT = 'consent';
|
|
14
|
-
function createAuthCommand() {
|
|
15
|
-
const auth = new commander_1.Command('auth').description('Authenticate with Parix');
|
|
16
|
-
auth
|
|
17
|
-
.command('login')
|
|
18
|
-
.description('Sign in with OAuth consent and PKCE')
|
|
19
|
-
.option('-b, --base-url <url>', 'Parix base URL')
|
|
20
|
-
.option('-p, --port <port>', 'Loopback callback port', config_1.parsePortOption)
|
|
21
|
-
.option('--prompt <prompt>', 'OAuth prompt override', DEFAULT_OIDC_PROMPT)
|
|
22
|
-
.action(async (options) => {
|
|
23
|
-
await handleLogin(options);
|
|
24
|
-
});
|
|
25
|
-
auth
|
|
26
|
-
.command('status')
|
|
27
|
-
.description('Show the current authenticated session')
|
|
28
|
-
.option('-b, --base-url <url>', 'Override the stored base URL')
|
|
29
|
-
.action(async (options) => {
|
|
30
|
-
await handleStatus(options);
|
|
31
|
-
});
|
|
32
|
-
auth
|
|
33
|
-
.command('logout')
|
|
34
|
-
.description('Remove the local session file')
|
|
35
|
-
.action(async () => {
|
|
36
|
-
await (0, session_1.clearStoredSession)();
|
|
37
|
-
(0, prompts_1.outro)(`Removed local session at ${session_1.SESSION_FILE_PATH}`);
|
|
38
|
-
});
|
|
39
|
-
return auth;
|
|
40
|
-
}
|
|
41
|
-
async function handleLogin(options) {
|
|
42
|
-
(0, prompts_1.intro)('Parix sign in');
|
|
43
|
-
const storedSession = await (0, session_1.readStoredSession)();
|
|
44
|
-
const baseUrl = (0, config_1.resolveBaseUrl)(options.baseUrl, storedSession, { useStoredSession: false });
|
|
45
|
-
const loopbackServer = await (0, loopback_server_1.startLoopbackServer)({
|
|
46
|
-
callbackPath: oauth_1.PARIX_LOCAL_CALLBACK_PATH,
|
|
47
|
-
port: options.port,
|
|
48
|
-
successPage: {
|
|
49
|
-
redirectUrl: new URL(oauth_1.CLI_OAUTH_CONSENT_GRANTED_PATH, baseUrl).toString(),
|
|
50
|
-
},
|
|
51
|
-
timeoutMs: config_1.LOGIN_TIMEOUT_MS,
|
|
52
|
-
});
|
|
53
|
-
try {
|
|
54
|
-
const pkcePair = (0, oauth_1.createPkcePair)();
|
|
55
|
-
const state = (0, oauth_1.buildCliState)(loopbackServer.callbackUrl);
|
|
56
|
-
loopbackServer.setState(state);
|
|
57
|
-
const browserUrl = (0, oauth_1.buildAuthorizeUrl)({
|
|
58
|
-
baseUrl,
|
|
59
|
-
codeChallenge: pkcePair.codeChallenge,
|
|
60
|
-
prompt: options.prompt,
|
|
61
|
-
state,
|
|
62
|
-
});
|
|
63
|
-
const prepareSpinner = (0, prompts_1.spinner)();
|
|
64
|
-
prepareSpinner.start('Preparing browser sign-in');
|
|
65
|
-
prepareSpinner.stop('Browser sign-in ready');
|
|
66
|
-
const opened = await (0, open_url_1.openUrlInBrowser)(browserUrl);
|
|
67
|
-
(0, prompts_1.note)(opened ? browserUrl : `Open this URL manually:\n${browserUrl}`, opened ? 'Opened browser URL' : 'Manual browser URL');
|
|
68
|
-
const waitSpinner = (0, prompts_1.spinner)();
|
|
69
|
-
waitSpinner.start('Waiting for browser callback');
|
|
70
|
-
const callbackResult = await loopbackServer.waitForResult();
|
|
71
|
-
waitSpinner.stop('Received browser callback');
|
|
72
|
-
// Shut down the loopback server before token exchange so browser keep-alive
|
|
73
|
-
// sockets cannot keep the process open after sign-in completes.
|
|
74
|
-
await loopbackServer.close().catch(() => { });
|
|
75
|
-
const tokenSet = await (0, oauth_api_1.exchangeAuthorizationCode)({
|
|
76
|
-
baseUrl,
|
|
77
|
-
code: callbackResult.code,
|
|
78
|
-
codeVerifier: pkcePair.codeVerifier,
|
|
79
|
-
});
|
|
80
|
-
const nextSession = await (0, oauth_session_1.createStoredSession)({
|
|
81
|
-
baseUrl,
|
|
82
|
-
createdAt: storedSession?.createdAt,
|
|
83
|
-
tokenSet,
|
|
84
|
-
});
|
|
85
|
-
await (0, session_1.writeStoredSession)(nextSession);
|
|
86
|
-
(0, prompts_1.note)(session_1.SESSION_FILE_PATH, 'Stored session file');
|
|
87
|
-
(0, prompts_1.outro)(`Signed in as ${formatUserLabel(nextSession.user)}`);
|
|
88
|
-
}
|
|
89
|
-
finally {
|
|
90
|
-
await loopbackServer.close().catch(() => { });
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
async function handleStatus(options) {
|
|
94
|
-
(0, prompts_1.intro)('Parix auth status');
|
|
95
|
-
const storedSession = await (0, session_1.readStoredSession)();
|
|
96
|
-
if (!storedSession) {
|
|
97
|
-
prompts_1.log.warn(`No local session found at ${session_1.SESSION_FILE_PATH}`);
|
|
98
|
-
process.exitCode = 1;
|
|
99
|
-
return;
|
|
100
|
-
}
|
|
101
|
-
const baseUrl = (0, config_1.resolveBaseUrl)(options.baseUrl, storedSession);
|
|
102
|
-
try {
|
|
103
|
-
const refreshedSession = await (0, oauth_session_1.hydrateStoredSessionOrganization)(await (0, oauth_session_1.ensureFreshSession)({
|
|
104
|
-
...storedSession,
|
|
105
|
-
baseUrl,
|
|
106
|
-
}));
|
|
107
|
-
const userInfo = await (0, oauth_api_1.fetchOAuthUserInfo)({
|
|
108
|
-
accessToken: refreshedSession.accessToken,
|
|
109
|
-
baseUrl,
|
|
110
|
-
});
|
|
111
|
-
(0, prompts_1.note)([
|
|
112
|
-
`User: ${userInfo.email ?? formatUserLabel(refreshedSession.user)}`,
|
|
113
|
-
`Base URL: ${baseUrl}`,
|
|
114
|
-
`Expires: ${refreshedSession.accessTokenExpiresAt}`,
|
|
115
|
-
`Scopes: ${refreshedSession.scopes.join(', ')}`,
|
|
116
|
-
`Organization: ${refreshedSession.organization.name ?? 'none'}`,
|
|
117
|
-
`Organization slug: ${refreshedSession.organization.slug ?? 'none'}`,
|
|
118
|
-
`Member role: ${refreshedSession.organization.memberRole ?? 'none'}`,
|
|
119
|
-
`Session file: ${session_1.SESSION_FILE_PATH}`,
|
|
120
|
-
].join('\n'), 'Authenticated session');
|
|
121
|
-
(0, prompts_1.outro)('Session is valid');
|
|
122
|
-
}
|
|
123
|
-
catch (error) {
|
|
124
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
125
|
-
prompts_1.log.warn(`Stored session is invalid: ${message}`);
|
|
126
|
-
process.exitCode = 1;
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
function formatUserLabel(user) {
|
|
130
|
-
return user.email ?? user.name ?? user.id;
|
|
131
|
-
}
|
package/dist/commands/auth.d.cts
DELETED
package/dist/commands/auth.d.ts
DELETED
package/dist/commands/auth.js
DELETED
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { intro, log, note, outro, spinner } from '@clack/prompts';
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import { LOGIN_TIMEOUT_MS, parsePortOption, resolveBaseUrl } from "../lib/config.js";
|
|
4
|
-
import { startLoopbackServer } from "../lib/loopback-server.js";
|
|
5
|
-
import { buildAuthorizeUrl, buildCliState, CLI_OAUTH_CONSENT_GRANTED_PATH, createPkcePair, PARIX_LOCAL_CALLBACK_PATH, } from "../lib/oauth.js";
|
|
6
|
-
import { exchangeAuthorizationCode, fetchOAuthUserInfo } from "../lib/oauth-api.js";
|
|
7
|
-
import { createStoredSession, ensureFreshSession, hydrateStoredSessionOrganization } from "../lib/oauth-session.js";
|
|
8
|
-
import { openUrlInBrowser } from "../lib/open-url.js";
|
|
9
|
-
import { clearStoredSession, readStoredSession, SESSION_FILE_PATH, writeStoredSession } from "../lib/session.js";
|
|
10
|
-
const DEFAULT_OIDC_PROMPT = 'consent';
|
|
11
|
-
export function createAuthCommand() {
|
|
12
|
-
const auth = new Command('auth').description('Authenticate with Parix');
|
|
13
|
-
auth
|
|
14
|
-
.command('login')
|
|
15
|
-
.description('Sign in with OAuth consent and PKCE')
|
|
16
|
-
.option('-b, --base-url <url>', 'Parix base URL')
|
|
17
|
-
.option('-p, --port <port>', 'Loopback callback port', parsePortOption)
|
|
18
|
-
.option('--prompt <prompt>', 'OAuth prompt override', DEFAULT_OIDC_PROMPT)
|
|
19
|
-
.action(async (options) => {
|
|
20
|
-
await handleLogin(options);
|
|
21
|
-
});
|
|
22
|
-
auth
|
|
23
|
-
.command('status')
|
|
24
|
-
.description('Show the current authenticated session')
|
|
25
|
-
.option('-b, --base-url <url>', 'Override the stored base URL')
|
|
26
|
-
.action(async (options) => {
|
|
27
|
-
await handleStatus(options);
|
|
28
|
-
});
|
|
29
|
-
auth
|
|
30
|
-
.command('logout')
|
|
31
|
-
.description('Remove the local session file')
|
|
32
|
-
.action(async () => {
|
|
33
|
-
await clearStoredSession();
|
|
34
|
-
outro(`Removed local session at ${SESSION_FILE_PATH}`);
|
|
35
|
-
});
|
|
36
|
-
return auth;
|
|
37
|
-
}
|
|
38
|
-
async function handleLogin(options) {
|
|
39
|
-
intro('Parix sign in');
|
|
40
|
-
const storedSession = await readStoredSession();
|
|
41
|
-
const baseUrl = resolveBaseUrl(options.baseUrl, storedSession, { useStoredSession: false });
|
|
42
|
-
const loopbackServer = await startLoopbackServer({
|
|
43
|
-
callbackPath: PARIX_LOCAL_CALLBACK_PATH,
|
|
44
|
-
port: options.port,
|
|
45
|
-
successPage: {
|
|
46
|
-
redirectUrl: new URL(CLI_OAUTH_CONSENT_GRANTED_PATH, baseUrl).toString(),
|
|
47
|
-
},
|
|
48
|
-
timeoutMs: LOGIN_TIMEOUT_MS,
|
|
49
|
-
});
|
|
50
|
-
try {
|
|
51
|
-
const pkcePair = createPkcePair();
|
|
52
|
-
const state = buildCliState(loopbackServer.callbackUrl);
|
|
53
|
-
loopbackServer.setState(state);
|
|
54
|
-
const browserUrl = buildAuthorizeUrl({
|
|
55
|
-
baseUrl,
|
|
56
|
-
codeChallenge: pkcePair.codeChallenge,
|
|
57
|
-
prompt: options.prompt,
|
|
58
|
-
state,
|
|
59
|
-
});
|
|
60
|
-
const prepareSpinner = spinner();
|
|
61
|
-
prepareSpinner.start('Preparing browser sign-in');
|
|
62
|
-
prepareSpinner.stop('Browser sign-in ready');
|
|
63
|
-
const opened = await openUrlInBrowser(browserUrl);
|
|
64
|
-
note(opened ? browserUrl : `Open this URL manually:\n${browserUrl}`, opened ? 'Opened browser URL' : 'Manual browser URL');
|
|
65
|
-
const waitSpinner = spinner();
|
|
66
|
-
waitSpinner.start('Waiting for browser callback');
|
|
67
|
-
const callbackResult = await loopbackServer.waitForResult();
|
|
68
|
-
waitSpinner.stop('Received browser callback');
|
|
69
|
-
// Shut down the loopback server before token exchange so browser keep-alive
|
|
70
|
-
// sockets cannot keep the process open after sign-in completes.
|
|
71
|
-
await loopbackServer.close().catch(() => { });
|
|
72
|
-
const tokenSet = await exchangeAuthorizationCode({
|
|
73
|
-
baseUrl,
|
|
74
|
-
code: callbackResult.code,
|
|
75
|
-
codeVerifier: pkcePair.codeVerifier,
|
|
76
|
-
});
|
|
77
|
-
const nextSession = await createStoredSession({
|
|
78
|
-
baseUrl,
|
|
79
|
-
createdAt: storedSession?.createdAt,
|
|
80
|
-
tokenSet,
|
|
81
|
-
});
|
|
82
|
-
await writeStoredSession(nextSession);
|
|
83
|
-
note(SESSION_FILE_PATH, 'Stored session file');
|
|
84
|
-
outro(`Signed in as ${formatUserLabel(nextSession.user)}`);
|
|
85
|
-
}
|
|
86
|
-
finally {
|
|
87
|
-
await loopbackServer.close().catch(() => { });
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
async function handleStatus(options) {
|
|
91
|
-
intro('Parix auth status');
|
|
92
|
-
const storedSession = await readStoredSession();
|
|
93
|
-
if (!storedSession) {
|
|
94
|
-
log.warn(`No local session found at ${SESSION_FILE_PATH}`);
|
|
95
|
-
process.exitCode = 1;
|
|
96
|
-
return;
|
|
97
|
-
}
|
|
98
|
-
const baseUrl = resolveBaseUrl(options.baseUrl, storedSession);
|
|
99
|
-
try {
|
|
100
|
-
const refreshedSession = await hydrateStoredSessionOrganization(await ensureFreshSession({
|
|
101
|
-
...storedSession,
|
|
102
|
-
baseUrl,
|
|
103
|
-
}));
|
|
104
|
-
const userInfo = await fetchOAuthUserInfo({
|
|
105
|
-
accessToken: refreshedSession.accessToken,
|
|
106
|
-
baseUrl,
|
|
107
|
-
});
|
|
108
|
-
note([
|
|
109
|
-
`User: ${userInfo.email ?? formatUserLabel(refreshedSession.user)}`,
|
|
110
|
-
`Base URL: ${baseUrl}`,
|
|
111
|
-
`Expires: ${refreshedSession.accessTokenExpiresAt}`,
|
|
112
|
-
`Scopes: ${refreshedSession.scopes.join(', ')}`,
|
|
113
|
-
`Organization: ${refreshedSession.organization.name ?? 'none'}`,
|
|
114
|
-
`Organization slug: ${refreshedSession.organization.slug ?? 'none'}`,
|
|
115
|
-
`Member role: ${refreshedSession.organization.memberRole ?? 'none'}`,
|
|
116
|
-
`Session file: ${SESSION_FILE_PATH}`,
|
|
117
|
-
].join('\n'), 'Authenticated session');
|
|
118
|
-
outro('Session is valid');
|
|
119
|
-
}
|
|
120
|
-
catch (error) {
|
|
121
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
122
|
-
log.warn(`Stored session is invalid: ${message}`);
|
|
123
|
-
process.exitCode = 1;
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function formatUserLabel(user) {
|
|
127
|
-
return user.email ?? user.name ?? user.id;
|
|
128
|
-
}
|