@01.software/cli 0.6.0 → 0.7.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/dist/index.js +390 -156
- package/dist/index.js.map +1 -1
- package/dist/mcp/chunk-3ZSKJM43.js +5395 -0
- package/dist/mcp/chunk-3ZSKJM43.js.map +1 -0
- package/dist/mcp/http.js +291 -0
- package/dist/mcp/http.js.map +1 -0
- package/dist/mcp/stdio.js +20 -0
- package/dist/mcp/stdio.js.map +1 -0
- package/dist/mcp/vercel.d.ts +5 -0
- package/dist/mcp/vercel.js +5669 -0
- package/package.json +6 -3
package/dist/index.js
CHANGED
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
CollectionClient,
|
|
10
10
|
ServerCommerceClient
|
|
11
11
|
} from "@01.software/sdk";
|
|
12
|
-
import pc from "picocolors";
|
|
13
12
|
|
|
14
13
|
// src/lib/credentials.ts
|
|
15
14
|
import {
|
|
@@ -93,7 +92,7 @@ function loadTenantList() {
|
|
|
93
92
|
const data = JSON.parse(raw);
|
|
94
93
|
if (!Array.isArray(data)) return null;
|
|
95
94
|
const valid = data.filter(
|
|
96
|
-
(
|
|
95
|
+
(t2) => typeof t2?.id === "string" && typeof t2?.name === "string"
|
|
97
96
|
);
|
|
98
97
|
return valid.length > 0 ? valid : null;
|
|
99
98
|
} catch {
|
|
@@ -126,77 +125,209 @@ ${entry}
|
|
|
126
125
|
}
|
|
127
126
|
}
|
|
128
127
|
|
|
129
|
-
// src/lib/
|
|
130
|
-
|
|
131
|
-
|
|
128
|
+
// src/lib/output.ts
|
|
129
|
+
import pc from "picocolors";
|
|
130
|
+
|
|
131
|
+
// src/lib/api-error.ts
|
|
132
|
+
var PERMISSION_CODES = [
|
|
133
|
+
"tenant_mismatch",
|
|
134
|
+
"account_suspended",
|
|
135
|
+
"feature_disabled",
|
|
136
|
+
"role_denied",
|
|
137
|
+
"credential_invalid",
|
|
138
|
+
"pat_tenant_unpinned",
|
|
139
|
+
"publishable_key_mismatch",
|
|
140
|
+
"scope_denied"
|
|
141
|
+
];
|
|
142
|
+
var DEGRADED_CODES = [
|
|
143
|
+
"redis_unavailable",
|
|
144
|
+
"provider_unavailable",
|
|
145
|
+
"rate_limited"
|
|
146
|
+
];
|
|
147
|
+
var NETWORK_CODES = [
|
|
148
|
+
"upstream_timeout",
|
|
149
|
+
"upstream_5xx",
|
|
150
|
+
"dns_failure"
|
|
151
|
+
];
|
|
152
|
+
function isPermissionCode(code) {
|
|
153
|
+
return PERMISSION_CODES.includes(code);
|
|
132
154
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (tenantHeaderInterceptorInstalled) return;
|
|
136
|
-
tenantHeaderInterceptorInstalled = true;
|
|
137
|
-
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
138
|
-
globalThis.fetch = async (input, init) => {
|
|
139
|
-
const headers = new Headers(init?.headers);
|
|
140
|
-
if (!headers.has("X-Tenant-Id")) headers.set("X-Tenant-Id", tenantId);
|
|
141
|
-
return originalFetch(input, { ...init, headers });
|
|
142
|
-
};
|
|
155
|
+
function isDegradedCode(code) {
|
|
156
|
+
return DEGRADED_CODES.includes(code);
|
|
143
157
|
}
|
|
144
|
-
function
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (
|
|
149
|
-
const
|
|
150
|
-
if (
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
158
|
+
function isNetworkCode(code) {
|
|
159
|
+
return NETWORK_CODES.includes(code);
|
|
160
|
+
}
|
|
161
|
+
function classifyError(err) {
|
|
162
|
+
if (err && typeof err === "object") {
|
|
163
|
+
const obj = err;
|
|
164
|
+
if (typeof obj.code === "string") {
|
|
165
|
+
const code = obj.code;
|
|
166
|
+
if (isPermissionCode(code)) {
|
|
167
|
+
return { type: "permission", code };
|
|
168
|
+
}
|
|
169
|
+
if (isDegradedCode(code)) {
|
|
170
|
+
const out = { type: "degraded", code };
|
|
171
|
+
if (typeof obj.retryAfter === "number") {
|
|
172
|
+
out.retryAfter = obj.retryAfter;
|
|
173
|
+
}
|
|
174
|
+
return out;
|
|
175
|
+
}
|
|
176
|
+
if (isNetworkCode(code)) {
|
|
177
|
+
return { type: "network", code };
|
|
178
|
+
}
|
|
154
179
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
180
|
+
if (obj.type === "validation" && typeof obj.code === "string") {
|
|
181
|
+
const out = { type: "validation", code: obj.code };
|
|
182
|
+
if (typeof obj.field === "string") out.field = obj.field;
|
|
183
|
+
if (obj.detail && typeof obj.detail === "object") {
|
|
184
|
+
out.detail = obj.detail;
|
|
185
|
+
}
|
|
186
|
+
return out;
|
|
187
|
+
}
|
|
188
|
+
const name = typeof obj.name === "string" ? obj.name : void 0;
|
|
189
|
+
const status = typeof obj.status === "number" ? obj.status : void 0;
|
|
190
|
+
if (name === "ConfigError" || status === 401) {
|
|
191
|
+
return { type: "permission", code: "credential_invalid" };
|
|
192
|
+
}
|
|
193
|
+
if (name === "NetworkError" || name === "TimeoutError" || status === 408 || status === 503) {
|
|
194
|
+
return { type: "network", code: "upstream_timeout" };
|
|
195
|
+
}
|
|
196
|
+
if (name === "GoneError" || status === 404) {
|
|
197
|
+
return {
|
|
198
|
+
type: "validation",
|
|
199
|
+
code: "not_found",
|
|
200
|
+
detail: typeof obj.message === "string" ? { message: obj.message } : void 0
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
if (name === "UsageLimitError" || status === 429) {
|
|
204
|
+
const out = { type: "degraded", code: "rate_limited" };
|
|
205
|
+
if (typeof obj.retryAfter === "number") out.retryAfter = obj.retryAfter;
|
|
206
|
+
return out;
|
|
207
|
+
}
|
|
208
|
+
if (name === "ValidationError" || status === 400 || status === 422) {
|
|
209
|
+
return {
|
|
210
|
+
type: "validation",
|
|
211
|
+
code: "invalid_argument",
|
|
212
|
+
detail: typeof obj.message === "string" ? { message: obj.message } : void 0
|
|
213
|
+
};
|
|
162
214
|
}
|
|
163
215
|
}
|
|
164
|
-
if (!publishableKey || !secretKey) {
|
|
165
|
-
console.error(pc.red("Authentication required."));
|
|
166
|
-
console.error(
|
|
167
|
-
pc.dim(
|
|
168
|
-
'Run "01 login" to authenticate via browser,\nor pass --api-key <token>,\nor set SOFTWARE_PUBLISHABLE_KEY and SOFTWARE_SECRET_KEY environment variables.'
|
|
169
|
-
)
|
|
170
|
-
);
|
|
171
|
-
process.exit(2);
|
|
172
|
-
}
|
|
173
|
-
if (!isValidBearerToken(secretKey)) {
|
|
174
|
-
console.error(
|
|
175
|
-
pc.red(
|
|
176
|
-
"Invalid API key format. Expected sk01_ or pat01_ token.\nLegacy hex credentials are no longer accepted \u2014 run `01 login` to re-authenticate."
|
|
177
|
-
)
|
|
178
|
-
);
|
|
179
|
-
process.exit(2);
|
|
180
|
-
}
|
|
181
|
-
if (tenantId) installTenantHeaderInterceptor(tenantId);
|
|
182
|
-
const serverOptions = { publishableKey, secretKey, tenantId };
|
|
183
216
|
return {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
secretKey
|
|
217
|
+
type: "validation",
|
|
218
|
+
code: "unknown",
|
|
219
|
+
detail: { message: err instanceof Error ? err.message : String(err) }
|
|
188
220
|
};
|
|
189
221
|
}
|
|
222
|
+
function adminErrorExitCode(err) {
|
|
223
|
+
if (err.code === "unknown") return 1;
|
|
224
|
+
switch (err.type) {
|
|
225
|
+
case "permission":
|
|
226
|
+
return 2;
|
|
227
|
+
case "validation":
|
|
228
|
+
return err.code === "not_found" ? 5 : 3;
|
|
229
|
+
case "network":
|
|
230
|
+
return 4;
|
|
231
|
+
case "degraded":
|
|
232
|
+
return err.code === "rate_limited" ? 6 : 4;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/lib/i18n.ts
|
|
237
|
+
var CLI_I18N_KO = {
|
|
238
|
+
// CLI-surface microcopy
|
|
239
|
+
AuthenticationRequired: "\uC778\uC99D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4",
|
|
240
|
+
RunLoginToAuthenticate: "`01 login` \uBA85\uB839\uC73C\uB85C \uBE0C\uB77C\uC6B0\uC800 \uC778\uC99D\uC744 \uC9C4\uD589\uD558\uAC70\uB098,",
|
|
241
|
+
PassApiKey: "`--api-key <token>` \uC635\uC158\uC744 \uC804\uB2EC\uD558\uAC70\uB098,",
|
|
242
|
+
SetEnvVars: "`SOFTWARE_PUBLISHABLE_KEY` / `SOFTWARE_SECRET_KEY` \uD658\uACBD \uBCC0\uC218\uB97C \uC124\uC815\uD558\uC138\uC694.",
|
|
243
|
+
InvalidApiKeyFormat: "API \uD0A4 \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. `sk01_` \uB610\uB294 `pat01_` \uD1A0\uD070\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
|
|
244
|
+
LegacyHexCredentialsRejected: "\uB808\uAC70\uC2DC hex \uC790\uACA9 \uC99D\uBA85\uC740 \uB354 \uC774\uC0C1 \uD5C8\uC6A9\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 `01 login`\uC73C\uB85C \uB2E4\uC2DC \uC778\uC99D\uD558\uC138\uC694.",
|
|
245
|
+
InvalidJsonObject: "`--{{label}}` \uC778\uC790\uB294 JSON \uAC1D\uCCB4\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
|
|
246
|
+
InvalidJsonArray: "`--{{label}}` \uC778\uC790\uB294 JSON \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
|
|
247
|
+
InvalidJsonValue: "`--{{label}}` \uC778\uC790\uC758 JSON \uD615\uC2DD\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {{value}}",
|
|
248
|
+
Empty: "(\uC5C6\uC74C)",
|
|
249
|
+
Total: "\uCD1D {{n}}\uAC1C",
|
|
250
|
+
PageOf: "{{page}} / {{total}} \uD398\uC774\uC9C0",
|
|
251
|
+
// AdminError code translations (mirrors apps/console admin.ts).
|
|
252
|
+
tenant_mismatch: "\uAD8C\uD55C\uC774 \uC5C6\uB294 \uD14C\uB10C\uD2B8",
|
|
253
|
+
account_suspended: "\uACC4\uC815\uC774 \uC815\uC9C0\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
254
|
+
feature_disabled: "\uC774 \uAE30\uB2A5\uC774 \uBE44\uD65C\uC131\uD654\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
255
|
+
role_denied: "\uAD8C\uD55C\uC774 \uBD80\uC871\uD569\uB2C8\uB2E4",
|
|
256
|
+
credential_invalid: "\uC790\uACA9 \uC99D\uBA85\uC774 \uC720\uD6A8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
257
|
+
pat_tenant_unpinned: "PAT \uD1A0\uD070\uC758 \uD14C\uB10C\uD2B8\uAC00 \uACE0\uC815\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4",
|
|
258
|
+
publishable_key_mismatch: "\uACF5\uAC1C \uD0A4\uAC00 \uC77C\uCE58\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
259
|
+
scope_denied: "\uBC94\uC704 \uAD8C\uD55C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
260
|
+
redis_unavailable: "\uCE90\uC2DC\uAC00 \uC77C\uC2DC\uC801\uC73C\uB85C \uC0AC\uC6A9 \uBD88\uAC00\uD569\uB2C8\uB2E4",
|
|
261
|
+
provider_unavailable: "\uC678\uBD80 \uACF5\uAE09\uC790\uAC00 \uC751\uB2F5\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
262
|
+
rate_limited: "\uC694\uCCAD\uC774 \uB108\uBB34 \uB9CE\uC2B5\uB2C8\uB2E4 \u2014 \uC7A0\uC2DC \uD6C4 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694",
|
|
263
|
+
upstream_timeout: "\uC751\uB2F5 \uC2DC\uAC04\uC774 \uCD08\uACFC\uB418\uC5C8\uC2B5\uB2C8\uB2E4",
|
|
264
|
+
upstream_5xx: "\uC678\uBD80 \uC11C\uBE44\uC2A4 \uC624\uB958",
|
|
265
|
+
dns_failure: "\uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0 \uC624\uB958",
|
|
266
|
+
not_found: "\uB9AC\uC18C\uC2A4\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4",
|
|
267
|
+
invalid_argument: "\uC778\uC790\uAC00 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4",
|
|
268
|
+
unknown: "\uC54C \uC218 \uC5C6\uB294 \uC624\uB958"
|
|
269
|
+
};
|
|
270
|
+
var CLI_I18N_EN = {
|
|
271
|
+
AuthenticationRequired: "Authentication required",
|
|
272
|
+
RunLoginToAuthenticate: "Run `01 login` to authenticate via browser, or",
|
|
273
|
+
PassApiKey: "pass `--api-key <token>`, or",
|
|
274
|
+
SetEnvVars: "set `SOFTWARE_PUBLISHABLE_KEY` and `SOFTWARE_SECRET_KEY` environment variables.",
|
|
275
|
+
InvalidApiKeyFormat: "Invalid API key format. Expected `sk01_` or `pat01_` token.",
|
|
276
|
+
LegacyHexCredentialsRejected: "Legacy hex credentials are no longer accepted \u2014 run `01 login` to re-authenticate.",
|
|
277
|
+
InvalidJsonObject: "--{{label}} must be a JSON object.",
|
|
278
|
+
InvalidJsonArray: "--{{label}} must be a JSON array.",
|
|
279
|
+
InvalidJsonValue: "Invalid JSON for --{{label}}: {{value}}",
|
|
280
|
+
Empty: "(empty)",
|
|
281
|
+
Total: "{{n}} total",
|
|
282
|
+
PageOf: "page {{page}}/{{total}}",
|
|
283
|
+
tenant_mismatch: "Tenant access denied",
|
|
284
|
+
account_suspended: "Your account has been suspended",
|
|
285
|
+
feature_disabled: "This feature is disabled",
|
|
286
|
+
role_denied: "Insufficient role permissions",
|
|
287
|
+
credential_invalid: "Invalid credentials",
|
|
288
|
+
pat_tenant_unpinned: "PAT token tenant is not pinned",
|
|
289
|
+
publishable_key_mismatch: "Publishable key mismatch",
|
|
290
|
+
scope_denied: "Scope permission denied",
|
|
291
|
+
redis_unavailable: "Cache temporarily unavailable",
|
|
292
|
+
provider_unavailable: "Upstream provider unavailable",
|
|
293
|
+
rate_limited: "Too many requests \u2014 please try again shortly",
|
|
294
|
+
upstream_timeout: "Upstream request timed out",
|
|
295
|
+
upstream_5xx: "Upstream service error",
|
|
296
|
+
dns_failure: "Network connection error",
|
|
297
|
+
not_found: "Resource not found",
|
|
298
|
+
invalid_argument: "Invalid argument",
|
|
299
|
+
unknown: "Unknown error"
|
|
300
|
+
};
|
|
301
|
+
var activeLocale = null;
|
|
302
|
+
function detectLocale() {
|
|
303
|
+
if (activeLocale) return activeLocale;
|
|
304
|
+
const raw = process.env.LC_ALL ?? process.env.LANG ?? process.env.LANGUAGE ?? "";
|
|
305
|
+
return raw.toLowerCase().startsWith("ko") ? "ko" : "en";
|
|
306
|
+
}
|
|
307
|
+
function setLocale(locale) {
|
|
308
|
+
activeLocale = locale ?? null;
|
|
309
|
+
}
|
|
310
|
+
function t(key, vars) {
|
|
311
|
+
const table = detectLocale() === "ko" ? CLI_I18N_KO : CLI_I18N_EN;
|
|
312
|
+
let s = table[key];
|
|
313
|
+
if (vars) {
|
|
314
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
315
|
+
const escaped = k.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
316
|
+
s = s.replace(new RegExp(`\\{\\{${escaped}\\}\\}`, "g"), String(v));
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
return s;
|
|
320
|
+
}
|
|
190
321
|
|
|
191
322
|
// src/lib/output.ts
|
|
192
|
-
|
|
323
|
+
var CLI_I18N_KEYS = Object.keys(CLI_I18N_KO);
|
|
193
324
|
function printJson(data) {
|
|
194
325
|
console.log(JSON.stringify(data, null, 2));
|
|
195
326
|
}
|
|
196
327
|
function printTable(data) {
|
|
197
328
|
if (Array.isArray(data)) {
|
|
198
329
|
if (data.length === 0) {
|
|
199
|
-
console.log(
|
|
330
|
+
console.log(pc.dim(t("Empty")));
|
|
200
331
|
return;
|
|
201
332
|
}
|
|
202
333
|
const keys = Object.keys(data[0]);
|
|
@@ -224,7 +355,7 @@ function printTable(data) {
|
|
|
224
355
|
const maxKey = Math.max(...entries.map(([k]) => k.length));
|
|
225
356
|
for (const [key, value] of entries) {
|
|
226
357
|
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
227
|
-
console.log(`${
|
|
358
|
+
console.log(`${pc.bold(key.padEnd(maxKey))} ${display}`);
|
|
228
359
|
}
|
|
229
360
|
} else {
|
|
230
361
|
console.log(String(data));
|
|
@@ -250,9 +381,12 @@ function printResult(data, format) {
|
|
|
250
381
|
const resp = data;
|
|
251
382
|
printTable(resp.docs);
|
|
252
383
|
console.log(
|
|
253
|
-
|
|
384
|
+
pc.dim(
|
|
254
385
|
`
|
|
255
|
-
${resp.totalDocs}
|
|
386
|
+
${t("Total", { n: resp.totalDocs })} | ${t("PageOf", {
|
|
387
|
+
page: resp.page,
|
|
388
|
+
total: resp.totalPages
|
|
389
|
+
})}`
|
|
256
390
|
)
|
|
257
391
|
);
|
|
258
392
|
return;
|
|
@@ -263,35 +397,107 @@ ${resp.totalDocs} total | page ${resp.page}/${resp.totalPages}`
|
|
|
263
397
|
}
|
|
264
398
|
}
|
|
265
399
|
function getExitCode(error) {
|
|
266
|
-
|
|
267
|
-
const err = error;
|
|
268
|
-
if (err.name === "ConfigError") return 2;
|
|
269
|
-
if (err.name === "ValidationError") return 3;
|
|
270
|
-
if (err.name === "NetworkError" || err.name === "TimeoutError") return 4;
|
|
271
|
-
if (err.name === "GoneError") return 5;
|
|
272
|
-
if (err.name === "UsageLimitError") return 6;
|
|
273
|
-
const s = err.status;
|
|
274
|
-
if (s === 401) return 2;
|
|
275
|
-
if (s === 400 || s === 422) return 3;
|
|
276
|
-
if (s === 408 || s === 503) return 4;
|
|
277
|
-
if (s === 404) return 5;
|
|
278
|
-
if (s === 429) return 6;
|
|
279
|
-
return 1;
|
|
400
|
+
return adminErrorExitCode(classifyError(error));
|
|
280
401
|
}
|
|
281
|
-
function
|
|
282
|
-
|
|
283
|
-
process.exit(getExitCode(error));
|
|
402
|
+
function resolveFormat(options) {
|
|
403
|
+
return options.format ?? process.env.OUTPUT_FORMAT ?? "json";
|
|
284
404
|
}
|
|
285
|
-
function printError(error) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
405
|
+
function printError(error, options = {}) {
|
|
406
|
+
const adminError = classifyError(error);
|
|
407
|
+
const knownKey = CLI_I18N_KEYS.includes(adminError.code) ? adminError.code : null;
|
|
408
|
+
const localized = knownKey ? t(knownKey) : null;
|
|
409
|
+
const rawErr = error && typeof error === "object" ? error : null;
|
|
410
|
+
const rawMessage = rawErr && typeof rawErr.message === "string" ? rawErr.message : "";
|
|
411
|
+
const stringErr = typeof error === "string" ? error : "";
|
|
412
|
+
const detailMessage = rawErr && rawErr.detail && typeof rawErr.detail === "object" && typeof rawErr.detail.message === "string" ? String(rawErr.detail.message) : "";
|
|
413
|
+
const headline = rawMessage || stringErr || detailMessage || localized || adminError.code;
|
|
414
|
+
console.error(pc.red(`Error: ${headline}`));
|
|
415
|
+
if (rawErr && typeof rawErr.code === "string" && rawErr.code !== adminError.code) {
|
|
416
|
+
console.error(pc.dim(`Code: ${rawErr.code}`));
|
|
292
417
|
} else {
|
|
293
|
-
console.error(
|
|
418
|
+
console.error(pc.dim(`Code: ${adminError.code}`));
|
|
419
|
+
}
|
|
420
|
+
if (rawErr && typeof rawErr.status === "number") {
|
|
421
|
+
console.error(pc.dim(`Status: ${rawErr.status}`));
|
|
294
422
|
}
|
|
423
|
+
console.error(pc.dim(`Type: ${adminError.type}`));
|
|
424
|
+
if (adminError.type === "degraded" && typeof adminError.retryAfter === "number") {
|
|
425
|
+
console.error(pc.yellow(`Retry after: ${adminError.retryAfter}s`));
|
|
426
|
+
}
|
|
427
|
+
if (rawErr && typeof rawErr.suggestion === "string") {
|
|
428
|
+
console.error(pc.yellow(rawErr.suggestion));
|
|
429
|
+
}
|
|
430
|
+
if (resolveFormat(options) === "json") {
|
|
431
|
+
console.log(JSON.stringify(adminError));
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
function exitWithError(error, options = {}) {
|
|
435
|
+
printError(error, options);
|
|
436
|
+
process.exit(getExitCode(error));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// src/lib/client.ts
|
|
440
|
+
function isValidBearerToken(secret) {
|
|
441
|
+
return secret.startsWith("sk01_") || secret.startsWith("pat01_");
|
|
442
|
+
}
|
|
443
|
+
function resolveClient(apiKeyFlag) {
|
|
444
|
+
let publishableKey = process.env.SOFTWARE_PUBLISHABLE_KEY;
|
|
445
|
+
let secretKey = apiKeyFlag ?? process.env.SOFTWARE_SECRET_KEY;
|
|
446
|
+
let tenantId;
|
|
447
|
+
if (!publishableKey || !secretKey) {
|
|
448
|
+
const local = loadLocalCredentials();
|
|
449
|
+
if (local) {
|
|
450
|
+
publishableKey = publishableKey ?? local.publishableKey;
|
|
451
|
+
secretKey = secretKey ?? local.secretKey;
|
|
452
|
+
tenantId = tenantId ?? local.tenantId;
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (!publishableKey || !secretKey) {
|
|
456
|
+
const stored = loadCredentials();
|
|
457
|
+
if (stored) {
|
|
458
|
+
publishableKey = publishableKey ?? stored.publishableKey;
|
|
459
|
+
secretKey = secretKey ?? stored.secretKey;
|
|
460
|
+
tenantId = tenantId ?? stored.tenantId;
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
if (!publishableKey || !secretKey) {
|
|
464
|
+
exitWithError({
|
|
465
|
+
type: "permission",
|
|
466
|
+
code: "credential_invalid",
|
|
467
|
+
detail: {
|
|
468
|
+
message: t("AuthenticationRequired"),
|
|
469
|
+
steps: [
|
|
470
|
+
t("RunLoginToAuthenticate"),
|
|
471
|
+
t("PassApiKey"),
|
|
472
|
+
t("SetEnvVars")
|
|
473
|
+
]
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
if (!isValidBearerToken(secretKey)) {
|
|
478
|
+
exitWithError({
|
|
479
|
+
type: "permission",
|
|
480
|
+
code: "credential_invalid",
|
|
481
|
+
detail: {
|
|
482
|
+
message: t("InvalidApiKeyFormat"),
|
|
483
|
+
suggestion: t("LegacyHexCredentialsRejected")
|
|
484
|
+
}
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
const serverOptions = { publishableKey, secretKey, tenantId };
|
|
488
|
+
return {
|
|
489
|
+
collections: new CollectionClient(
|
|
490
|
+
publishableKey,
|
|
491
|
+
secretKey,
|
|
492
|
+
void 0,
|
|
493
|
+
void 0,
|
|
494
|
+
void 0,
|
|
495
|
+
tenantId
|
|
496
|
+
),
|
|
497
|
+
commerce: new ServerCommerceClient(serverOptions),
|
|
498
|
+
publishableKey,
|
|
499
|
+
secretKey
|
|
500
|
+
};
|
|
295
501
|
}
|
|
296
502
|
|
|
297
503
|
// src/commands/crud.ts
|
|
@@ -300,32 +506,44 @@ import { basename } from "path";
|
|
|
300
506
|
import { COLLECTIONS } from "@01.software/sdk";
|
|
301
507
|
|
|
302
508
|
// src/lib/parse.ts
|
|
303
|
-
|
|
304
|
-
|
|
509
|
+
function failArg(label, value, expectedKind) {
|
|
510
|
+
const message = expectedKind === "object" ? t("InvalidJsonObject", { label }) : expectedKind === "array" ? t("InvalidJsonArray", { label }) : t("InvalidJsonValue", { label, value });
|
|
511
|
+
exitWithError({
|
|
512
|
+
type: "validation",
|
|
513
|
+
code: "invalid_argument",
|
|
514
|
+
field: label,
|
|
515
|
+
// `message` is read by `printError` for the stderr headline so the
|
|
516
|
+
// user sees the label/value in the one-line summary; `detail` carries
|
|
517
|
+
// the structured fields for `--format json` consumers.
|
|
518
|
+
message,
|
|
519
|
+
detail: { message, value, field: label }
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
function parseJson(value, label, options = {}) {
|
|
523
|
+
let parsed;
|
|
305
524
|
try {
|
|
306
|
-
|
|
307
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
308
|
-
console.error(pc3.red(`--${label} must be a JSON object.`));
|
|
309
|
-
process.exit(3);
|
|
310
|
-
}
|
|
311
|
-
return parsed;
|
|
525
|
+
parsed = JSON.parse(value);
|
|
312
526
|
} catch {
|
|
313
|
-
|
|
314
|
-
process.exit(3);
|
|
527
|
+
return failArg(label, value, "value");
|
|
315
528
|
}
|
|
529
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
530
|
+
return failArg(label, value, "object");
|
|
531
|
+
}
|
|
532
|
+
void options;
|
|
533
|
+
return parsed;
|
|
316
534
|
}
|
|
317
|
-
function parseJsonArray(value, label) {
|
|
535
|
+
function parseJsonArray(value, label, options = {}) {
|
|
536
|
+
let parsed;
|
|
318
537
|
try {
|
|
319
|
-
|
|
320
|
-
if (!Array.isArray(parsed)) {
|
|
321
|
-
console.error(pc3.red(`--${label} must be a JSON array.`));
|
|
322
|
-
process.exit(3);
|
|
323
|
-
}
|
|
324
|
-
return parsed;
|
|
538
|
+
parsed = JSON.parse(value);
|
|
325
539
|
} catch {
|
|
326
|
-
|
|
327
|
-
|
|
540
|
+
return failArg(label, value, "value");
|
|
541
|
+
}
|
|
542
|
+
if (!Array.isArray(parsed)) {
|
|
543
|
+
return failArg(label, value, "array");
|
|
328
544
|
}
|
|
545
|
+
void options;
|
|
546
|
+
return parsed;
|
|
329
547
|
}
|
|
330
548
|
|
|
331
549
|
// src/commands/crud.ts
|
|
@@ -854,7 +1072,7 @@ import { createServer } from "http";
|
|
|
854
1072
|
import { execFile, exec } from "child_process";
|
|
855
1073
|
import { platform } from "os";
|
|
856
1074
|
import { URL } from "url";
|
|
857
|
-
import
|
|
1075
|
+
import pc2 from "picocolors";
|
|
858
1076
|
var WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software";
|
|
859
1077
|
var TIMEOUT_MS = 3 * 60 * 1e3;
|
|
860
1078
|
function escapeHtml(s) {
|
|
@@ -864,7 +1082,7 @@ function openBrowser(url) {
|
|
|
864
1082
|
const os = platform();
|
|
865
1083
|
const onError = () => {
|
|
866
1084
|
console.log(
|
|
867
|
-
|
|
1085
|
+
pc2.yellow(
|
|
868
1086
|
`Could not open browser automatically. Open this URL manually:
|
|
869
1087
|
${url}`
|
|
870
1088
|
)
|
|
@@ -910,7 +1128,7 @@ async function exchangeCode(code) {
|
|
|
910
1128
|
if (!res.ok) {
|
|
911
1129
|
const body = await res.text().catch(() => "");
|
|
912
1130
|
console.error(
|
|
913
|
-
|
|
1131
|
+
pc2.red(
|
|
914
1132
|
`Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`
|
|
915
1133
|
)
|
|
916
1134
|
);
|
|
@@ -918,7 +1136,7 @@ async function exchangeCode(code) {
|
|
|
918
1136
|
}
|
|
919
1137
|
const data = await res.json();
|
|
920
1138
|
if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") {
|
|
921
|
-
console.error(
|
|
1139
|
+
console.error(pc2.red(`Exchange failed: malformed response from ${url}`));
|
|
922
1140
|
return null;
|
|
923
1141
|
}
|
|
924
1142
|
return {
|
|
@@ -927,12 +1145,12 @@ async function exchangeCode(code) {
|
|
|
927
1145
|
tenantName: data.tenantName,
|
|
928
1146
|
tenantId: data.tenantId,
|
|
929
1147
|
tenants: Array.isArray(data.tenants) ? data.tenants.filter(
|
|
930
|
-
(
|
|
1148
|
+
(t2) => typeof t2?.id === "string" && typeof t2?.name === "string"
|
|
931
1149
|
) : void 0
|
|
932
1150
|
};
|
|
933
1151
|
} catch (err) {
|
|
934
1152
|
console.error(
|
|
935
|
-
|
|
1153
|
+
pc2.red(
|
|
936
1154
|
`Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
937
1155
|
)
|
|
938
1156
|
);
|
|
@@ -954,7 +1172,7 @@ function startAuthServer(options) {
|
|
|
954
1172
|
const error = url.searchParams.get("error");
|
|
955
1173
|
if (error) {
|
|
956
1174
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error));
|
|
957
|
-
console.error(
|
|
1175
|
+
console.error(pc2.red(`Login failed: ${error}`));
|
|
958
1176
|
cleanup(2);
|
|
959
1177
|
return;
|
|
960
1178
|
}
|
|
@@ -967,14 +1185,14 @@ function startAuthServer(options) {
|
|
|
967
1185
|
}
|
|
968
1186
|
if (receivedState !== options.state) {
|
|
969
1187
|
res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML('State mismatch. Start over with "01 login".'));
|
|
970
|
-
console.error(
|
|
1188
|
+
console.error(pc2.red("Login failed: state mismatch."));
|
|
971
1189
|
cleanup(2);
|
|
972
1190
|
return;
|
|
973
1191
|
}
|
|
974
1192
|
exchangeCode(code).then((creds) => {
|
|
975
1193
|
if (!creds) {
|
|
976
1194
|
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML('Invalid or expired code. Start over with "01 login".'));
|
|
977
|
-
console.error(
|
|
1195
|
+
console.error(pc2.red("Login failed: code exchange failed."));
|
|
978
1196
|
cleanup(2);
|
|
979
1197
|
return;
|
|
980
1198
|
}
|
|
@@ -987,9 +1205,9 @@ function startAuthServer(options) {
|
|
|
987
1205
|
if (creds.tenants && creds.tenants.length > 0) {
|
|
988
1206
|
saveTenantList(creds.tenants);
|
|
989
1207
|
}
|
|
990
|
-
console.log(
|
|
1208
|
+
console.log(pc2.green(`
|
|
991
1209
|
Logged in successfully!`));
|
|
992
|
-
console.log(
|
|
1210
|
+
console.log(pc2.dim(`Tenant: ${creds.tenantName}`));
|
|
993
1211
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML);
|
|
994
1212
|
cleanup(0);
|
|
995
1213
|
});
|
|
@@ -1011,7 +1229,7 @@ Logged in successfully!`));
|
|
|
1011
1229
|
}
|
|
1012
1230
|
timeout = setTimeout(() => {
|
|
1013
1231
|
console.error(
|
|
1014
|
-
|
|
1232
|
+
pc2.red("\nLogin timed out (3 minutes). Please try again.")
|
|
1015
1233
|
);
|
|
1016
1234
|
cleanup(4);
|
|
1017
1235
|
}, TIMEOUT_MS);
|
|
@@ -1030,18 +1248,18 @@ function registerAuthCommands(program2) {
|
|
|
1030
1248
|
state,
|
|
1031
1249
|
saveFn: (creds) => {
|
|
1032
1250
|
saveCredentials(creds);
|
|
1033
|
-
console.log(
|
|
1251
|
+
console.log(pc2.dim(`Credentials saved to ${getCredentialsPath()}`));
|
|
1034
1252
|
}
|
|
1035
1253
|
});
|
|
1036
1254
|
const params = new URLSearchParams({ port: String(port), state });
|
|
1037
1255
|
const loginUrl = `${WEB_URL}/cli-auth?${params.toString()}`;
|
|
1038
|
-
console.log(
|
|
1039
|
-
console.log(
|
|
1256
|
+
console.log(pc2.dim("Opening browser for login..."));
|
|
1257
|
+
console.log(pc2.dim(`If the browser does not open, visit:
|
|
1040
1258
|
${loginUrl}`));
|
|
1041
1259
|
openBrowser(loginUrl);
|
|
1042
1260
|
} catch (err) {
|
|
1043
1261
|
console.error(
|
|
1044
|
-
|
|
1262
|
+
pc2.red(
|
|
1045
1263
|
`Server error: ${err instanceof Error ? err.message : String(err)}`
|
|
1046
1264
|
)
|
|
1047
1265
|
);
|
|
@@ -1051,9 +1269,9 @@ ${loginUrl}`));
|
|
|
1051
1269
|
program2.command("logout").description("Remove stored credentials").action(() => {
|
|
1052
1270
|
const deleted = deleteCredentials();
|
|
1053
1271
|
if (deleted) {
|
|
1054
|
-
console.log(
|
|
1272
|
+
console.log(pc2.green("Logged out. Credentials removed."));
|
|
1055
1273
|
} else {
|
|
1056
|
-
console.log(
|
|
1274
|
+
console.log(pc2.dim("No stored credentials found."));
|
|
1057
1275
|
}
|
|
1058
1276
|
});
|
|
1059
1277
|
program2.command("whoami").description("Show current authentication status").action(() => {
|
|
@@ -1062,23 +1280,23 @@ ${loginUrl}`));
|
|
|
1062
1280
|
const creds = localCreds || globalCreds;
|
|
1063
1281
|
const isLocal = !!localCreds;
|
|
1064
1282
|
if (!creds) {
|
|
1065
|
-
console.log(
|
|
1283
|
+
console.log(pc2.dim('Not logged in. Run "01 login" to authenticate.'));
|
|
1066
1284
|
return;
|
|
1067
1285
|
}
|
|
1068
1286
|
const masked = creds.publishableKey.length > 8 ? creds.publishableKey.slice(0, 4) + "..." + creds.publishableKey.slice(-4) : "****";
|
|
1069
|
-
const scope = isLocal ?
|
|
1070
|
-
console.log(`Tenant: ${
|
|
1071
|
-
console.log(`Publishable Key: ${
|
|
1072
|
-
console.log(`Stored at: ${
|
|
1287
|
+
const scope = isLocal ? pc2.cyan(" (local)") : "";
|
|
1288
|
+
console.log(`Tenant: ${pc2.bold(creds.tenantName)}${scope}`);
|
|
1289
|
+
console.log(`Publishable Key: ${pc2.dim(masked)}`);
|
|
1290
|
+
console.log(`Stored at: ${pc2.dim(creds.storedAt)}`);
|
|
1073
1291
|
console.log(
|
|
1074
|
-
`File: ${
|
|
1292
|
+
`File: ${pc2.dim(isLocal ? getLocalCredentialsPath() : getCredentialsPath())}`
|
|
1075
1293
|
);
|
|
1076
1294
|
});
|
|
1077
1295
|
const tenant = program2.command("tenant").description("Manage tenant switching");
|
|
1078
1296
|
tenant.command("list").description("Show cached tenant list").action(() => {
|
|
1079
1297
|
const tenants = loadTenantList();
|
|
1080
1298
|
if (!tenants || tenants.length === 0) {
|
|
1081
|
-
console.log(
|
|
1299
|
+
console.log(pc2.dim('No cached tenants. Run "01 login" first.'));
|
|
1082
1300
|
return;
|
|
1083
1301
|
}
|
|
1084
1302
|
const localCreds = loadLocalCredentials();
|
|
@@ -1086,30 +1304,30 @@ ${loginUrl}`));
|
|
|
1086
1304
|
const activeCreds = localCreds || globalCreds;
|
|
1087
1305
|
const activeId = activeCreds?.tenantId;
|
|
1088
1306
|
const activeName = activeCreds?.tenantName;
|
|
1089
|
-
console.log(
|
|
1090
|
-
for (const
|
|
1091
|
-
const active = (activeId ?
|
|
1092
|
-
console.log(` ${
|
|
1307
|
+
console.log(pc2.bold("Cached tenants:\n"));
|
|
1308
|
+
for (const t2 of tenants) {
|
|
1309
|
+
const active = (activeId ? t2.id === activeId : t2.name === activeName) ? pc2.green(" *") : "";
|
|
1310
|
+
console.log(` ${t2.name}${active}`);
|
|
1093
1311
|
}
|
|
1094
1312
|
console.log();
|
|
1095
1313
|
if (activeName) {
|
|
1096
1314
|
const scope = localCreds ? "(local)" : "(global)";
|
|
1097
|
-
console.log(
|
|
1315
|
+
console.log(pc2.dim(`* active ${scope}`));
|
|
1098
1316
|
}
|
|
1099
1317
|
});
|
|
1100
1318
|
tenant.command("use <name>").description("Switch to a different tenant via browser").option("--local", "Save credentials locally in the current project").action(async (name, opts) => {
|
|
1101
1319
|
const tenants = loadTenantList();
|
|
1102
1320
|
if (!tenants || tenants.length === 0) {
|
|
1103
|
-
console.error(
|
|
1321
|
+
console.error(pc2.red('No cached tenants. Run "01 login" first.'));
|
|
1104
1322
|
process.exit(2);
|
|
1105
1323
|
}
|
|
1106
1324
|
const match = tenants.find(
|
|
1107
|
-
(
|
|
1325
|
+
(t2) => t2.name.toLowerCase() === name.toLowerCase()
|
|
1108
1326
|
);
|
|
1109
1327
|
if (!match) {
|
|
1110
|
-
console.error(
|
|
1328
|
+
console.error(pc2.red(`Tenant "${name}" not found in cache.`));
|
|
1111
1329
|
console.error(
|
|
1112
|
-
|
|
1330
|
+
pc2.dim(`Available: ${tenants.map((t2) => t2.name).join(", ")}`)
|
|
1113
1331
|
);
|
|
1114
1332
|
process.exit(3);
|
|
1115
1333
|
}
|
|
@@ -1122,12 +1340,12 @@ ${loginUrl}`));
|
|
|
1122
1340
|
if (isLocal) {
|
|
1123
1341
|
saveLocalCredentials(creds);
|
|
1124
1342
|
console.log(
|
|
1125
|
-
|
|
1343
|
+
pc2.dim(`Credentials saved to ${getLocalCredentialsPath()}`)
|
|
1126
1344
|
);
|
|
1127
1345
|
} else {
|
|
1128
1346
|
saveCredentials(creds);
|
|
1129
1347
|
console.log(
|
|
1130
|
-
|
|
1348
|
+
pc2.dim(`Credentials saved to ${getCredentialsPath()}`)
|
|
1131
1349
|
);
|
|
1132
1350
|
}
|
|
1133
1351
|
}
|
|
@@ -1138,13 +1356,13 @@ ${loginUrl}`));
|
|
|
1138
1356
|
tenantId: match.id
|
|
1139
1357
|
});
|
|
1140
1358
|
const loginUrl = `${WEB_URL}/cli-auth?${params.toString()}`;
|
|
1141
|
-
console.log(
|
|
1142
|
-
console.log(
|
|
1359
|
+
console.log(pc2.dim(`Switching to tenant "${match.name}"...`));
|
|
1360
|
+
console.log(pc2.dim(`If the browser does not open, visit:
|
|
1143
1361
|
${loginUrl}`));
|
|
1144
1362
|
openBrowser(loginUrl);
|
|
1145
1363
|
} catch (err) {
|
|
1146
1364
|
console.error(
|
|
1147
|
-
|
|
1365
|
+
pc2.red(
|
|
1148
1366
|
`Server error: ${err instanceof Error ? err.message : String(err)}`
|
|
1149
1367
|
)
|
|
1150
1368
|
);
|
|
@@ -1205,13 +1423,28 @@ import { existsSync as existsSync2 } from "fs";
|
|
|
1205
1423
|
import { spawn } from "child_process";
|
|
1206
1424
|
import { fileURLToPath } from "url";
|
|
1207
1425
|
var __dirname = dirname(fileURLToPath(import.meta.url));
|
|
1208
|
-
function findStdioEntry() {
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1426
|
+
function findStdioEntry(baseDir = __dirname) {
|
|
1427
|
+
const packaged = resolve(baseDir, "mcp/stdio.js");
|
|
1428
|
+
if (existsSync2(packaged)) return packaged;
|
|
1429
|
+
const roots = ["../../../..", "../../..", "../.."];
|
|
1430
|
+
const entries = ["apps/mcp/dist/stdio.js", "apps/mcp/.xmcp/stdio.js"];
|
|
1431
|
+
for (const entry of entries) {
|
|
1432
|
+
for (const root of roots) {
|
|
1433
|
+
const candidate = resolve(baseDir, root, entry);
|
|
1434
|
+
if (existsSync2(candidate)) return candidate;
|
|
1435
|
+
}
|
|
1212
1436
|
}
|
|
1213
1437
|
return null;
|
|
1214
1438
|
}
|
|
1439
|
+
function createMcpEnv(baseEnv, client) {
|
|
1440
|
+
const env = {
|
|
1441
|
+
...baseEnv,
|
|
1442
|
+
SOFTWARE_PUBLISHABLE_KEY: client.publishableKey,
|
|
1443
|
+
SOFTWARE_SECRET_KEY: client.secretKey
|
|
1444
|
+
};
|
|
1445
|
+
delete env.SOFTWARE_TENANT_ID;
|
|
1446
|
+
return env;
|
|
1447
|
+
}
|
|
1215
1448
|
function registerMcpCommands(program2) {
|
|
1216
1449
|
program2.command("mcp").description("Start MCP server over stdio").action(() => {
|
|
1217
1450
|
const client = resolveClient(program2.opts().apiKey);
|
|
@@ -1224,11 +1457,7 @@ function registerMcpCommands(program2) {
|
|
|
1224
1457
|
);
|
|
1225
1458
|
}
|
|
1226
1459
|
const child = spawn(process.execPath, [stdioEntry], {
|
|
1227
|
-
env:
|
|
1228
|
-
...process.env,
|
|
1229
|
-
SOFTWARE_PUBLISHABLE_KEY: client.publishableKey,
|
|
1230
|
-
SOFTWARE_SECRET_KEY: client.secretKey
|
|
1231
|
-
},
|
|
1460
|
+
env: createMcpEnv(process.env, client),
|
|
1232
1461
|
stdio: ["inherit", "inherit", "inherit"]
|
|
1233
1462
|
});
|
|
1234
1463
|
child.on("error", (err) => {
|
|
@@ -1243,12 +1472,17 @@ function registerMcpCommands(program2) {
|
|
|
1243
1472
|
// src/index.ts
|
|
1244
1473
|
var require2 = createRequire(import.meta.url);
|
|
1245
1474
|
var { version } = require2("../package.json");
|
|
1246
|
-
process.on("unhandledRejection", (err) => {
|
|
1247
|
-
exitWithError(err);
|
|
1248
|
-
});
|
|
1249
1475
|
var program = new Command();
|
|
1250
|
-
program.name("01").description("CLI for the 01.software platform").version(version).option("--api-key <key>", "API key (sk01_... or pat01_... token)").option("--format <format>", "Output format: json, table, or ndjson");
|
|
1476
|
+
program.name("01").description("CLI for the 01.software platform").version(version).option("--api-key <key>", "API key (sk01_... or pat01_... token)").option("--format <format>", "Output format: json, table, or ndjson").option("--lang <locale>", "Force locale (ko or en)");
|
|
1251
1477
|
var getFormat = () => program.opts().format ?? process.env.OUTPUT_FORMAT ?? "json";
|
|
1478
|
+
program.hook("preAction", () => {
|
|
1479
|
+
const lang = program.opts().lang;
|
|
1480
|
+
if (lang === "ko" || lang === "en") setLocale(lang);
|
|
1481
|
+
process.env.OUTPUT_FORMAT = getFormat();
|
|
1482
|
+
});
|
|
1483
|
+
process.on("unhandledRejection", (err) => {
|
|
1484
|
+
exitWithError(err, { format: getFormat() });
|
|
1485
|
+
});
|
|
1252
1486
|
var getClient = () => resolveClient(program.opts().apiKey);
|
|
1253
1487
|
registerCrudCommands(program, getClient, getFormat);
|
|
1254
1488
|
registerOrderCommands(program, getClient, getFormat);
|