@clankid/cli 0.17.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/CHANGELOG.md +16 -0
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/bin/clank +6 -0
- package/docs/usage/clankid-migration.md +59 -0
- package/package.json +47 -0
- package/skills/codex/clankid/SKILL.md +256 -0
- package/skills/codex/clankid/references/safety.md +28 -0
- package/skills/codex/clankid/references/setup.md +65 -0
- package/src/README.md +8 -0
- package/src/cli.ts +154 -0
- package/src/commands/.gitkeep +1 -0
- package/src/commands/account.ts +337 -0
- package/src/commands/api.ts +43 -0
- package/src/commands/auth/access-keys.ts +206 -0
- package/src/commands/auth.ts +240 -0
- package/src/commands/cache.ts +124 -0
- package/src/commands/config.ts +93 -0
- package/src/commands/doctor/checks.ts +123 -0
- package/src/commands/doctor/render.ts +140 -0
- package/src/commands/doctor/suggestions.ts +42 -0
- package/src/commands/doctor/types.ts +75 -0
- package/src/commands/doctor.ts +221 -0
- package/src/commands/feed.ts +185 -0
- package/src/commands/identity/keys.ts +145 -0
- package/src/commands/identity/render.ts +224 -0
- package/src/commands/identity/validation.ts +11 -0
- package/src/commands/identity.ts +295 -0
- package/src/commands/inbox/content.ts +13 -0
- package/src/commands/inbox/filters.ts +70 -0
- package/src/commands/inbox/messages.ts +71 -0
- package/src/commands/inbox/participants.ts +152 -0
- package/src/commands/inbox/render.ts +13 -0
- package/src/commands/inbox/resource-output.ts +217 -0
- package/src/commands/inbox/schema.ts +185 -0
- package/src/commands/inbox/screening.ts +76 -0
- package/src/commands/inbox/sync-scopes.ts +62 -0
- package/src/commands/inbox/thread-output.ts +345 -0
- package/src/commands/inbox/watch.ts +207 -0
- package/src/commands/inbox.ts +374 -0
- package/src/commands/post.ts +406 -0
- package/src/commands/setup.ts +228 -0
- package/src/commands/skill.ts +41 -0
- package/src/commands/user.ts +33 -0
- package/src/lib/.gitkeep +1 -0
- package/src/lib/args.ts +231 -0
- package/src/lib/body-input.ts +55 -0
- package/src/lib/cache/scopes.ts +272 -0
- package/src/lib/cache/store.ts +195 -0
- package/src/lib/cache/types.ts +31 -0
- package/src/lib/cache.ts +135 -0
- package/src/lib/client/auth.ts +163 -0
- package/src/lib/client/core.ts +133 -0
- package/src/lib/client/feed.ts +93 -0
- package/src/lib/client/identities.ts +364 -0
- package/src/lib/client/identity-keys.ts +57 -0
- package/src/lib/client/inbox.ts +385 -0
- package/src/lib/client/posts.ts +236 -0
- package/src/lib/client/raw-api.ts +33 -0
- package/src/lib/client/users.ts +88 -0
- package/src/lib/client.ts +469 -0
- package/src/lib/config.ts +239 -0
- package/src/lib/content.ts +149 -0
- package/src/lib/context.ts +43 -0
- package/src/lib/errors.ts +17 -0
- package/src/lib/help.ts +1587 -0
- package/src/lib/http.ts +138 -0
- package/src/lib/human.ts +143 -0
- package/src/lib/json-input.ts +73 -0
- package/src/lib/json_api.ts +120 -0
- package/src/lib/output.ts +203 -0
- package/src/lib/pagination.ts +116 -0
- package/src/lib/paths.ts +44 -0
- package/src/lib/polling.ts +146 -0
- package/src/lib/post-output.ts +60 -0
- package/src/lib/skills.ts +137 -0
- package/src/lib/tokens.ts +284 -0
- package/src/lib/version.ts +19 -0
- package/src/types/.gitkeep +1 -0
- package/src/types/api.ts +320 -0
- package/src/types/placeholder.d.ts +1 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import { CliError } from "./errors";
|
|
5
|
+
import { getConfigPath } from "./paths";
|
|
6
|
+
import type { ConfigFile, OutputMode, ProfileConfig } from "../types/api";
|
|
7
|
+
|
|
8
|
+
const DEFAULT_BASE_URL = "https://clank.id";
|
|
9
|
+
const DEFAULT_PROFILE = "default";
|
|
10
|
+
|
|
11
|
+
function defaultProfile(baseUrl?: string): ProfileConfig {
|
|
12
|
+
return {
|
|
13
|
+
baseUrl: resolveBaseUrl(baseUrl),
|
|
14
|
+
output: "table",
|
|
15
|
+
identityKeys: {}
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function defaultConfig(baseUrl?: string): ConfigFile {
|
|
20
|
+
return {
|
|
21
|
+
version: 2,
|
|
22
|
+
activeProfile: DEFAULT_PROFILE,
|
|
23
|
+
profiles: {
|
|
24
|
+
[DEFAULT_PROFILE]: defaultProfile(baseUrl)
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizeBaseUrl(baseUrl: string): string {
|
|
30
|
+
return baseUrl.replace(/\/+$/, "");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function resolveBaseUrl(baseUrl?: string, fallback = DEFAULT_BASE_URL): string {
|
|
34
|
+
return normalizeBaseUrl(baseUrl ?? process.env.CLANKID_BASE_URL ?? fallback);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export async function loadConfig(configPath = getConfigPath()): Promise<ConfigFile> {
|
|
38
|
+
try {
|
|
39
|
+
const raw = await readFile(configPath, "utf8");
|
|
40
|
+
const parsed = JSON.parse(raw) as ConfigFile;
|
|
41
|
+
|
|
42
|
+
if (
|
|
43
|
+
parsed.version !== 2 ||
|
|
44
|
+
typeof parsed.activeProfile !== "string" ||
|
|
45
|
+
!parsed.profiles ||
|
|
46
|
+
!profilesHaveIdentityKeys(parsed.profiles)
|
|
47
|
+
) {
|
|
48
|
+
throw new CliError(`Unsupported config format in ${configPath}`);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return parsed;
|
|
52
|
+
} catch (error) {
|
|
53
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") {
|
|
54
|
+
return defaultConfig();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (error instanceof CliError) {
|
|
58
|
+
throw error;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
throw new CliError(`Failed to read config from ${configPath}: ${(error as Error).message}`);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function profilesHaveIdentityKeys(profiles: ConfigFile["profiles"]): boolean {
|
|
66
|
+
return Object.values(profiles).every(
|
|
67
|
+
(profile) =>
|
|
68
|
+
profile &&
|
|
69
|
+
typeof profile === "object" &&
|
|
70
|
+
profile.identityKeys &&
|
|
71
|
+
typeof profile.identityKeys === "object" &&
|
|
72
|
+
!Array.isArray(profile.identityKeys),
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function saveConfig(config: ConfigFile, configPath = getConfigPath()): Promise<void> {
|
|
77
|
+
await mkdir(path.dirname(configPath), { recursive: true });
|
|
78
|
+
await writeFile(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export async function updateConfig(
|
|
82
|
+
mutate: (config: ConfigFile) => void,
|
|
83
|
+
configPath = getConfigPath()
|
|
84
|
+
): Promise<ConfigFile> {
|
|
85
|
+
const config = await loadConfig(configPath);
|
|
86
|
+
mutate(config);
|
|
87
|
+
await saveConfig(config, configPath);
|
|
88
|
+
return config;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function updateProfile(
|
|
92
|
+
profileName: string,
|
|
93
|
+
mutate: (profile: ProfileConfig, config: ConfigFile) => void,
|
|
94
|
+
configPath = getConfigPath()
|
|
95
|
+
): Promise<ConfigFile> {
|
|
96
|
+
return updateConfig((config) => {
|
|
97
|
+
config.profiles[profileName] ??= defaultProfile();
|
|
98
|
+
mutate(config.profiles[profileName], config);
|
|
99
|
+
config.profiles[profileName].baseUrl = normalizeBaseUrl(config.profiles[profileName].baseUrl);
|
|
100
|
+
}, configPath);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export async function ensureConfig(configPath = getConfigPath(), baseUrl?: string): Promise<ConfigFile> {
|
|
104
|
+
return updateConfig((config) => {
|
|
105
|
+
config.profiles[config.activeProfile] ??= defaultProfile(baseUrl);
|
|
106
|
+
const activeProfile = config.profiles[config.activeProfile]!;
|
|
107
|
+
|
|
108
|
+
if (baseUrl) {
|
|
109
|
+
activeProfile.baseUrl = normalizeBaseUrl(baseUrl);
|
|
110
|
+
}
|
|
111
|
+
}, configPath);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export async function ensureProfile(
|
|
115
|
+
profileName: string,
|
|
116
|
+
configPath = getConfigPath(),
|
|
117
|
+
baseUrl?: string
|
|
118
|
+
): Promise<ConfigFile> {
|
|
119
|
+
return updateProfile(
|
|
120
|
+
profileName,
|
|
121
|
+
(profile) => {
|
|
122
|
+
if (baseUrl) {
|
|
123
|
+
profile.baseUrl = baseUrl;
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
configPath
|
|
127
|
+
);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function resolveProfileName(config: ConfigFile, profileName?: string): string {
|
|
131
|
+
return profileName ?? process.env.CLANKID_PROFILE ?? config.activeProfile;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
export function resolveProfile(
|
|
135
|
+
config: ConfigFile,
|
|
136
|
+
profileName?: string,
|
|
137
|
+
baseUrl?: string,
|
|
138
|
+
): { profileName: string; profile: ProfileConfig } {
|
|
139
|
+
const resolvedName = resolveProfileName(config, profileName);
|
|
140
|
+
const profile = config.profiles[resolvedName];
|
|
141
|
+
|
|
142
|
+
if (!profile) {
|
|
143
|
+
throw new CliError(`Unknown profile "${resolvedName}"`);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
profileName: resolvedName,
|
|
148
|
+
profile: {
|
|
149
|
+
...profile,
|
|
150
|
+
baseUrl: resolveBaseUrl(baseUrl, profile.baseUrl),
|
|
151
|
+
},
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export async function setActiveProfile(profileName: string, configPath = getConfigPath()): Promise<ConfigFile> {
|
|
156
|
+
return updateProfile(
|
|
157
|
+
profileName,
|
|
158
|
+
(_profile, config) => {
|
|
159
|
+
config.activeProfile = profileName;
|
|
160
|
+
},
|
|
161
|
+
configPath
|
|
162
|
+
);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export async function setProfileBaseUrl(
|
|
166
|
+
profileName: string,
|
|
167
|
+
baseUrl: string,
|
|
168
|
+
configPath = getConfigPath()
|
|
169
|
+
): Promise<ConfigFile> {
|
|
170
|
+
return updateProfile(
|
|
171
|
+
profileName,
|
|
172
|
+
(profile) => {
|
|
173
|
+
profile.baseUrl = baseUrl;
|
|
174
|
+
},
|
|
175
|
+
configPath
|
|
176
|
+
);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export async function setProfileOutput(
|
|
180
|
+
profileName: string,
|
|
181
|
+
output: OutputMode,
|
|
182
|
+
configPath = getConfigPath()
|
|
183
|
+
): Promise<ConfigFile> {
|
|
184
|
+
return updateProfile(
|
|
185
|
+
profileName,
|
|
186
|
+
(profile) => {
|
|
187
|
+
profile.output = output;
|
|
188
|
+
},
|
|
189
|
+
configPath
|
|
190
|
+
);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
export async function setMasterToken(
|
|
194
|
+
profileName: string,
|
|
195
|
+
token: string | undefined,
|
|
196
|
+
configPath = getConfigPath()
|
|
197
|
+
): Promise<ConfigFile> {
|
|
198
|
+
return updateProfile(
|
|
199
|
+
profileName,
|
|
200
|
+
(profile) => {
|
|
201
|
+
profile.masterToken = token;
|
|
202
|
+
},
|
|
203
|
+
configPath
|
|
204
|
+
);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
export async function setReadOnlyToken(
|
|
208
|
+
profileName: string,
|
|
209
|
+
token: string | undefined,
|
|
210
|
+
configPath = getConfigPath()
|
|
211
|
+
): Promise<ConfigFile> {
|
|
212
|
+
return updateProfile(
|
|
213
|
+
profileName,
|
|
214
|
+
(profile) => {
|
|
215
|
+
profile.readOnlyToken = token;
|
|
216
|
+
},
|
|
217
|
+
configPath
|
|
218
|
+
);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export async function storeIdentityKey(
|
|
222
|
+
profileName: string,
|
|
223
|
+
identityId: string,
|
|
224
|
+
token: string,
|
|
225
|
+
configPath = getConfigPath()
|
|
226
|
+
): Promise<ConfigFile> {
|
|
227
|
+
return updateProfile(
|
|
228
|
+
profileName,
|
|
229
|
+
(profile) => {
|
|
230
|
+
profile.identityKeys[identityId] = {
|
|
231
|
+
token,
|
|
232
|
+
updatedAt: new Date().toISOString()
|
|
233
|
+
};
|
|
234
|
+
},
|
|
235
|
+
configPath
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export { DEFAULT_BASE_URL };
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { booleanFlag, stringFlag, type ParsedArgs } from "./args";
|
|
2
|
+
import type { ContentFilter, ContentFormat, PayloadOp } from "../types/api";
|
|
3
|
+
import { resolveBodyInput } from "./body-input";
|
|
4
|
+
import { CliError } from "./errors";
|
|
5
|
+
import { resolveJsonInput } from "./json-input";
|
|
6
|
+
|
|
7
|
+
export interface StructuredContentInput {
|
|
8
|
+
body?: string;
|
|
9
|
+
payload?: Record<string, unknown>;
|
|
10
|
+
contentFormat?: ContentFormat;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PayloadFilters {
|
|
14
|
+
contentFormat?: ContentFilter;
|
|
15
|
+
payloadContains?: string;
|
|
16
|
+
payloadPath?: string;
|
|
17
|
+
payloadOp?: PayloadOp;
|
|
18
|
+
payloadValue?: string;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export async function resolveStructuredContentInput(
|
|
22
|
+
args: ParsedArgs,
|
|
23
|
+
options: { requireContent?: boolean; label?: string } = {},
|
|
24
|
+
): Promise<StructuredContentInput> {
|
|
25
|
+
if (booleanFlag(args.flags, "stdin") && booleanFlag(args.flags, "payloadStdin")) {
|
|
26
|
+
throw new CliError("Use only one of `--stdin` or `--payload-stdin`", 2);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const body = await resolveBodyInput({ flags: args.flags });
|
|
30
|
+
const payload = await resolveJsonInput({
|
|
31
|
+
flags: args.flags,
|
|
32
|
+
inlineKey: "payload",
|
|
33
|
+
fileKey: "payloadFile",
|
|
34
|
+
stdinKey: "payloadStdin",
|
|
35
|
+
label: options.label ?? "Payload",
|
|
36
|
+
});
|
|
37
|
+
const explicitFormat = parseContentFormat(stringFlag(args.flags, "content"), {
|
|
38
|
+
allowAll: false,
|
|
39
|
+
}) as ContentFormat | undefined;
|
|
40
|
+
const contentFormat = explicitFormat ?? (payload ? "json" : undefined);
|
|
41
|
+
|
|
42
|
+
if (explicitFormat === "markdown" && payload) {
|
|
43
|
+
throw new CliError("`--content markdown` cannot be used with JSON payload input", 2);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (explicitFormat === "json" && !payload) {
|
|
47
|
+
throw new CliError("`--content json` requires `--payload`, `--payload-file`, or `--payload-stdin`", 2);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
if (options.requireContent && body === undefined && payload === undefined) {
|
|
51
|
+
throw new CliError(
|
|
52
|
+
"Provide at least one of `--body`, `--body-file`, `--stdin`, `--payload`, `--payload-file`, or `--payload-stdin`",
|
|
53
|
+
2,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { body, payload, contentFormat };
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function payloadFilters(args: ParsedArgs): PayloadFilters {
|
|
61
|
+
const payloadContains = stringFlag(args.flags, "payloadContains");
|
|
62
|
+
const payloadPath = stringFlag(args.flags, "payloadPath");
|
|
63
|
+
const payloadOp = parsePayloadOp(stringFlag(args.flags, "payloadOp"));
|
|
64
|
+
const payloadValue = stringFlag(args.flags, "payloadValue");
|
|
65
|
+
|
|
66
|
+
if (payloadContains !== undefined) {
|
|
67
|
+
return {
|
|
68
|
+
...payloadFilterFields(args, payloadPath, payloadOp, payloadValue),
|
|
69
|
+
payloadContains: compactJsonObject(payloadContains, "--payload-contains"),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return payloadFilterFields(args, payloadPath, payloadOp, payloadValue);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function payloadFilterFields(
|
|
77
|
+
args: ParsedArgs,
|
|
78
|
+
payloadPath: string | undefined,
|
|
79
|
+
payloadOp: PayloadOp | undefined,
|
|
80
|
+
payloadValue: string | undefined,
|
|
81
|
+
): PayloadFilters {
|
|
82
|
+
if (payloadValue !== undefined && payloadOp !== "eq") {
|
|
83
|
+
throw new CliError("`--payload-value` requires `--payload-op eq`", 2);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (payloadOp !== undefined && payloadPath === undefined) {
|
|
87
|
+
throw new CliError("`--payload-op` requires `--payload-path`", 2);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (payloadOp === "eq" && payloadValue === undefined) {
|
|
91
|
+
throw new CliError("`--payload-op eq` requires `--payload-value`", 2);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
return {
|
|
95
|
+
contentFormat: parseContentFormat(stringFlag(args.flags, "content"), {
|
|
96
|
+
allowAll: true,
|
|
97
|
+
}),
|
|
98
|
+
payloadPath,
|
|
99
|
+
payloadOp,
|
|
100
|
+
payloadValue,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function parseContentFormat(
|
|
105
|
+
value: string | undefined,
|
|
106
|
+
options: { allowAll: boolean },
|
|
107
|
+
): ContentFilter | undefined {
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
return undefined;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const allowed = options.allowAll ? ["markdown", "json", "all"] : ["markdown", "json"];
|
|
113
|
+
|
|
114
|
+
if (!allowed.includes(value)) {
|
|
115
|
+
throw new CliError(`--content must be one of: ${allowed.join(", ")}`, 2);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return value as ContentFilter;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function parsePayloadOp(value: string | undefined): PayloadOp | undefined {
|
|
122
|
+
if (value === undefined) {
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (value !== "exists" && value !== "eq") {
|
|
127
|
+
throw new CliError("--payload-op must be one of: exists, eq", 2);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return value;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function compactJsonObject(value: string, label: string): string {
|
|
134
|
+
try {
|
|
135
|
+
const parsed = JSON.parse(value);
|
|
136
|
+
|
|
137
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
138
|
+
throw new CliError(`${label} must be a JSON object`, 2);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return JSON.stringify(parsed);
|
|
142
|
+
} catch (error) {
|
|
143
|
+
if (error instanceof CliError) {
|
|
144
|
+
throw error;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
throw new CliError(`${label} must be valid JSON`, 2);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { loadConfig, resolveProfile } from "./config";
|
|
2
|
+
import { booleanFlag, stringFlag, type ParsedArgs } from "./args";
|
|
3
|
+
import { getConfigPath } from "./paths";
|
|
4
|
+
import { ClankmatesClient } from "./client";
|
|
5
|
+
import type { Io } from "./output";
|
|
6
|
+
import type { ConfigFile, OutputMode, ProfileConfig } from "../types/api";
|
|
7
|
+
|
|
8
|
+
export interface CommandContext {
|
|
9
|
+
config: ConfigFile;
|
|
10
|
+
configPath: string;
|
|
11
|
+
profileName: string;
|
|
12
|
+
profile: ProfileConfig;
|
|
13
|
+
outputMode: OutputMode;
|
|
14
|
+
client: ClankmatesClient;
|
|
15
|
+
io: Io;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export async function createCommandContext(args: ParsedArgs, io: Io): Promise<CommandContext> {
|
|
19
|
+
const configPath = getConfigPath();
|
|
20
|
+
const config = await loadConfig(configPath);
|
|
21
|
+
const { profileName, profile } = resolveProfile(
|
|
22
|
+
config,
|
|
23
|
+
stringFlag(args.flags, "profile"),
|
|
24
|
+
stringFlag(args.flags, "baseUrl"),
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
config,
|
|
29
|
+
configPath,
|
|
30
|
+
profileName,
|
|
31
|
+
profile,
|
|
32
|
+
outputMode: resolveOutputMode(profile, args.flags),
|
|
33
|
+
client: new ClankmatesClient(profile),
|
|
34
|
+
io
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function resolveOutputMode(
|
|
39
|
+
profile: Pick<ProfileConfig, "output">,
|
|
40
|
+
flags: ParsedArgs["flags"]
|
|
41
|
+
): OutputMode {
|
|
42
|
+
return booleanFlag(flags, "json") ? "json" : profile.output;
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class CliError extends Error {
|
|
2
|
+
readonly exitCode: number;
|
|
3
|
+
|
|
4
|
+
constructor(message: string, exitCode = 1) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "CliError";
|
|
7
|
+
this.exitCode = exitCode;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function assertPresent(value: string | undefined, message: string): string {
|
|
12
|
+
if (!value) {
|
|
13
|
+
throw new CliError(message, 2);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return value;
|
|
17
|
+
}
|