@oauth2-cli/qui-cli-plugin 0.3.0 → 0.3.1

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/CHANGELOG.md CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. See [commit-and-tag-version](https://github.com/absolute-version/commit-and-tag-version) for commit guidelines.
4
4
 
5
+ ## [0.3.1](https://github.com/battis/oauth2-cli/compare/qui-cli-plugin/0.3.0...qui-cli-plugin/0.3.1) (2026-01-03)
6
+
7
+
8
+ ### Features
9
+
10
+ * extend @oauth2-cli/qui-cli-plugin by instantiating new versions ([14713c5](https://github.com/battis/oauth2-cli/commit/14713c5d1c884c9a1d62ca34d0e5c364ffd8f19b))
11
+
5
12
  ## [0.3.0](https://github.com/battis/oauth2-cli/compare/qui-cli-plugin/0.2.7...qui-cli-plugin/0.3.0) (2025-12-24)
6
13
 
7
14
 
package/README.md CHANGED
@@ -8,11 +8,13 @@
8
8
  ## Install
9
9
 
10
10
  ```sh
11
- npm install @battis/qui-cli @oauth2-cli/qui-cli-plugin
11
+ npm install @qui-cli/core @oauth2-cli/qui-cli-plugin
12
12
  ```
13
13
 
14
14
  ## Usage
15
15
 
16
- See [@battis/qui-cli](https://www.npmjs.com/package/@battis/qui-cli) for more information about quickly building CLI apps.
16
+ See [@qui-cli](https://github.com/battis/qui-cli#readme) for more information about quickly building CLI apps.
17
17
 
18
- See [examples/qui-cli-plugin](../../examples/qui-cli-plugin).
18
+ See [examples/qui-cli-plugin](../../examples/qui-cli-plugin) for a sample use of this plugin with an arbitrary API.
19
+
20
+ See [@oauth2-cli/canvas](https://github.com/groton-school/canvas-cli/tree/main/packages/oauth2-cli/canvas#readme) for an example of extending this plugin for a specific API.
@@ -1,7 +1,7 @@
1
1
  import { PathString, URLString } from '@battis/descriptive-types';
2
2
  import '@qui-cli/env-1password';
3
3
  import * as Plugin from '@qui-cli/plugin';
4
- import * as OAuth2 from 'oauth2-cli';
4
+ import * as OAuth2CLI from 'oauth2-cli';
5
5
  import * as OpenIDClient from 'openid-client';
6
6
  type EnvironmentVars = {
7
7
  client_id: string;
@@ -31,20 +31,29 @@ export type Configuration = Plugin.Configuration & {
31
31
  headers?: Record<string, string>;
32
32
  authorization_endpoint?: URLString;
33
33
  token_endpoint?: URLString;
34
- store?: OAuth2.TokenStorage;
34
+ store?: OAuth2CLI.TokenStorage;
35
35
  token_path?: PathString;
36
36
  env: EnvironmentVars;
37
37
  man: Usage;
38
38
  suppress?: OptionSuppression;
39
39
  };
40
- export declare const name = "@oauth2-cli/qui-cli-plugin";
41
- export declare function configure(proposal?: Partial<Omit<Configuration, 'env' | 'suppress'>> & {
42
- env?: Partial<EnvironmentVars>;
43
- suppress?: Partial<OptionSuppression>;
44
- }): void;
45
- export declare function options(): Plugin.Options;
46
- export declare function init(args: Plugin.ExpectedArguments<typeof options>): Promise<void>;
47
- export declare function getToken(): Promise<OAuth2.Token | undefined>;
48
- export declare function request(url: URL | string, method?: string, body?: OpenIDClient.FetchBody, headers?: Record<string, string>, options?: OpenIDClient.DPoPOptions): Promise<Response>;
49
- export declare function requestJSON<T = unknown>(url: URL | string, method?: string, body?: OpenIDClient.FetchBody, headers?: Record<string, string>, options?: OpenIDClient.DPoPOptions): Promise<T>;
40
+ export declare class OAuth2 {
41
+ readonly name: string;
42
+ [key: string]: unknown;
43
+ private static names;
44
+ private static ports;
45
+ constructor(name?: string);
46
+ private config;
47
+ private client;
48
+ configure(proposal?: Partial<Omit<Configuration, 'env' | 'suppress'>> & {
49
+ env?: Partial<EnvironmentVars>;
50
+ suppress?: Partial<OptionSuppression>;
51
+ }): void;
52
+ options(): Plugin.Options;
53
+ init(args: Plugin.ExpectedArguments<typeof this.options>): Promise<void>;
54
+ private getClient;
55
+ getToken(): Promise<OAuth2CLI.Token | undefined>;
56
+ request(url: URL | string, method?: string, body?: OpenIDClient.FetchBody, headers?: Record<string, string>, options?: OpenIDClient.DPoPOptions): Promise<Response>;
57
+ requestJSON<T = unknown>(url: URL | string, method?: string, body?: OpenIDClient.FetchBody, headers?: Record<string, string>, options?: OpenIDClient.DPoPOptions): Promise<T>;
58
+ }
50
59
  export {};
package/dist/OAuth2.js ADDED
@@ -0,0 +1,184 @@
1
+ import { Colors } from '@qui-cli/colors';
2
+ import '@qui-cli/env-1password';
3
+ import { Env } from '@qui-cli/env-1password';
4
+ import { Log } from '@qui-cli/log';
5
+ import { Root } from '@qui-cli/root';
6
+ import path from 'node:path';
7
+ import * as OAuth2CLI from 'oauth2-cli';
8
+ import { EnvironmentStorage } from './EnvironmentStorage.js';
9
+ export class OAuth2 {
10
+ name;
11
+ static names = [];
12
+ static ports = {};
13
+ constructor(name = '@oauth2-cli/qui-cli-plugin') {
14
+ this.name = name;
15
+ if (OAuth2.names.includes(name)) {
16
+ throw new Error(`A @qui-cli/plugin named ${Colors.value(name)} has already been instantiated.`);
17
+ }
18
+ }
19
+ config = {
20
+ env: {
21
+ client_id: 'CLIENT_ID',
22
+ client_secret: 'CLIENT_SECRET',
23
+ redirect_uri: 'REDIRECT_URI',
24
+ authorization_endpoint: 'AUTHORIZATION_ENDPOINT',
25
+ token_endpoint: 'TOKEN_ENDPOINT',
26
+ token_path: 'TOKEN_PATH',
27
+ access_token: 'ACCESS_TOKEN'
28
+ },
29
+ man: {
30
+ heading: 'OAuth 2.0 client options'
31
+ },
32
+ suppress: {
33
+ tokenPath: true
34
+ }
35
+ };
36
+ client = undefined;
37
+ configure(proposal = {}) {
38
+ for (const key in proposal) {
39
+ if (proposal[key] !== undefined) {
40
+ this.config[key] = proposal[key];
41
+ }
42
+ }
43
+ if (!this.config.store) {
44
+ if (this.config.token_path) {
45
+ this.config.store = new OAuth2CLI.FileStorage(path.resolve(Root.path(), this.config.token_path));
46
+ }
47
+ else {
48
+ this.config.store = new EnvironmentStorage(this.config.env.access_token);
49
+ }
50
+ }
51
+ if (this.config.redirect_uri) {
52
+ const url = new URL(this.config.redirect_uri);
53
+ if (url.hostname !== 'localhost') {
54
+ Log.warning(`The ${Colors.varName('redirect_uri')} value ${Colors.url(this.config.redirect_uri)} may not work: it ${Colors.keyword('must')} point to ${Colors.url('localhost')}`);
55
+ }
56
+ if (url.protocol !== 'http:') {
57
+ Log.warning(`The ${Colors.url(url.protocol)} protocol may not work without additional configuration. The server listening for the redirect will be running at ${Colors.url(`http://localhost:${url.port}`)}`);
58
+ }
59
+ if (OAuth2.ports[url.port] && OAuth2.ports[url.port] !== this.name) {
60
+ Log.warning(`The port ${Colors.value(url.port)} has already been registered to another instance of this plugin named ${Colors.value(OAuth2.ports[url.port])}. This will likely cause a failure if both instances of the plugin are listening for redirects at relatively proximate moments in time.`);
61
+ }
62
+ }
63
+ }
64
+ options() {
65
+ return {
66
+ man: [
67
+ { level: 1, text: this.config.man.heading },
68
+ ...(this.config.man.text || []).map((t) => ({ text: t }))
69
+ ],
70
+ opt: {
71
+ ...(this.config.suppress?.clientId
72
+ ? {}
73
+ : {
74
+ clientId: {
75
+ description: `OAuth 2.0 client ID (defaults to environment variable ${Colors.value(this.config.env.client_id)})`,
76
+ secret: true,
77
+ default: this.config.client_id
78
+ }
79
+ }),
80
+ ...(this.config.suppress?.clientSecret
81
+ ? {}
82
+ : {
83
+ clientSecret: {
84
+ description: `OAuth 2.0 client secret (defaults to environment variable ${Colors.value(this.config.env.client_secret)}`,
85
+ secret: true,
86
+ default: this.config.client_secret
87
+ }
88
+ }),
89
+ ...(this.config.suppress?.redirectUri
90
+ ? {}
91
+ : {
92
+ redirectUri: {
93
+ description: `OAuth 2.0 redirect URI (must be to host ${Colors.url('localhost')}, defaults to environment variables ${Colors.value(this.config.env.redirect_uri)})`,
94
+ hint: Colors.quotedValue(`"http://localhost:XXXX/path/to/redirect"`),
95
+ default: this.config.redirect_uri
96
+ }
97
+ }),
98
+ ...(this.config.suppress?.authorizationEndpoint
99
+ ? {}
100
+ : {
101
+ authorizationEndpoint: {
102
+ description: `OAuth 2.0 authorization endpoint (defaults to environment variable ${Colors.value(this.config.env.authorization_endpoint)}`,
103
+ default: this.config.authorization_endpoint
104
+ }
105
+ }),
106
+ ...(this.config.suppress?.tokenEndpoint
107
+ ? {}
108
+ : {
109
+ tokenEndpoint: {
110
+ description: `OAuth 2.0 token endpoint (will fall back to authorization endpoint if not provided, defaults to environment variable ${Colors.value(this.config.env.token_endpoint)}`,
111
+ default: this.config.token_endpoint
112
+ }
113
+ }),
114
+ ...(this.config.suppress?.tokenPath
115
+ ? {}
116
+ : {
117
+ tokenPath: {
118
+ description: `Path to token storage JSON file (defaults to environent variable ${Colors.value(this.config.env.token_path)}`,
119
+ default: this.config.token_path
120
+ }
121
+ })
122
+ }
123
+ };
124
+ }
125
+ async init(args) {
126
+ const { values: { clientId: client_id = await Env.get({
127
+ key: this.config.env.client_id
128
+ }), clientSecret: client_secret = await Env.get({
129
+ key: this.config.env.client_secret
130
+ }), redirectUri: redirect_uri = await Env.get({
131
+ key: this.config.env.redirect_uri
132
+ }), authorizationEndpoint: authorization_endpoint = await Env.get({
133
+ key: this.config.env.authorization_endpoint
134
+ }), tokenEndpoint: token_endpoint = await Env.get({
135
+ key: this.config.env.token_endpoint
136
+ }), tokenPath: token_path = await Env.get({
137
+ key: this.config.env.token_path
138
+ }) } } = args;
139
+ this.configure({
140
+ client_id,
141
+ client_secret,
142
+ redirect_uri,
143
+ authorization_endpoint,
144
+ token_endpoint,
145
+ token_path
146
+ });
147
+ }
148
+ getClient() {
149
+ if (!this.client) {
150
+ const { client_id, client_secret, redirect_uri, authorization_endpoint, token_endpoint, headers, store } = this.config;
151
+ if (!client_id) {
152
+ throw new Error('OAuth 2.0 client ID not defined');
153
+ }
154
+ if (!client_secret) {
155
+ throw new Error('OAuth 2.0 client secret not defined');
156
+ }
157
+ if (!redirect_uri) {
158
+ throw new Error('OAuth 2.0 redirect URI not defined');
159
+ }
160
+ if (!authorization_endpoint) {
161
+ throw new Error('OAuth 2.0 authorization endpoint not defined');
162
+ }
163
+ this.client = new OAuth2CLI.Client({
164
+ client_id,
165
+ client_secret,
166
+ redirect_uri,
167
+ authorization_endpoint,
168
+ token_endpoint,
169
+ headers,
170
+ store
171
+ });
172
+ }
173
+ return this.client;
174
+ }
175
+ async getToken() {
176
+ return await this.getClient().getToken();
177
+ }
178
+ async request(url, method = 'GET', body, headers, options) {
179
+ return await this.getClient().request(url, method, body, headers, options);
180
+ }
181
+ async requestJSON(url, method = 'GET', body, headers, options) {
182
+ return (await this.getClient().requestJSON(url, method, body, headers, options));
183
+ }
184
+ }
package/dist/index.d.ts CHANGED
@@ -1,3 +1,3 @@
1
- import * as OAuth2 from './OAuth2CLI.js';
1
+ import { OAuth2 as Plugin } from './OAuth2.js';
2
2
  export * from './EnvironmentStorage.js';
3
- export { OAuth2 };
3
+ export declare const OAuth2: Plugin;
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { register } from '@qui-cli/plugin';
2
- import * as OAuth2 from './OAuth2CLI.js';
2
+ import { OAuth2 as Plugin } from './OAuth2.js';
3
3
  export * from './EnvironmentStorage.js';
4
- export { OAuth2 };
4
+ export const OAuth2 = new Plugin();
5
5
  await register(OAuth2);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oauth2-cli/qui-cli-plugin",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "@battis/qui-cli.plugin wrapper for oauth2-cli",
5
5
  "homepage": "https://github.com/battis/oauth2-cli/tree/main/packages/qui-cli-plugin#readme",
6
6
  "repository": {
@@ -16,15 +16,16 @@
16
16
  "main": "./dist/index.js",
17
17
  "types": "./dist/index.d.ts",
18
18
  "dependencies": {
19
+ "@qui-cli/log": "^4.0.2",
19
20
  "openid-client": "^6.8.1",
20
21
  "oauth2-cli": "0.3.0"
21
22
  },
22
23
  "devDependencies": {
23
24
  "@battis/descriptive-types": "^0.2.6",
24
- "@qui-cli/colors": "^3.1.1",
25
- "@qui-cli/env-1password": "^1.2.2",
25
+ "@qui-cli/colors": "^3.2.1",
26
+ "@qui-cli/env-1password": "^1.2.5",
26
27
  "@qui-cli/plugin": "^4.0.0",
27
- "@qui-cli/root": "^3.0.4",
28
+ "@qui-cli/root": "^3.0.6",
28
29
  "@tsconfig/node20": "^20.1.8",
29
30
  "commit-and-tag-version": "^12.6.1",
30
31
  "del-cli": "^6.0.0",
package/dist/OAuth2CLI.js DELETED
@@ -1,159 +0,0 @@
1
- import { Colors } from '@qui-cli/colors';
2
- import '@qui-cli/env-1password';
3
- import { Env } from '@qui-cli/env-1password';
4
- import { Root } from '@qui-cli/root';
5
- import path from 'node:path';
6
- import * as OAuth2 from 'oauth2-cli';
7
- import { EnvironmentStorage } from './EnvironmentStorage.js';
8
- export const name = '@oauth2-cli/qui-cli-plugin';
9
- const config = {
10
- env: {
11
- client_id: 'CLIENT_ID',
12
- client_secret: 'CLIENT_SECRET',
13
- redirect_uri: 'REDIRECT_URI',
14
- authorization_endpoint: 'AUTHORIZATION_ENDPOINT',
15
- token_endpoint: 'TOKEN_ENDPOINT',
16
- token_path: 'TOKEN_PATH',
17
- access_token: 'ACCESS_TOKEN'
18
- },
19
- man: {
20
- heading: 'OAuth 2.0 client options'
21
- },
22
- suppress: {
23
- tokenPath: true
24
- }
25
- };
26
- let client = undefined;
27
- export function configure(proposal = {}) {
28
- for (const key in proposal) {
29
- if (proposal[key] !== undefined) {
30
- config[key] = proposal[key];
31
- }
32
- }
33
- if (!config.store) {
34
- if (config.token_path) {
35
- config.store = new OAuth2.FileStorage(path.resolve(Root.path(), config.token_path));
36
- }
37
- else {
38
- config.store = new EnvironmentStorage(config.env.access_token);
39
- }
40
- }
41
- }
42
- export function options() {
43
- return {
44
- man: [
45
- { level: 1, text: config.man.heading },
46
- ...(config.man.text || []).map((t) => ({ text: t }))
47
- ],
48
- opt: {
49
- ...(config.suppress?.clientId
50
- ? {}
51
- : {
52
- clientId: {
53
- description: `OAuth 2.0 client ID (defaults to environment variable ${Colors.value(config.env.client_id)})`,
54
- secret: true,
55
- default: config.client_id
56
- }
57
- }),
58
- ...(config.suppress?.clientSecret
59
- ? {}
60
- : {
61
- clientSecret: {
62
- description: `OAuth 2.0 client secret (defaults to environment variable ${Colors.value(config.env.client_secret)}`,
63
- secret: true,
64
- default: config.client_secret
65
- }
66
- }),
67
- ...(config.suppress?.redirectUri
68
- ? {}
69
- : {
70
- redirectUri: {
71
- description: `OAuth 2.0 redirect URI (must be to host ${Colors.url('localhost')}, defaults to environment variables ${Colors.value(config.env.redirect_uri)})`,
72
- hint: Colors.quotedValue(`"http://localhost:XXXX/path/to/redirect"`),
73
- default: config.redirect_uri
74
- }
75
- }),
76
- ...(config.suppress?.authorizationEndpoint
77
- ? {}
78
- : {
79
- authorizationEndpoint: {
80
- description: `OAuth 2.0 authorization endpoint (defaults to environment variable ${Colors.value(config.env.authorization_endpoint)}`,
81
- default: config.authorization_endpoint
82
- }
83
- }),
84
- ...(config.suppress?.tokenEndpoint
85
- ? {}
86
- : {
87
- tokenEndpoint: {
88
- description: `OAuth 2.0 token endpoint (will fall back to authorization endpoint if not provided, defaults to environment variable ${Colors.value(config.env.token_endpoint)}`,
89
- default: config.token_endpoint
90
- }
91
- }),
92
- ...(config.suppress?.tokenPath
93
- ? {}
94
- : {
95
- tokenPath: {
96
- description: `Path to token storage JSON file (defaults to environent variable ${Colors.value(config.env.token_path)}`,
97
- default: config.token_path
98
- }
99
- })
100
- }
101
- };
102
- }
103
- export async function init(args) {
104
- const { values: { clientId: client_id = await Env.get({
105
- key: config.env.client_id
106
- }), clientSecret: client_secret = await Env.get({
107
- key: config.env.client_secret
108
- }), redirectUri: redirect_uri = await Env.get({
109
- key: config.env.redirect_uri
110
- }), authorizationEndpoint: authorization_endpoint = await Env.get({
111
- key: config.env.authorization_endpoint
112
- }), tokenEndpoint: token_endpoint = await Env.get({
113
- key: config.env.token_endpoint
114
- }), tokenPath: token_path = await Env.get({ key: config.env.token_path }) } } = args;
115
- configure({
116
- client_id,
117
- client_secret,
118
- redirect_uri,
119
- authorization_endpoint,
120
- token_endpoint,
121
- token_path
122
- });
123
- }
124
- function getClient() {
125
- if (!client) {
126
- const { client_id, client_secret, redirect_uri, authorization_endpoint, token_endpoint, headers, store } = config;
127
- if (!client_id) {
128
- throw new Error('OAuth 2.0 client ID not defined');
129
- }
130
- if (!client_secret) {
131
- throw new Error('OAuth 2.0 client secret not defined');
132
- }
133
- if (!redirect_uri) {
134
- throw new Error('OAuth 2.0 redirect URI not defined');
135
- }
136
- if (!authorization_endpoint) {
137
- throw new Error('OAuth 2.0 authorization endpoint not defined');
138
- }
139
- client = new OAuth2.Client({
140
- client_id,
141
- client_secret,
142
- redirect_uri,
143
- authorization_endpoint,
144
- token_endpoint,
145
- headers,
146
- store
147
- });
148
- }
149
- return client;
150
- }
151
- export async function getToken() {
152
- return await getClient().getToken();
153
- }
154
- export async function request(url, method = 'GET', body, headers, options) {
155
- return await getClient().request(url, method, body, headers, options);
156
- }
157
- export async function requestJSON(url, method = 'GET', body, headers, options) {
158
- return (await getClient().requestJSON(url, method, body, headers, options));
159
- }