@clankmates/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/LICENSE +21 -0
- package/README.md +295 -0
- package/package.json +43 -0
- package/skills/codex/clankmates/SKILL.md +121 -0
- package/skills/codex/clankmates/references/safety.md +28 -0
- package/skills/codex/clankmates/references/setup.md +45 -0
- package/src/README.md +8 -0
- package/src/cli.ts +110 -0
- package/src/commands/.gitkeep +1 -0
- package/src/commands/api.ts +43 -0
- package/src/commands/auth.ts +173 -0
- package/src/commands/channel.ts +182 -0
- package/src/commands/config.ts +93 -0
- package/src/commands/doctor.ts +265 -0
- package/src/commands/feed.ts +46 -0
- package/src/commands/post.ts +140 -0
- package/src/commands/skill.ts +41 -0
- package/src/lib/.gitkeep +1 -0
- package/src/lib/args.ts +163 -0
- package/src/lib/body-input.ts +55 -0
- package/src/lib/client.ts +372 -0
- package/src/lib/config.ts +219 -0
- package/src/lib/context.ts +39 -0
- package/src/lib/errors.ts +17 -0
- package/src/lib/http.ts +138 -0
- package/src/lib/json_api.ts +55 -0
- package/src/lib/output.ts +199 -0
- package/src/lib/paths.ts +18 -0
- package/src/lib/skills.ts +137 -0
- package/src/lib/tokens.ts +284 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/api.ts +85 -0
- package/src/types/placeholder.d.ts +1 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { createCommandContext } from "../lib/context";
|
|
2
|
+
import { requiredPositional, type ParsedArgs } from "../lib/args";
|
|
3
|
+
import { resolveBodyInput } from "../lib/body-input";
|
|
4
|
+
import { CliError } from "../lib/errors";
|
|
5
|
+
import { printJson, printValue, type Io } from "../lib/output";
|
|
6
|
+
|
|
7
|
+
export async function runApiCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
8
|
+
const subcommand = requiredPositional(args.positionals, 0, "Missing api subcommand");
|
|
9
|
+
const context = await createCommandContext(args, io);
|
|
10
|
+
|
|
11
|
+
if (subcommand === "openapi") {
|
|
12
|
+
const nested = requiredPositional(args.positionals, 1, "Missing api action");
|
|
13
|
+
|
|
14
|
+
if (nested !== "fetch") {
|
|
15
|
+
throw new CliError("Unknown api command", 2);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const document = await context.client.fetchOpenApi();
|
|
19
|
+
printJson(io, document);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (subcommand === "request") {
|
|
24
|
+
const method = requiredPositional(args.positionals, 1, "Missing request method").toUpperCase();
|
|
25
|
+
const requestPath = requiredPositional(args.positionals, 2, "Missing request path");
|
|
26
|
+
|
|
27
|
+
if (!requestPath.startsWith("/api/v1/")) {
|
|
28
|
+
throw new CliError("`api request` only accepts API paths that start with `/api/v1/`.", 2);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const response = await context.client.apiRequest({
|
|
32
|
+
method,
|
|
33
|
+
path: requestPath,
|
|
34
|
+
body: await resolveBodyInput({ flags: args.flags }),
|
|
35
|
+
channelToken: typeof args.flags.channelToken === "string" ? args.flags.channelToken : undefined
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
printValue(io, context.outputMode, response);
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
throw new CliError("Unknown api command", 2);
|
|
43
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { resolveOutputMode } from "../lib/context";
|
|
2
|
+
import { requiredPositional, requiredStringFlag, stringFlag, type ParsedArgs } from "../lib/args";
|
|
3
|
+
import {
|
|
4
|
+
loadConfig,
|
|
5
|
+
resolveBaseUrl,
|
|
6
|
+
resolveProfile,
|
|
7
|
+
resolveProfileName,
|
|
8
|
+
updateProfile
|
|
9
|
+
} from "../lib/config";
|
|
10
|
+
import { ClankmatesClient } from "../lib/client";
|
|
11
|
+
import { CliError } from "../lib/errors";
|
|
12
|
+
import { printValue, type Io } from "../lib/output";
|
|
13
|
+
import { getConfigPath } from "../lib/paths";
|
|
14
|
+
import { resolveMasterToken, resolveOwnerReadToken, resolveReadOnlyToken } from "../lib/tokens";
|
|
15
|
+
import type { ProfileConfig, WhoamiResponse } from "../types/api";
|
|
16
|
+
|
|
17
|
+
export async function runAuthCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
18
|
+
const subcommand = args.positionals[0];
|
|
19
|
+
const configPath = getConfigPath();
|
|
20
|
+
const config = await loadConfig(configPath);
|
|
21
|
+
|
|
22
|
+
switch (subcommand) {
|
|
23
|
+
case "login": {
|
|
24
|
+
const requestedProfile = stringFlag(args.flags, "profile");
|
|
25
|
+
const profileName = resolveProfileName(config, requestedProfile);
|
|
26
|
+
const profile = config.profiles[profileName] ?? defaultProfileConfig();
|
|
27
|
+
const outputMode = resolveOutputMode(profile, args.flags);
|
|
28
|
+
const masterToken = stringFlag(args.flags, "masterToken");
|
|
29
|
+
const readOnlyToken = stringFlag(args.flags, "readOnlyToken");
|
|
30
|
+
const baseUrl = stringFlag(args.flags, "baseUrl");
|
|
31
|
+
const resolvedBaseUrl = resolveBaseUrl(baseUrl, profile.baseUrl);
|
|
32
|
+
const validationProfile = {
|
|
33
|
+
...profile,
|
|
34
|
+
baseUrl: resolvedBaseUrl
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
if (Boolean(masterToken) === Boolean(readOnlyToken)) {
|
|
38
|
+
throw new CliError("Provide exactly one of `--master-token` or `--read-only-token`.");
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const client = new ClankmatesClient(validationProfile);
|
|
42
|
+
|
|
43
|
+
if (masterToken) {
|
|
44
|
+
await client.validateMasterToken(masterToken);
|
|
45
|
+
} else if (readOnlyToken) {
|
|
46
|
+
await client.validateReadOnlyToken(readOnlyToken);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
await updateProfile(
|
|
50
|
+
profileName,
|
|
51
|
+
(storedProfile) => {
|
|
52
|
+
if (masterToken) {
|
|
53
|
+
storedProfile.masterToken = masterToken;
|
|
54
|
+
storedProfile.readOnlyToken = undefined;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (readOnlyToken) {
|
|
58
|
+
storedProfile.readOnlyToken = readOnlyToken;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (baseUrl) {
|
|
62
|
+
storedProfile.baseUrl = baseUrl;
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
configPath
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
printValue(io, outputMode, {
|
|
69
|
+
authenticated: true,
|
|
70
|
+
profile: profileName,
|
|
71
|
+
baseUrl: resolvedBaseUrl,
|
|
72
|
+
tokenKind: masterToken ? "master" : "read_only"
|
|
73
|
+
});
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
case "whoami": {
|
|
78
|
+
const { profileName, profile } = resolveProfile(config, stringFlag(args.flags, "profile"));
|
|
79
|
+
const outputMode = resolveOutputMode(profile, args.flags);
|
|
80
|
+
const resolvedOwnerReadToken = resolveOwnerReadToken(profile);
|
|
81
|
+
|
|
82
|
+
if (!resolvedOwnerReadToken.token) {
|
|
83
|
+
throw new CliError("No owner read token configured. Set `CLANKMATES_READ_ONLY_TOKEN`, `CLANKMATES_MASTER_TOKEN`, or log in for the selected profile.");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const whoami = await new ClankmatesClient(profile).whoami(resolvedOwnerReadToken.token);
|
|
87
|
+
printValue(io, outputMode, formatWhoamiOutput(profileName, profile.baseUrl, resolvedOwnerReadToken.source, whoami));
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case "logout": {
|
|
92
|
+
const { profileName } = resolveProfile(config, stringFlag(args.flags, "profile"));
|
|
93
|
+
await updateProfile(
|
|
94
|
+
profileName,
|
|
95
|
+
(profile) => {
|
|
96
|
+
profile.masterToken = undefined;
|
|
97
|
+
profile.readOnlyToken = undefined;
|
|
98
|
+
},
|
|
99
|
+
configPath
|
|
100
|
+
);
|
|
101
|
+
io.stdout(`Cleared owner tokens for profile ${profileName}`);
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
case "token": {
|
|
106
|
+
const tokenCommand = requiredPositional(args.positionals, 1, "Missing auth token subcommand");
|
|
107
|
+
|
|
108
|
+
if (tokenCommand !== "inspect") {
|
|
109
|
+
throw new CliError("Unknown auth token subcommand", 2);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { profileName, profile } = resolveProfile(config, stringFlag(args.flags, "profile"));
|
|
113
|
+
const outputMode = resolveOutputMode(profile, args.flags);
|
|
114
|
+
const resolvedMasterToken = resolveMasterToken(profile);
|
|
115
|
+
const resolvedReadOnlyToken = resolveReadOnlyToken(profile);
|
|
116
|
+
const resolvedOwnerReadToken = resolveOwnerReadToken(profile);
|
|
117
|
+
|
|
118
|
+
printValue(io, outputMode, {
|
|
119
|
+
profile: profileName,
|
|
120
|
+
baseUrl: profile.baseUrl,
|
|
121
|
+
hasMasterToken: Boolean(resolvedMasterToken.token),
|
|
122
|
+
masterTokenSource: resolvedMasterToken.source,
|
|
123
|
+
hasReadOnlyToken: Boolean(resolvedReadOnlyToken.token),
|
|
124
|
+
readOnlyTokenSource: resolvedReadOnlyToken.source,
|
|
125
|
+
ownerReadTokenAvailable: Boolean(resolvedOwnerReadToken.token),
|
|
126
|
+
ownerReadTokenSource: resolvedOwnerReadToken.source,
|
|
127
|
+
storedChannelTokens: Object.keys(profile.channelTokens).length
|
|
128
|
+
});
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
default:
|
|
133
|
+
throw new CliError("Unknown auth subcommand", 2);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function defaultProfileConfig(): ProfileConfig {
|
|
138
|
+
return {
|
|
139
|
+
baseUrl: resolveBaseUrl(),
|
|
140
|
+
output: "table",
|
|
141
|
+
channelTokens: {}
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function formatWhoamiOutput(
|
|
146
|
+
profileName: string,
|
|
147
|
+
baseUrl: string,
|
|
148
|
+
ownerTokenSource: string,
|
|
149
|
+
whoami: WhoamiResponse
|
|
150
|
+
) {
|
|
151
|
+
const base = {
|
|
152
|
+
authenticated: whoami.authenticated,
|
|
153
|
+
profile: profileName,
|
|
154
|
+
baseUrl,
|
|
155
|
+
ownerTokenSource,
|
|
156
|
+
actorType: whoami.actor.type,
|
|
157
|
+
actorId: whoami.actor.id
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
if (whoami.actor.type === "user") {
|
|
161
|
+
return {
|
|
162
|
+
...base,
|
|
163
|
+
email: whoami.actor.email,
|
|
164
|
+
actorScope: whoami.actor.scope ?? "master"
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
...base,
|
|
170
|
+
name: whoami.actor.name,
|
|
171
|
+
visibility: whoami.actor.visibility
|
|
172
|
+
};
|
|
173
|
+
}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
import {
|
|
2
|
+
booleanFlag,
|
|
3
|
+
requiredPositional,
|
|
4
|
+
requiredStringFlag,
|
|
5
|
+
stringFlag,
|
|
6
|
+
type ParsedArgs,
|
|
7
|
+
} from "../lib/args";
|
|
8
|
+
import { storeChannelToken } from "../lib/config";
|
|
9
|
+
import { createCommandContext } from "../lib/context";
|
|
10
|
+
import { CliError } from "../lib/errors";
|
|
11
|
+
import { printJson, printValue, type Io } from "../lib/output";
|
|
12
|
+
|
|
13
|
+
const CHANNEL_NAME_PATTERN = /^[a-z0-9][a-z0-9_-]*$/;
|
|
14
|
+
const CHANNEL_NAME_ERROR =
|
|
15
|
+
"Channel names must start with a lowercase letter or digit and then use only lowercase letters, digits, hyphens, or underscores.";
|
|
16
|
+
|
|
17
|
+
export async function runChannelCommand(
|
|
18
|
+
args: ParsedArgs,
|
|
19
|
+
io: Io,
|
|
20
|
+
): Promise<void> {
|
|
21
|
+
const subcommand = args.positionals[0];
|
|
22
|
+
const context = await createCommandContext(args, io);
|
|
23
|
+
|
|
24
|
+
switch (subcommand) {
|
|
25
|
+
case "list": {
|
|
26
|
+
const response = await context.client.listChannels();
|
|
27
|
+
const rows = response.items.map((item) => ({
|
|
28
|
+
id: item.id,
|
|
29
|
+
name: item.attributes.name,
|
|
30
|
+
visibility: item.attributes.visibility,
|
|
31
|
+
description: item.attributes.description ?? "",
|
|
32
|
+
}));
|
|
33
|
+
|
|
34
|
+
if (context.outputMode === "json") {
|
|
35
|
+
printJson(io, {
|
|
36
|
+
items: response.items,
|
|
37
|
+
nextCursor: response.nextCursor,
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
printValue(io, context.outputMode, rows);
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
case "get": {
|
|
47
|
+
const channel = await context.client.resolveOwnedChannel(
|
|
48
|
+
requiredPositional(args.positionals, 1, "Missing channel"),
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
printValue(
|
|
52
|
+
io,
|
|
53
|
+
context.outputMode,
|
|
54
|
+
context.outputMode === "json"
|
|
55
|
+
? channel
|
|
56
|
+
: {
|
|
57
|
+
id: channel.id,
|
|
58
|
+
name: channel.attributes.name,
|
|
59
|
+
visibility: channel.attributes.visibility,
|
|
60
|
+
description: channel.attributes.description ?? "",
|
|
61
|
+
postingPausedUntil: channel.attributes.posting_paused_until ?? "",
|
|
62
|
+
},
|
|
63
|
+
);
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
case "create": {
|
|
68
|
+
const name = requiredStringFlag(args.flags, "name");
|
|
69
|
+
assertValidChannelName(name);
|
|
70
|
+
|
|
71
|
+
const channel = await context.client.createChannel({
|
|
72
|
+
name,
|
|
73
|
+
description: stringFlag(args.flags, "description"),
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
printValue(
|
|
77
|
+
io,
|
|
78
|
+
context.outputMode,
|
|
79
|
+
context.outputMode === "json"
|
|
80
|
+
? channel
|
|
81
|
+
: {
|
|
82
|
+
id: channel.id,
|
|
83
|
+
name: channel.attributes.name,
|
|
84
|
+
visibility: channel.attributes.visibility,
|
|
85
|
+
description: channel.attributes.description ?? "",
|
|
86
|
+
},
|
|
87
|
+
);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
case "update": {
|
|
92
|
+
const channelRef = requiredPositional(
|
|
93
|
+
args.positionals,
|
|
94
|
+
1,
|
|
95
|
+
"Missing channel",
|
|
96
|
+
);
|
|
97
|
+
const name = stringFlag(args.flags, "name");
|
|
98
|
+
const description = stringFlag(args.flags, "description");
|
|
99
|
+
|
|
100
|
+
if (name === undefined && description === undefined) {
|
|
101
|
+
throw new CliError(
|
|
102
|
+
"Provide at least one of `--name` or `--description`.",
|
|
103
|
+
2,
|
|
104
|
+
);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (name !== undefined) {
|
|
108
|
+
assertValidChannelName(name);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const channelId = await context.client.resolveChannelId(channelRef);
|
|
112
|
+
const channel = await context.client.updateChannel({
|
|
113
|
+
channelId,
|
|
114
|
+
name,
|
|
115
|
+
description,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
printValue(
|
|
119
|
+
io,
|
|
120
|
+
context.outputMode,
|
|
121
|
+
context.outputMode === "json"
|
|
122
|
+
? channel
|
|
123
|
+
: {
|
|
124
|
+
id: channel.id,
|
|
125
|
+
name: channel.attributes.name,
|
|
126
|
+
visibility: channel.attributes.visibility,
|
|
127
|
+
description: channel.attributes.description ?? "",
|
|
128
|
+
},
|
|
129
|
+
);
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
case "delete": {
|
|
134
|
+
const channelId = await context.client.resolveChannelId(
|
|
135
|
+
requiredPositional(args.positionals, 1, "Missing channel"),
|
|
136
|
+
);
|
|
137
|
+
await context.client.deleteChannel(channelId);
|
|
138
|
+
|
|
139
|
+
printValue(
|
|
140
|
+
io,
|
|
141
|
+
context.outputMode,
|
|
142
|
+
context.outputMode === "json"
|
|
143
|
+
? { ok: true, id: channelId }
|
|
144
|
+
: `Deleted channel ${channelId}.`,
|
|
145
|
+
);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
case "rotate-token": {
|
|
150
|
+
const channelId = await context.client.resolveChannelId(
|
|
151
|
+
requiredPositional(args.positionals, 1, "Missing channel"),
|
|
152
|
+
);
|
|
153
|
+
const response = await context.client.rotateChannelToken(channelId);
|
|
154
|
+
|
|
155
|
+
if (booleanFlag(args.flags, "save")) {
|
|
156
|
+
await storeChannelToken(
|
|
157
|
+
context.profileName,
|
|
158
|
+
channelId,
|
|
159
|
+
response.token,
|
|
160
|
+
context.configPath,
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (booleanFlag(args.flags, "tokenOnly")) {
|
|
165
|
+
io.stdout(response.token);
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
printValue(io, context.outputMode, response);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
default:
|
|
174
|
+
throw new CliError("Unknown channel subcommand", 2);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function assertValidChannelName(name: string): void {
|
|
179
|
+
if (!CHANNEL_NAME_PATTERN.test(name)) {
|
|
180
|
+
throw new CliError(CHANNEL_NAME_ERROR, 2);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ensureConfig,
|
|
3
|
+
ensureProfile,
|
|
4
|
+
loadConfig,
|
|
5
|
+
resolveProfile,
|
|
6
|
+
resolveProfileName,
|
|
7
|
+
setActiveProfile,
|
|
8
|
+
setProfileBaseUrl,
|
|
9
|
+
setProfileOutput
|
|
10
|
+
} from "../lib/config";
|
|
11
|
+
import { booleanFlag, requiredPositional, stringFlag, type ParsedArgs } from "../lib/args";
|
|
12
|
+
import { CliError } from "../lib/errors";
|
|
13
|
+
import { printValue, type Io } from "../lib/output";
|
|
14
|
+
import { getConfigPath } from "../lib/paths";
|
|
15
|
+
|
|
16
|
+
export async function runConfigCommand(args: ParsedArgs, io: Io): Promise<void> {
|
|
17
|
+
const subcommand = requiredPositional(args.positionals, 0, "Missing config subcommand");
|
|
18
|
+
const configPath = getConfigPath();
|
|
19
|
+
|
|
20
|
+
switch (subcommand) {
|
|
21
|
+
case "init": {
|
|
22
|
+
const baseUrl = stringFlag(args.flags, "baseUrl");
|
|
23
|
+
const requestedProfile = stringFlag(args.flags, "profile");
|
|
24
|
+
const config = requestedProfile
|
|
25
|
+
? await ensureProfile(resolveProfileName(await loadConfig(configPath), requestedProfile), configPath, baseUrl)
|
|
26
|
+
: await ensureConfig(configPath, baseUrl);
|
|
27
|
+
const { profileName, profile } = resolveProfile(config, requestedProfile);
|
|
28
|
+
|
|
29
|
+
printValue(io, booleanFlag(args.flags, "json") ? "json" : "table", {
|
|
30
|
+
configPath,
|
|
31
|
+
profile: profileName,
|
|
32
|
+
activeProfile: config.activeProfile,
|
|
33
|
+
baseUrl: profile.baseUrl,
|
|
34
|
+
output: profile.output
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
case "set": {
|
|
40
|
+
const key = requiredPositional(args.positionals, 1, "Missing config key");
|
|
41
|
+
const value = requiredPositional(args.positionals, 2, "Missing config value");
|
|
42
|
+
const config = await loadConfig(configPath);
|
|
43
|
+
const { profileName } = resolveProfile(config, stringFlag(args.flags, "profile"));
|
|
44
|
+
|
|
45
|
+
if (key === "base-url") {
|
|
46
|
+
await setProfileBaseUrl(profileName, value, configPath);
|
|
47
|
+
io.stdout(`Set base URL for profile ${profileName} to ${value}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (key === "output") {
|
|
52
|
+
if (value !== "json" && value !== "table") {
|
|
53
|
+
throw new CliError("Output must be `json` or `table`", 2);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await setProfileOutput(profileName, value, configPath);
|
|
57
|
+
io.stdout(`Set output for profile ${profileName} to ${value}`);
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new CliError(`Unknown config key "${key}"`, 2);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
case "profile": {
|
|
65
|
+
const profileCommand = requiredPositional(args.positionals, 1, "Missing profile subcommand");
|
|
66
|
+
|
|
67
|
+
if (profileCommand === "list") {
|
|
68
|
+
const config = await loadConfig(configPath);
|
|
69
|
+
const rows = Object.entries(config.profiles).map(([name, profile]) => ({
|
|
70
|
+
name,
|
|
71
|
+
active: name === config.activeProfile ? "yes" : "",
|
|
72
|
+
baseUrl: profile.baseUrl,
|
|
73
|
+
output: profile.output
|
|
74
|
+
}));
|
|
75
|
+
|
|
76
|
+
printValue(io, booleanFlag(args.flags, "json") ? "json" : "table", rows);
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (profileCommand === "use") {
|
|
81
|
+
const name = requiredPositional(args.positionals, 2, "Missing profile name");
|
|
82
|
+
const config = await setActiveProfile(name, configPath);
|
|
83
|
+
io.stdout(`Active profile is now ${config.activeProfile}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
throw new CliError(`Unknown profile subcommand "${profileCommand}"`, 2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
default:
|
|
91
|
+
throw new CliError(`Unknown config subcommand "${subcommand}"`, 2);
|
|
92
|
+
}
|
|
93
|
+
}
|