@quonfig/cli 0.0.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/LICENSE +21 -0
- package/README.md +707 -0
- package/bin/dev.cmd +3 -0
- package/bin/dev.js +9 -0
- package/bin/run.cmd +3 -0
- package/bin/run.js +8 -0
- package/dist/api/get-environments.d.ts +10 -0
- package/dist/api/get-environments.js +11 -0
- package/dist/codegen/code-generators/base-generator.d.ts +11 -0
- package/dist/codegen/code-generators/base-generator.js +8 -0
- package/dist/codegen/code-generators/base-typescript-generator.d.ts +17 -0
- package/dist/codegen/code-generators/base-typescript-generator.js +35 -0
- package/dist/codegen/code-generators/node-typescript-generator.d.ts +8 -0
- package/dist/codegen/code-generators/node-typescript-generator.js +96 -0
- package/dist/codegen/code-generators/react-typescript-generator.d.ts +11 -0
- package/dist/codegen/code-generators/react-typescript-generator.js +116 -0
- package/dist/codegen/config-downloader.d.ts +7 -0
- package/dist/codegen/config-downloader.js +26 -0
- package/dist/codegen/json-schema-to-zod.d.ts +3 -0
- package/dist/codegen/json-schema-to-zod.js +170 -0
- package/dist/codegen/language-mappers/json-to-zod-mapper.d.ts +4 -0
- package/dist/codegen/language-mappers/json-to-zod-mapper.js +55 -0
- package/dist/codegen/language-mappers/zod-base-mapper.d.ts +35 -0
- package/dist/codegen/language-mappers/zod-base-mapper.js +111 -0
- package/dist/codegen/language-mappers/zod-to-string-mapper.d.ts +25 -0
- package/dist/codegen/language-mappers/zod-to-string-mapper.js +87 -0
- package/dist/codegen/language-mappers/zod-to-typescript-mapper.d.ts +35 -0
- package/dist/codegen/language-mappers/zod-to-typescript-mapper.js +133 -0
- package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.d.ts +36 -0
- package/dist/codegen/language-mappers/zod-to-typescript-return-value-mapper.js +150 -0
- package/dist/codegen/mustache-extractor.d.ts +6 -0
- package/dist/codegen/mustache-extractor.js +83 -0
- package/dist/codegen/schema-evaluator.d.ts +37 -0
- package/dist/codegen/schema-evaluator.js +283 -0
- package/dist/codegen/schema-extractor.d.ts +24 -0
- package/dist/codegen/schema-extractor.js +265 -0
- package/dist/codegen/types.d.ts +44 -0
- package/dist/codegen/types.js +5 -0
- package/dist/codegen/zod-introspection.d.ts +85 -0
- package/dist/codegen/zod-introspection.js +205 -0
- package/dist/commands/create.d.ts +19 -0
- package/dist/commands/create.js +175 -0
- package/dist/commands/download.d.ts +13 -0
- package/dist/commands/download.js +63 -0
- package/dist/commands/generate-new-hex-key.d.ts +7 -0
- package/dist/commands/generate-new-hex-key.js +10 -0
- package/dist/commands/generate.d.ts +16 -0
- package/dist/commands/generate.js +220 -0
- package/dist/commands/get.d.ts +13 -0
- package/dist/commands/get.js +141 -0
- package/dist/commands/info.d.ts +21 -0
- package/dist/commands/info.js +464 -0
- package/dist/commands/interactive.d.ts +5 -0
- package/dist/commands/interactive.js +8 -0
- package/dist/commands/list.d.ts +13 -0
- package/dist/commands/list.js +61 -0
- package/dist/commands/login.d.ts +10 -0
- package/dist/commands/login.js +124 -0
- package/dist/commands/logout.d.ts +7 -0
- package/dist/commands/logout.js +12 -0
- package/dist/commands/mcp.d.ts +13 -0
- package/dist/commands/mcp.js +145 -0
- package/dist/commands/override.d.ts +17 -0
- package/dist/commands/override.js +170 -0
- package/dist/commands/profile.d.ts +7 -0
- package/dist/commands/profile.js +47 -0
- package/dist/commands/schema.d.ts +21 -0
- package/dist/commands/schema.js +168 -0
- package/dist/commands/serve.d.ts +13 -0
- package/dist/commands/serve.js +99 -0
- package/dist/commands/set-default.d.ts +20 -0
- package/dist/commands/set-default.js +273 -0
- package/dist/commands/verify.d.ts +12 -0
- package/dist/commands/verify.js +48 -0
- package/dist/commands/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +52 -0
- package/dist/commands/workspace.d.ts +7 -0
- package/dist/commands/workspace.js +38 -0
- package/dist/constants.d.ts +2 -0
- package/dist/constants.js +2 -0
- package/dist/help.d.ts +4 -0
- package/dist/help.js +10 -0
- package/dist/index.d.ts +40 -0
- package/dist/index.js +112 -0
- package/dist/interactive-prompt.d.ts +2 -0
- package/dist/interactive-prompt.js +162 -0
- package/dist/quonfig.d.ts +15 -0
- package/dist/quonfig.js +75 -0
- package/dist/result.d.ts +35 -0
- package/dist/result.js +9 -0
- package/dist/services/mcp.d.ts +15 -0
- package/dist/services/mcp.js +59 -0
- package/dist/ui/get-confirmation.d.ts +10 -0
- package/dist/ui/get-confirmation.js +4 -0
- package/dist/ui/get-environment.d.ts +11 -0
- package/dist/ui/get-environment.js +42 -0
- package/dist/ui/get-key.d.ts +27 -0
- package/dist/ui/get-key.js +15 -0
- package/dist/ui/get-string.d.ts +5 -0
- package/dist/ui/get-string.js +8 -0
- package/dist/ui/get-value.d.ts +18 -0
- package/dist/ui/get-value.js +46 -0
- package/dist/util/autocomplete.d.ts +6 -0
- package/dist/util/autocomplete.js +25 -0
- package/dist/util/coerce.d.ts +7 -0
- package/dist/util/coerce.js +62 -0
- package/dist/util/color.d.ts +3 -0
- package/dist/util/color.js +4 -0
- package/dist/util/config-value-dto.d.ts +16 -0
- package/dist/util/config-value-dto.js +82 -0
- package/dist/util/context.d.ts +18 -0
- package/dist/util/context.js +10 -0
- package/dist/util/domain-urls.d.ts +3 -0
- package/dist/util/domain-urls.js +18 -0
- package/dist/util/encryption.d.ts +20 -0
- package/dist/util/encryption.js +153 -0
- package/dist/util/file-manager.d.ts +12 -0
- package/dist/util/file-manager.js +16 -0
- package/dist/util/get-client.d.ts +7 -0
- package/dist/util/get-client.js +127 -0
- package/dist/util/is-interactive.d.ts +5 -0
- package/dist/util/is-interactive.js +7 -0
- package/dist/util/json-maybe.d.ts +3 -0
- package/dist/util/json-maybe.js +14 -0
- package/dist/util/name-arg.d.ts +4 -0
- package/dist/util/name-arg.js +3 -0
- package/dist/util/oauth-client.d.ts +30 -0
- package/dist/util/oauth-client.js +99 -0
- package/dist/util/secret-flags.d.ts +13 -0
- package/dist/util/secret-flags.js +13 -0
- package/dist/util/token-storage.d.ts +25 -0
- package/dist/util/token-storage.js +90 -0
- package/dist/validations/value.d.ts +4 -0
- package/dist/validations/value.js +11 -0
- package/dist/verify/standalone.d.ts +14 -0
- package/dist/verify/standalone.js +133 -0
- package/dist/verify/validate.d.ts +43 -0
- package/dist/verify/validate.js +756 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.js +2 -0
- package/oclif.manifest.json +1429 -0
- package/package.json +104 -0
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { BaseCommand } from '../index.js';
|
|
2
|
+
import { decodeJWT } from '../util/oauth-client.js';
|
|
3
|
+
import { getActiveProfile, loadAuthConfig, loadTokens } from '../util/token-storage.js';
|
|
4
|
+
export default class Whoami extends BaseCommand {
|
|
5
|
+
static description = 'Display information about the currently logged in user';
|
|
6
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
7
|
+
async run() {
|
|
8
|
+
const authConfig = await loadAuthConfig();
|
|
9
|
+
const tokens = await loadTokens();
|
|
10
|
+
if (!authConfig || !tokens?.accessToken) {
|
|
11
|
+
this.log('Not logged in. Use `qfg login` to authenticate.');
|
|
12
|
+
return {
|
|
13
|
+
loggedIn: false,
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
// Get email from stored user info or decode JWT
|
|
17
|
+
let userEmail = tokens.userEmail;
|
|
18
|
+
let orgId;
|
|
19
|
+
if (!userEmail) {
|
|
20
|
+
try {
|
|
21
|
+
const payload = decodeJWT(tokens.accessToken);
|
|
22
|
+
userEmail = payload.email;
|
|
23
|
+
orgId = payload.org_id;
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
// If we can't decode the token, continue without email
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
// Get the active profile
|
|
30
|
+
const activeProfile = getActiveProfile();
|
|
31
|
+
const profile = authConfig.profiles[activeProfile] || authConfig.profiles[authConfig.defaultProfile || 'default'];
|
|
32
|
+
if (!profile) {
|
|
33
|
+
this.log('Not logged in. Use `qfg login` to authenticate.');
|
|
34
|
+
return {
|
|
35
|
+
loggedIn: false,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
// Display user information
|
|
39
|
+
this.log(`Logged in as: ${userEmail || 'Unknown'}`);
|
|
40
|
+
this.log(`Active profile: ${activeProfile}`);
|
|
41
|
+
this.log(`Active workspace: ${profile.workspaceName || profile.workspace}`);
|
|
42
|
+
return {
|
|
43
|
+
email: userEmail,
|
|
44
|
+
loggedIn: true,
|
|
45
|
+
organizationId: orgId || profile.workspace,
|
|
46
|
+
profile: activeProfile,
|
|
47
|
+
userId: tokens.userId,
|
|
48
|
+
workspace: profile.workspace,
|
|
49
|
+
workspaceName: profile.workspaceName,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { BaseCommand } from '../index.js';
|
|
2
|
+
import { getActiveProfile, loadAuthConfig } from '../util/token-storage.js';
|
|
3
|
+
export default class Workspace extends BaseCommand {
|
|
4
|
+
static description = 'Display current workspace. To switch workspaces, run `qfg login --profile <name>`.';
|
|
5
|
+
static examples = ['<%= config.bin %> <%= command.id %>'];
|
|
6
|
+
async run() {
|
|
7
|
+
const authConfig = await loadAuthConfig();
|
|
8
|
+
if (!authConfig || Object.keys(authConfig.profiles).length === 0) {
|
|
9
|
+
return this.err('Not logged in. Please run `qfg login` first.');
|
|
10
|
+
}
|
|
11
|
+
// Get the active profile
|
|
12
|
+
const activeProfile = getActiveProfile();
|
|
13
|
+
const profile = authConfig.profiles[activeProfile] || authConfig.profiles[authConfig.defaultProfile || 'default'];
|
|
14
|
+
if (!profile) {
|
|
15
|
+
return this.err('No active profile found. Please run `qfg login` first.');
|
|
16
|
+
}
|
|
17
|
+
this.log(`Active profile: ${activeProfile}`);
|
|
18
|
+
this.log(`Workspace: ${profile.workspaceName || profile.workspace}`);
|
|
19
|
+
// Show all profiles if more than one
|
|
20
|
+
const profileNames = Object.keys(authConfig.profiles);
|
|
21
|
+
if (profileNames.length > 1) {
|
|
22
|
+
this.log('\nAll profiles:');
|
|
23
|
+
for (const name of profileNames) {
|
|
24
|
+
const p = authConfig.profiles[name];
|
|
25
|
+
const marker = name === activeProfile ? ' (active)' : '';
|
|
26
|
+
this.log(` ${name}: ${p.workspaceName || p.workspace}${marker}`);
|
|
27
|
+
}
|
|
28
|
+
this.log('\nTo switch, use: qfg login --profile <name>');
|
|
29
|
+
this.log('To change default: qfg profile');
|
|
30
|
+
}
|
|
31
|
+
return {
|
|
32
|
+
activeProfile,
|
|
33
|
+
profiles: Object.fromEntries(profileNames.map((name) => [name, authConfig.profiles[name]])),
|
|
34
|
+
workspace: profile.workspace,
|
|
35
|
+
workspaceName: profile.workspaceName,
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/help.d.ts
ADDED
package/dist/help.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { Help } from '@oclif/core';
|
|
2
|
+
import { interactivePrompt } from './interactive-prompt.js';
|
|
3
|
+
export default class MyHelpClass extends Help {
|
|
4
|
+
async showRootHelp() {
|
|
5
|
+
if (process.argv.slice(2).includes('--help')) {
|
|
6
|
+
return super.showRootHelp();
|
|
7
|
+
}
|
|
8
|
+
await interactivePrompt(this.config);
|
|
9
|
+
}
|
|
10
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
2
|
+
import { Command } from '@oclif/core';
|
|
3
|
+
import { Client } from '@quonfig/node';
|
|
4
|
+
import type { ProjectEnvId } from '@quonfig/node';
|
|
5
|
+
import { JsonObj, Result } from './result.js';
|
|
6
|
+
export declare abstract class BaseCommand extends Command {
|
|
7
|
+
static baseFlags: {
|
|
8
|
+
interactive: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
9
|
+
'no-color': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
10
|
+
verbose: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
11
|
+
};
|
|
12
|
+
static enableJsonFlag: boolean;
|
|
13
|
+
err: (error: Error | object | string, json?: JsonObj) => never;
|
|
14
|
+
isVerbose: boolean;
|
|
15
|
+
ok: (message: object | string, json?: JsonObj) => JsonObj;
|
|
16
|
+
resultMessage: (result: Result<unknown>) => string | JsonObj | null;
|
|
17
|
+
verboseLog: (category: string | unknown, message?: unknown) => void;
|
|
18
|
+
protected catch(err: {
|
|
19
|
+
exitCode?: number;
|
|
20
|
+
code?: string;
|
|
21
|
+
} & Error): Promise<void>;
|
|
22
|
+
init(): Promise<void>;
|
|
23
|
+
}
|
|
24
|
+
export declare abstract class APICommand extends BaseCommand {
|
|
25
|
+
static baseFlags: {
|
|
26
|
+
profile: import("@oclif/core/lib/interfaces/parser.js").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces/parser.js").CustomOptions>;
|
|
27
|
+
interactive: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
28
|
+
'no-color': import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
29
|
+
verbose: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
30
|
+
};
|
|
31
|
+
currentEnvironment: ProjectEnvId;
|
|
32
|
+
rawApiClient: Client;
|
|
33
|
+
workspaceId?: string;
|
|
34
|
+
get apiClient(): {
|
|
35
|
+
get: (path: string) => Promise<import("./result.js").RequestResult>;
|
|
36
|
+
post: (path: string, payload: unknown) => Promise<import("./result.js").RequestResult>;
|
|
37
|
+
put: (path: string, payload: unknown) => Promise<import("./result.js").RequestResult>;
|
|
38
|
+
};
|
|
39
|
+
init(): Promise<void>;
|
|
40
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
export { run } from '@oclif/core';
|
|
2
|
+
import { Command, Flags } from '@oclif/core';
|
|
3
|
+
import rawGetClient, { unwrapRequest } from './util/get-client.js';
|
|
4
|
+
const globalFlags = {
|
|
5
|
+
interactive: Flags.boolean({
|
|
6
|
+
allowNo: true,
|
|
7
|
+
default: true,
|
|
8
|
+
description: 'Force interactive mode',
|
|
9
|
+
helpGroup: 'GLOBAL',
|
|
10
|
+
}),
|
|
11
|
+
'no-color': Flags.boolean({
|
|
12
|
+
default: false,
|
|
13
|
+
description: 'Do not colorize output',
|
|
14
|
+
env: 'NO_COLOR',
|
|
15
|
+
helpGroup: 'GLOBAL',
|
|
16
|
+
}),
|
|
17
|
+
verbose: Flags.boolean({
|
|
18
|
+
default: false,
|
|
19
|
+
description: 'Verbose output',
|
|
20
|
+
helpGroup: 'GLOBAL',
|
|
21
|
+
}),
|
|
22
|
+
};
|
|
23
|
+
export class BaseCommand extends Command {
|
|
24
|
+
static baseFlags = {
|
|
25
|
+
...globalFlags,
|
|
26
|
+
};
|
|
27
|
+
static enableJsonFlag = true;
|
|
28
|
+
err = (error, json) => {
|
|
29
|
+
if (this.jsonEnabled()) {
|
|
30
|
+
throw json ?? error;
|
|
31
|
+
}
|
|
32
|
+
if (typeof error === 'string') {
|
|
33
|
+
return this.error(error, { code: 'ERR', exit: 1 });
|
|
34
|
+
}
|
|
35
|
+
this.error(this.toErrorJson(error), { code: 'ERR', exit: 1 });
|
|
36
|
+
};
|
|
37
|
+
isVerbose;
|
|
38
|
+
ok = (message, json) => {
|
|
39
|
+
if (typeof message === 'string') {
|
|
40
|
+
this.log(message);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
this.log(this.toSuccessJson(message));
|
|
44
|
+
}
|
|
45
|
+
return json ?? { message };
|
|
46
|
+
};
|
|
47
|
+
resultMessage = (result) => {
|
|
48
|
+
if (result.error) {
|
|
49
|
+
return this.err(result.message, result.json);
|
|
50
|
+
}
|
|
51
|
+
if (result.message) {
|
|
52
|
+
this.log(result.message);
|
|
53
|
+
return result.json ?? result.message;
|
|
54
|
+
}
|
|
55
|
+
return null;
|
|
56
|
+
};
|
|
57
|
+
verboseLog = (category, message) => {
|
|
58
|
+
if (!this.isVerbose)
|
|
59
|
+
return;
|
|
60
|
+
if (message) {
|
|
61
|
+
this.logToStderr(`[${category}] ${typeof message === 'string' ? message : JSON.stringify(message)}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
this.logToStderr(typeof category === 'string' ? category : JSON.stringify(category));
|
|
65
|
+
}
|
|
66
|
+
};
|
|
67
|
+
async catch(err) {
|
|
68
|
+
// Override oclif's default error handling to suppress stack traces in production
|
|
69
|
+
// but preserve error messages for tests
|
|
70
|
+
// In test environment, just re-throw the error
|
|
71
|
+
if (process.env.NODE_ENV === 'test') {
|
|
72
|
+
throw err;
|
|
73
|
+
}
|
|
74
|
+
// In production, log the error message without stack trace
|
|
75
|
+
this.log(err.message);
|
|
76
|
+
this.exit(err.exitCode || 1);
|
|
77
|
+
}
|
|
78
|
+
async init() {
|
|
79
|
+
await super.init();
|
|
80
|
+
const { flags } = await this.parse();
|
|
81
|
+
this.isVerbose = flags.verbose;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export class APICommand extends BaseCommand {
|
|
85
|
+
static baseFlags = {
|
|
86
|
+
...globalFlags,
|
|
87
|
+
profile: Flags.string({
|
|
88
|
+
char: 'p',
|
|
89
|
+
description: 'Profile to use (defaults to ENV var QUONFIG_PROFILE or "default")',
|
|
90
|
+
helpGroup: 'GLOBAL',
|
|
91
|
+
required: false,
|
|
92
|
+
}),
|
|
93
|
+
};
|
|
94
|
+
currentEnvironment;
|
|
95
|
+
rawApiClient;
|
|
96
|
+
workspaceId;
|
|
97
|
+
get apiClient() {
|
|
98
|
+
return {
|
|
99
|
+
get: async (path) => unwrapRequest(this, this.rawApiClient.get(path)),
|
|
100
|
+
post: async (path, payload) => unwrapRequest(this, this.rawApiClient.post(path, payload)),
|
|
101
|
+
put: async (path, payload) => unwrapRequest(this, this.rawApiClient.put(path, payload)),
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
async init() {
|
|
105
|
+
await super.init();
|
|
106
|
+
const { flags } = await this.parse();
|
|
107
|
+
this.rawApiClient = await rawGetClient(this, undefined, flags.profile);
|
|
108
|
+
// For JWT-based auth, we'll need to get environment info from the token
|
|
109
|
+
// For now, set a placeholder - this should be enhanced later
|
|
110
|
+
this.currentEnvironment = { id: 'unknown', projectId: 0 };
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import Create from './commands/create.js';
|
|
2
|
+
import Download from './commands/download.js';
|
|
3
|
+
import GenerateNewHexKey from './commands/generate-new-hex-key.js';
|
|
4
|
+
import Get from './commands/get.js';
|
|
5
|
+
import Info from './commands/info.js';
|
|
6
|
+
import List from './commands/list.js';
|
|
7
|
+
import Mcp from './commands/mcp.js';
|
|
8
|
+
import Override from './commands/override.js';
|
|
9
|
+
import Serve from './commands/serve.js';
|
|
10
|
+
import SetDefault from './commands/set-default.js';
|
|
11
|
+
import { APICommand } from './index.js';
|
|
12
|
+
import getString from './ui/get-string.js';
|
|
13
|
+
import autocomplete from './util/autocomplete.js';
|
|
14
|
+
import { green } from './util/color.js';
|
|
15
|
+
const convertToKebabCase = (text) => text
|
|
16
|
+
.replaceAll(/([A-Z])/g, ' $1')
|
|
17
|
+
.trim()
|
|
18
|
+
.replaceAll(' ', '-')
|
|
19
|
+
.toLowerCase();
|
|
20
|
+
const commands = [
|
|
21
|
+
SetDefault,
|
|
22
|
+
GenerateNewHexKey,
|
|
23
|
+
Create,
|
|
24
|
+
Get,
|
|
25
|
+
Info,
|
|
26
|
+
Download,
|
|
27
|
+
Mcp,
|
|
28
|
+
Override,
|
|
29
|
+
List,
|
|
30
|
+
Serve,
|
|
31
|
+
].map((command) => {
|
|
32
|
+
const id = convertToKebabCase(command.name);
|
|
33
|
+
return {
|
|
34
|
+
command,
|
|
35
|
+
description: command.description,
|
|
36
|
+
displayCommandName: id,
|
|
37
|
+
id,
|
|
38
|
+
implicitFlags: [],
|
|
39
|
+
};
|
|
40
|
+
});
|
|
41
|
+
commands.push({
|
|
42
|
+
command: SetDefault,
|
|
43
|
+
description: 'Set/update the default value for an environment to an ENV Var',
|
|
44
|
+
displayCommandName: 'set-default --env-var',
|
|
45
|
+
id: 'set-default',
|
|
46
|
+
implicitFlags: ['env-var'],
|
|
47
|
+
}, {
|
|
48
|
+
command: SetDefault,
|
|
49
|
+
description: 'Set/update the default value for an environment with --secret',
|
|
50
|
+
displayCommandName: 'set-default --secret',
|
|
51
|
+
id: 'set-default',
|
|
52
|
+
implicitFlags: ['secret'],
|
|
53
|
+
}, {
|
|
54
|
+
command: Create,
|
|
55
|
+
description: 'Create a new item in Quonfig with --secret',
|
|
56
|
+
displayCommandName: 'create --secret',
|
|
57
|
+
id: 'create',
|
|
58
|
+
implicitFlags: ['secret'],
|
|
59
|
+
}, {
|
|
60
|
+
command: Create,
|
|
61
|
+
description: 'Create an ENV Var provided item in Quonfig with --env-var',
|
|
62
|
+
displayCommandName: 'create --env-var',
|
|
63
|
+
id: 'create',
|
|
64
|
+
implicitFlags: ['env-var'],
|
|
65
|
+
});
|
|
66
|
+
const descriptions = {};
|
|
67
|
+
for (const command of commands) {
|
|
68
|
+
descriptions[command.displayCommandName] = command.description.split('\n')[0];
|
|
69
|
+
}
|
|
70
|
+
const isAPICommand = (command) =>
|
|
71
|
+
// Check if command extends APICommand by checking prototype chain
|
|
72
|
+
command.prototype instanceof APICommand;
|
|
73
|
+
const promptForInput = async (arg, name, commandId, command) => {
|
|
74
|
+
let value;
|
|
75
|
+
const message = arg.description ?? name;
|
|
76
|
+
if (arg.options) {
|
|
77
|
+
value = await autocomplete({ message, source: arg.options });
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
switch (arg.type) {
|
|
81
|
+
case 'option': {
|
|
82
|
+
// APICommand-based commands handle their own "name" arg prompting with autocomplete from metadata API
|
|
83
|
+
// Skip prompting here for those commands (except create which needs the name upfront)
|
|
84
|
+
if (name === 'name' && commandId !== 'create' && isAPICommand(command)) {
|
|
85
|
+
// Skip prompting - let the OAuth command handle it interactively
|
|
86
|
+
value = undefined;
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
value = await getString({ allowBlank: false, message });
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
default: {
|
|
94
|
+
throw new Error(`Unknown arg type: ${arg.type}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (!value && name !== 'name') {
|
|
99
|
+
throw new Error(`Missing required argument: ${name}`);
|
|
100
|
+
}
|
|
101
|
+
return value;
|
|
102
|
+
};
|
|
103
|
+
const getArgs = async (args, commandId, command) => {
|
|
104
|
+
const requiredArgs = Object.keys(args);
|
|
105
|
+
const values = await Promise.all(Object.entries(args)
|
|
106
|
+
.filter(([key]) => requiredArgs.includes(key))
|
|
107
|
+
.map(([key, arg]) => promptForInput(arg, key, commandId, command)));
|
|
108
|
+
// Filter out undefined values (OAuth commands handle their own name prompting)
|
|
109
|
+
return values.filter((v) => v !== undefined);
|
|
110
|
+
};
|
|
111
|
+
const getFlags = async (flags, commandId, implicitFlags, command) => {
|
|
112
|
+
if (!flags && implicitFlags.length === 0) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
const inputs = [];
|
|
116
|
+
const promiseInputs = [];
|
|
117
|
+
for (const [key, arg] of Object.entries(flags)) {
|
|
118
|
+
if (arg.required || implicitFlags.includes(key)) {
|
|
119
|
+
if (arg.type === 'boolean') {
|
|
120
|
+
inputs.push(`--${key}`);
|
|
121
|
+
}
|
|
122
|
+
else {
|
|
123
|
+
// eslint-disable-next-line no-await-in-loop
|
|
124
|
+
const value = await promptForInput(arg, key, commandId, command);
|
|
125
|
+
inputs.push(`--${key}=${value}`);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
await Promise.all(promiseInputs);
|
|
130
|
+
return inputs;
|
|
131
|
+
};
|
|
132
|
+
export const interactivePrompt = async (config) => {
|
|
133
|
+
const longestCommandId = commands
|
|
134
|
+
.map((c) => c.displayCommandName.length)
|
|
135
|
+
.sort((a, b) => a - b)
|
|
136
|
+
.reverse()[0];
|
|
137
|
+
const namesAndDescriptions = commands
|
|
138
|
+
.map((command) => ({
|
|
139
|
+
description: `${command.displayCommandName}${' '.repeat(longestCommandId - command.displayCommandName.length + 4)} ${descriptions[command.displayCommandName]}`.replaceAll('the provided', 'a'),
|
|
140
|
+
id: command.displayCommandName,
|
|
141
|
+
}))
|
|
142
|
+
.sort((a, b) => a.description.localeCompare(b.description));
|
|
143
|
+
const selectedChoice = await autocomplete({
|
|
144
|
+
message: green('What would you like to do?'),
|
|
145
|
+
source: namesAndDescriptions.map((c) => c.description),
|
|
146
|
+
});
|
|
147
|
+
const selectedCommandId = namesAndDescriptions.find((c) => c.description === selectedChoice)?.id;
|
|
148
|
+
if (selectedCommandId) {
|
|
149
|
+
const chosenCommand = commands.find((c) => c.displayCommandName === selectedCommandId);
|
|
150
|
+
if (!chosenCommand) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const cliArgs = process.argv.slice(2);
|
|
154
|
+
const args = await getArgs(chosenCommand.command.args, chosenCommand.id, chosenCommand.command);
|
|
155
|
+
const flags = await getFlags(chosenCommand.command.flags, chosenCommand.id, chosenCommand.implicitFlags, chosenCommand.command);
|
|
156
|
+
const allArgs = [...cliArgs, ...args, ...flags];
|
|
157
|
+
if (process.env.VERBOSE) {
|
|
158
|
+
allArgs.push('--verbose');
|
|
159
|
+
}
|
|
160
|
+
await config.runCommand(chosenCommand.id, allArgs);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { Quonfig } from '@quonfig/node';
|
|
2
|
+
import type { Value } from '@quonfig/node';
|
|
3
|
+
import { CommandLike } from './ui/get-key.js';
|
|
4
|
+
type Flags = {
|
|
5
|
+
['sdk-key']?: string;
|
|
6
|
+
};
|
|
7
|
+
type FlagsOrDatafile = Flags | string;
|
|
8
|
+
export declare const initQuonfig: (_ctx: CommandLike, flagsOrDatafile: FlagsOrDatafile) => Promise<Quonfig>;
|
|
9
|
+
export declare const getQuonfig: () => Quonfig;
|
|
10
|
+
export declare const overrideFor: ({ currentEnvironmentId, key, }: {
|
|
11
|
+
currentEnvironmentId: string;
|
|
12
|
+
key: string;
|
|
13
|
+
}) => Value | undefined;
|
|
14
|
+
export declare const defaultValueFor: (_envId: string, key: string) => Value | undefined;
|
|
15
|
+
export {};
|
package/dist/quonfig.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Quonfig } from '@quonfig/node';
|
|
2
|
+
let quonfig;
|
|
3
|
+
const DEFAULT_CONTEXT_USER_ID_NAMESPACE = 'prefab-api-key';
|
|
4
|
+
const DEFAULT_CONTEXT_USER_ID = 'user-id';
|
|
5
|
+
export const initQuonfig = async (_ctx, flagsOrDatafile) => {
|
|
6
|
+
let sdkKey = 'NO_API_KEY';
|
|
7
|
+
let datafile;
|
|
8
|
+
if (typeof flagsOrDatafile === 'string') {
|
|
9
|
+
datafile = flagsOrDatafile;
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
if (!flagsOrDatafile['sdk-key']) {
|
|
13
|
+
throw new Error(`SDK key is required`);
|
|
14
|
+
}
|
|
15
|
+
sdkKey = flagsOrDatafile['sdk-key'];
|
|
16
|
+
}
|
|
17
|
+
const apiUrl = process.env.QUONFIG_API_URL || 'https://api.quonfig.com';
|
|
18
|
+
quonfig = new Quonfig({
|
|
19
|
+
sdkKey,
|
|
20
|
+
collectEvaluationSummaries: false,
|
|
21
|
+
collectLoggerCounts: false,
|
|
22
|
+
contextUploadMode: 'none',
|
|
23
|
+
datafile,
|
|
24
|
+
enableSSE: false,
|
|
25
|
+
apiUrl,
|
|
26
|
+
});
|
|
27
|
+
await quonfig.init();
|
|
28
|
+
return quonfig;
|
|
29
|
+
};
|
|
30
|
+
export const getQuonfig = () => quonfig;
|
|
31
|
+
/**
|
|
32
|
+
* Get the raw ConfigResponse for a key (new quonfig JSON format).
|
|
33
|
+
* The new format uses `default.rules` and `environment.rules` instead of `rows`.
|
|
34
|
+
*/
|
|
35
|
+
const getRawConfig = (key) => {
|
|
36
|
+
return quonfig.rawConfig?.(key);
|
|
37
|
+
};
|
|
38
|
+
/**
|
|
39
|
+
* Find rules in an environment that target a specific user via criteria.
|
|
40
|
+
*/
|
|
41
|
+
const getEnvironmentRules = (config) => {
|
|
42
|
+
return config.environment?.rules;
|
|
43
|
+
};
|
|
44
|
+
export const overrideFor = ({ currentEnvironmentId, key, }) => {
|
|
45
|
+
const config = getRawConfig(key);
|
|
46
|
+
if (!config)
|
|
47
|
+
return undefined;
|
|
48
|
+
// In the new format, environment-specific rules are in config.environment.rules
|
|
49
|
+
const rules = getEnvironmentRules(config);
|
|
50
|
+
if (!rules)
|
|
51
|
+
return undefined;
|
|
52
|
+
for (const rule of rules) {
|
|
53
|
+
for (const criterion of rule.criteria) {
|
|
54
|
+
if (criterion.propertyName === `${DEFAULT_CONTEXT_USER_ID_NAMESPACE}.${DEFAULT_CONTEXT_USER_ID}`) {
|
|
55
|
+
return rule.value;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
};
|
|
61
|
+
export const defaultValueFor = (_envId, key) => {
|
|
62
|
+
const config = getRawConfig(key);
|
|
63
|
+
if (!config)
|
|
64
|
+
return undefined;
|
|
65
|
+
// In the new format, the default value is the last rule in environment rules, or default rules
|
|
66
|
+
const envRules = config.environment?.rules;
|
|
67
|
+
if (envRules && envRules.length > 0) {
|
|
68
|
+
return envRules[envRules.length - 1]?.value;
|
|
69
|
+
}
|
|
70
|
+
const defaultRules = config.default?.rules;
|
|
71
|
+
if (defaultRules && defaultRules.length > 0) {
|
|
72
|
+
return defaultRules[defaultRules.length - 1]?.value;
|
|
73
|
+
}
|
|
74
|
+
return undefined;
|
|
75
|
+
};
|
package/dist/result.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
export type Result<T> = Failure | Noop | Success<T>;
|
|
2
|
+
export type JsonObj = Record<string, unknown>;
|
|
3
|
+
export type Noop = {
|
|
4
|
+
error: false;
|
|
5
|
+
json?: JsonObj;
|
|
6
|
+
message?: string;
|
|
7
|
+
ok: false;
|
|
8
|
+
};
|
|
9
|
+
export type Failure = {
|
|
10
|
+
error: true;
|
|
11
|
+
json?: JsonObj;
|
|
12
|
+
message: string;
|
|
13
|
+
ok: false;
|
|
14
|
+
};
|
|
15
|
+
export type Success<T> = {
|
|
16
|
+
error: false;
|
|
17
|
+
json?: JsonObj;
|
|
18
|
+
message?: string;
|
|
19
|
+
ok: true;
|
|
20
|
+
value: T;
|
|
21
|
+
};
|
|
22
|
+
export declare const noop: (message?: string) => Noop;
|
|
23
|
+
export declare const failure: (message: string, json?: JsonObj) => Failure;
|
|
24
|
+
export declare const success: <T>(value: T, message?: string, json?: JsonObj) => Success<T>;
|
|
25
|
+
export type RequestSuccess = {
|
|
26
|
+
json: JsonObj;
|
|
27
|
+
ok: true;
|
|
28
|
+
status: number;
|
|
29
|
+
};
|
|
30
|
+
export type RequestFailure = {
|
|
31
|
+
error: JsonObj;
|
|
32
|
+
ok: false;
|
|
33
|
+
status: number;
|
|
34
|
+
};
|
|
35
|
+
export type RequestResult = RequestFailure | RequestSuccess;
|
package/dist/result.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const noop = (message) => ({ error: false, message, ok: false });
|
|
2
|
+
export const failure = (message, json) => ({ error: true, json, message, ok: false });
|
|
3
|
+
export const success = (value, message, json) => ({
|
|
4
|
+
error: false,
|
|
5
|
+
json,
|
|
6
|
+
message,
|
|
7
|
+
ok: true,
|
|
8
|
+
value,
|
|
9
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export declare const SupportedEditors: readonly ["claude-code", "codeium"];
|
|
2
|
+
export type SupportedEditor = (typeof SupportedEditors)[number];
|
|
3
|
+
type ConfigPaths = {
|
|
4
|
+
name: string;
|
|
5
|
+
global: {
|
|
6
|
+
mac: string;
|
|
7
|
+
windows: string;
|
|
8
|
+
linux?: string;
|
|
9
|
+
} | string;
|
|
10
|
+
local?: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const ConfigPaths: Record<SupportedEditor, ConfigPaths>;
|
|
13
|
+
export declare function resolveConfigPath(editor: SupportedEditor, local?: boolean): string | undefined;
|
|
14
|
+
export declare function getServersConfig(editorConfig: Record<string, unknown>): Record<string, unknown>;
|
|
15
|
+
export {};
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
export const SupportedEditors = ['claude-code', 'codeium'];
|
|
4
|
+
export const ConfigPaths = {
|
|
5
|
+
// cursor: {
|
|
6
|
+
// name: 'Cursor',
|
|
7
|
+
// global: '~/.cursor/mcp.json',
|
|
8
|
+
// local: '.cursor/mcp.json',
|
|
9
|
+
// },
|
|
10
|
+
'claude-code': {
|
|
11
|
+
name: 'Claude Code',
|
|
12
|
+
global: '~/.claude.json',
|
|
13
|
+
},
|
|
14
|
+
codeium: {
|
|
15
|
+
name: 'Codeium (Codex)',
|
|
16
|
+
global: {
|
|
17
|
+
mac: '~/Library/Application Support/Codeium/mcp_config.json',
|
|
18
|
+
windows: '@/Codeium/mcp_config.json',
|
|
19
|
+
linux: '~/.config/codeium/mcp_config.json',
|
|
20
|
+
},
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
function resolvePath(path) {
|
|
24
|
+
if (path.startsWith('~/')) {
|
|
25
|
+
return join(homedir(), path.slice(2));
|
|
26
|
+
}
|
|
27
|
+
if (path.startsWith('@/')) {
|
|
28
|
+
// Windows AppData
|
|
29
|
+
const appData = process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
30
|
+
return join(appData, path.slice(2));
|
|
31
|
+
}
|
|
32
|
+
return path;
|
|
33
|
+
}
|
|
34
|
+
export function resolveConfigPath(editor, local = false) {
|
|
35
|
+
const editorConfig = ConfigPaths[editor];
|
|
36
|
+
const paths = local ? editorConfig.local : editorConfig.global;
|
|
37
|
+
if (!paths)
|
|
38
|
+
return undefined;
|
|
39
|
+
if (typeof paths === 'string') {
|
|
40
|
+
return resolvePath(paths);
|
|
41
|
+
}
|
|
42
|
+
// Platform-specific paths
|
|
43
|
+
switch (process.platform) {
|
|
44
|
+
case 'darwin':
|
|
45
|
+
return resolvePath(paths.mac);
|
|
46
|
+
case 'win32':
|
|
47
|
+
return resolvePath(paths.windows);
|
|
48
|
+
case 'linux':
|
|
49
|
+
return paths.linux ? resolvePath(paths.linux) : undefined;
|
|
50
|
+
default:
|
|
51
|
+
return undefined;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
export function getServersConfig(editorConfig) {
|
|
55
|
+
if (!editorConfig.mcpServers) {
|
|
56
|
+
editorConfig.mcpServers = {};
|
|
57
|
+
}
|
|
58
|
+
return editorConfig.mcpServers;
|
|
59
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export declare const confirmFlag: {
|
|
2
|
+
confirm: import("@oclif/core/lib/interfaces/parser.js").BooleanFlag<boolean>;
|
|
3
|
+
};
|
|
4
|
+
declare const getConfirmation: ({ flags, message }: {
|
|
5
|
+
flags: {
|
|
6
|
+
confirm?: boolean;
|
|
7
|
+
};
|
|
8
|
+
message: string;
|
|
9
|
+
}) => Promise<boolean>;
|
|
10
|
+
export default getConfirmation;
|