@01.software/cli 0.5.2 → 0.7.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/dist/index.js +404 -179
- package/dist/index.js.map +1 -1
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -7,11 +7,8 @@ import { Command } from "commander";
|
|
|
7
7
|
// src/lib/client.ts
|
|
8
8
|
import {
|
|
9
9
|
CollectionClient,
|
|
10
|
-
|
|
11
|
-
CartApi,
|
|
12
|
-
ProductApi
|
|
10
|
+
ServerCommerceClient
|
|
13
11
|
} from "@01.software/sdk";
|
|
14
|
-
import pc from "picocolors";
|
|
15
12
|
|
|
16
13
|
// src/lib/credentials.ts
|
|
17
14
|
import {
|
|
@@ -95,7 +92,7 @@ function loadTenantList() {
|
|
|
95
92
|
const data = JSON.parse(raw);
|
|
96
93
|
if (!Array.isArray(data)) return null;
|
|
97
94
|
const valid = data.filter(
|
|
98
|
-
(
|
|
95
|
+
(t2) => typeof t2?.id === "string" && typeof t2?.name === "string"
|
|
99
96
|
);
|
|
100
97
|
return valid.length > 0 ? valid : null;
|
|
101
98
|
} catch {
|
|
@@ -128,79 +125,209 @@ ${entry}
|
|
|
128
125
|
}
|
|
129
126
|
}
|
|
130
127
|
|
|
131
|
-
// src/lib/
|
|
132
|
-
|
|
133
|
-
|
|
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);
|
|
134
154
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (tenantHeaderInterceptorInstalled) return;
|
|
138
|
-
tenantHeaderInterceptorInstalled = true;
|
|
139
|
-
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
140
|
-
globalThis.fetch = async (input, init) => {
|
|
141
|
-
const headers = new Headers(init?.headers);
|
|
142
|
-
if (!headers.has("X-Tenant-Id")) headers.set("X-Tenant-Id", tenantId);
|
|
143
|
-
return originalFetch(input, { ...init, headers });
|
|
144
|
-
};
|
|
155
|
+
function isDegradedCode(code) {
|
|
156
|
+
return DEGRADED_CODES.includes(code);
|
|
145
157
|
}
|
|
146
|
-
function
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
if (
|
|
151
|
-
const
|
|
152
|
-
if (
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
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
|
+
}
|
|
156
179
|
}
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
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
|
+
};
|
|
164
214
|
}
|
|
165
215
|
}
|
|
166
|
-
if (!publishableKey || !secretKey) {
|
|
167
|
-
console.error(pc.red("Authentication required."));
|
|
168
|
-
console.error(
|
|
169
|
-
pc.dim(
|
|
170
|
-
'Run "01 login" to authenticate via browser,\nor pass --api-key <token>,\nor set SOFTWARE_PUBLISHABLE_KEY and SOFTWARE_SECRET_KEY environment variables.'
|
|
171
|
-
)
|
|
172
|
-
);
|
|
173
|
-
process.exit(2);
|
|
174
|
-
}
|
|
175
|
-
if (!isValidBearerToken(secretKey)) {
|
|
176
|
-
console.error(
|
|
177
|
-
pc.red(
|
|
178
|
-
"Invalid API key format. Expected sk01_ or pat01_ token.\nLegacy hex credentials are no longer accepted \u2014 run `01 login` to re-authenticate."
|
|
179
|
-
)
|
|
180
|
-
);
|
|
181
|
-
process.exit(2);
|
|
182
|
-
}
|
|
183
|
-
if (tenantId) installTenantHeaderInterceptor(tenantId);
|
|
184
|
-
const apiOpts = { publishableKey, secretKey };
|
|
185
216
|
return {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
product: new ProductApi(apiOpts),
|
|
190
|
-
publishableKey,
|
|
191
|
-
secretKey
|
|
217
|
+
type: "validation",
|
|
218
|
+
code: "unknown",
|
|
219
|
+
detail: { message: err instanceof Error ? err.message : String(err) }
|
|
192
220
|
};
|
|
193
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
|
+
}
|
|
194
321
|
|
|
195
322
|
// src/lib/output.ts
|
|
196
|
-
|
|
323
|
+
var CLI_I18N_KEYS = Object.keys(CLI_I18N_KO);
|
|
197
324
|
function printJson(data) {
|
|
198
325
|
console.log(JSON.stringify(data, null, 2));
|
|
199
326
|
}
|
|
200
327
|
function printTable(data) {
|
|
201
328
|
if (Array.isArray(data)) {
|
|
202
329
|
if (data.length === 0) {
|
|
203
|
-
console.log(
|
|
330
|
+
console.log(pc.dim(t("Empty")));
|
|
204
331
|
return;
|
|
205
332
|
}
|
|
206
333
|
const keys = Object.keys(data[0]);
|
|
@@ -228,7 +355,7 @@ function printTable(data) {
|
|
|
228
355
|
const maxKey = Math.max(...entries.map(([k]) => k.length));
|
|
229
356
|
for (const [key, value] of entries) {
|
|
230
357
|
const display = typeof value === "object" ? JSON.stringify(value) : String(value);
|
|
231
|
-
console.log(`${
|
|
358
|
+
console.log(`${pc.bold(key.padEnd(maxKey))} ${display}`);
|
|
232
359
|
}
|
|
233
360
|
} else {
|
|
234
361
|
console.log(String(data));
|
|
@@ -254,9 +381,12 @@ function printResult(data, format) {
|
|
|
254
381
|
const resp = data;
|
|
255
382
|
printTable(resp.docs);
|
|
256
383
|
console.log(
|
|
257
|
-
|
|
384
|
+
pc.dim(
|
|
258
385
|
`
|
|
259
|
-
${resp.totalDocs}
|
|
386
|
+
${t("Total", { n: resp.totalDocs })} | ${t("PageOf", {
|
|
387
|
+
page: resp.page,
|
|
388
|
+
total: resp.totalPages
|
|
389
|
+
})}`
|
|
260
390
|
)
|
|
261
391
|
);
|
|
262
392
|
return;
|
|
@@ -267,35 +397,112 @@ ${resp.totalDocs} total | page ${resp.page}/${resp.totalPages}`
|
|
|
267
397
|
}
|
|
268
398
|
}
|
|
269
399
|
function getExitCode(error) {
|
|
270
|
-
|
|
271
|
-
const err = error;
|
|
272
|
-
if (err.name === "ConfigError") return 2;
|
|
273
|
-
if (err.name === "ValidationError") return 3;
|
|
274
|
-
if (err.name === "NetworkError" || err.name === "TimeoutError") return 4;
|
|
275
|
-
if (err.name === "GoneError") return 5;
|
|
276
|
-
if (err.name === "UsageLimitError") return 6;
|
|
277
|
-
const s = err.status;
|
|
278
|
-
if (s === 401) return 2;
|
|
279
|
-
if (s === 400 || s === 422) return 3;
|
|
280
|
-
if (s === 408 || s === 503) return 4;
|
|
281
|
-
if (s === 404) return 5;
|
|
282
|
-
if (s === 429) return 6;
|
|
283
|
-
return 1;
|
|
400
|
+
return adminErrorExitCode(classifyError(error));
|
|
284
401
|
}
|
|
285
|
-
function
|
|
286
|
-
|
|
287
|
-
process.exit(getExitCode(error));
|
|
402
|
+
function resolveFormat(options) {
|
|
403
|
+
return options.format ?? process.env.OUTPUT_FORMAT ?? "json";
|
|
288
404
|
}
|
|
289
|
-
function printError(error) {
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
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}`));
|
|
296
417
|
} else {
|
|
297
|
-
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}`));
|
|
298
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
|
+
var tenantHeaderInterceptorInstalled = false;
|
|
444
|
+
function installTenantHeaderInterceptor(tenantId) {
|
|
445
|
+
if (tenantHeaderInterceptorInstalled) return;
|
|
446
|
+
tenantHeaderInterceptorInstalled = true;
|
|
447
|
+
const originalFetch = globalThis.fetch.bind(globalThis);
|
|
448
|
+
globalThis.fetch = async (input, init) => {
|
|
449
|
+
const headers = new Headers(init?.headers);
|
|
450
|
+
if (!headers.has("X-Tenant-Id")) headers.set("X-Tenant-Id", tenantId);
|
|
451
|
+
return originalFetch(input, { ...init, headers });
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function resolveClient(apiKeyFlag) {
|
|
455
|
+
let publishableKey = process.env.SOFTWARE_PUBLISHABLE_KEY;
|
|
456
|
+
let secretKey = apiKeyFlag ?? process.env.SOFTWARE_SECRET_KEY;
|
|
457
|
+
let tenantId;
|
|
458
|
+
if (!publishableKey || !secretKey) {
|
|
459
|
+
const local = loadLocalCredentials();
|
|
460
|
+
if (local) {
|
|
461
|
+
publishableKey = publishableKey ?? local.publishableKey;
|
|
462
|
+
secretKey = secretKey ?? local.secretKey;
|
|
463
|
+
tenantId = local.tenantId;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (!publishableKey || !secretKey) {
|
|
467
|
+
const stored = loadCredentials();
|
|
468
|
+
if (stored) {
|
|
469
|
+
publishableKey = publishableKey ?? stored.publishableKey;
|
|
470
|
+
secretKey = secretKey ?? stored.secretKey;
|
|
471
|
+
tenantId = tenantId ?? stored.tenantId;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
if (!publishableKey || !secretKey) {
|
|
475
|
+
exitWithError({
|
|
476
|
+
type: "permission",
|
|
477
|
+
code: "credential_invalid",
|
|
478
|
+
detail: {
|
|
479
|
+
message: t("AuthenticationRequired"),
|
|
480
|
+
steps: [
|
|
481
|
+
t("RunLoginToAuthenticate"),
|
|
482
|
+
t("PassApiKey"),
|
|
483
|
+
t("SetEnvVars")
|
|
484
|
+
]
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
if (!isValidBearerToken(secretKey)) {
|
|
489
|
+
exitWithError({
|
|
490
|
+
type: "permission",
|
|
491
|
+
code: "credential_invalid",
|
|
492
|
+
detail: {
|
|
493
|
+
message: t("InvalidApiKeyFormat"),
|
|
494
|
+
suggestion: t("LegacyHexCredentialsRejected")
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
}
|
|
498
|
+
if (tenantId) installTenantHeaderInterceptor(tenantId);
|
|
499
|
+
const serverOptions = { publishableKey, secretKey, tenantId };
|
|
500
|
+
return {
|
|
501
|
+
collections: new CollectionClient(publishableKey, secretKey),
|
|
502
|
+
commerce: new ServerCommerceClient(serverOptions),
|
|
503
|
+
publishableKey,
|
|
504
|
+
secretKey
|
|
505
|
+
};
|
|
299
506
|
}
|
|
300
507
|
|
|
301
508
|
// src/commands/crud.ts
|
|
@@ -304,32 +511,44 @@ import { basename } from "path";
|
|
|
304
511
|
import { COLLECTIONS } from "@01.software/sdk";
|
|
305
512
|
|
|
306
513
|
// src/lib/parse.ts
|
|
307
|
-
|
|
308
|
-
|
|
514
|
+
function failArg(label, value, expectedKind) {
|
|
515
|
+
const message = expectedKind === "object" ? t("InvalidJsonObject", { label }) : expectedKind === "array" ? t("InvalidJsonArray", { label }) : t("InvalidJsonValue", { label, value });
|
|
516
|
+
exitWithError({
|
|
517
|
+
type: "validation",
|
|
518
|
+
code: "invalid_argument",
|
|
519
|
+
field: label,
|
|
520
|
+
// `message` is read by `printError` for the stderr headline so the
|
|
521
|
+
// user sees the label/value in the one-line summary; `detail` carries
|
|
522
|
+
// the structured fields for `--format json` consumers.
|
|
523
|
+
message,
|
|
524
|
+
detail: { message, value, field: label }
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
function parseJson(value, label, options = {}) {
|
|
528
|
+
let parsed;
|
|
309
529
|
try {
|
|
310
|
-
|
|
311
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
312
|
-
console.error(pc3.red(`--${label} must be a JSON object.`));
|
|
313
|
-
process.exit(3);
|
|
314
|
-
}
|
|
315
|
-
return parsed;
|
|
530
|
+
parsed = JSON.parse(value);
|
|
316
531
|
} catch {
|
|
317
|
-
|
|
318
|
-
|
|
532
|
+
return failArg(label, value, "value");
|
|
533
|
+
}
|
|
534
|
+
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
|
|
535
|
+
return failArg(label, value, "object");
|
|
319
536
|
}
|
|
537
|
+
void options;
|
|
538
|
+
return parsed;
|
|
320
539
|
}
|
|
321
|
-
function parseJsonArray(value, label) {
|
|
540
|
+
function parseJsonArray(value, label, options = {}) {
|
|
541
|
+
let parsed;
|
|
322
542
|
try {
|
|
323
|
-
|
|
324
|
-
if (!Array.isArray(parsed)) {
|
|
325
|
-
console.error(pc3.red(`--${label} must be a JSON array.`));
|
|
326
|
-
process.exit(3);
|
|
327
|
-
}
|
|
328
|
-
return parsed;
|
|
543
|
+
parsed = JSON.parse(value);
|
|
329
544
|
} catch {
|
|
330
|
-
|
|
331
|
-
process.exit(3);
|
|
545
|
+
return failArg(label, value, "value");
|
|
332
546
|
}
|
|
547
|
+
if (!Array.isArray(parsed)) {
|
|
548
|
+
return failArg(label, value, "array");
|
|
549
|
+
}
|
|
550
|
+
void options;
|
|
551
|
+
return parsed;
|
|
333
552
|
}
|
|
334
553
|
|
|
335
554
|
// src/commands/crud.ts
|
|
@@ -534,9 +753,9 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
534
753
|
opts.shippingAddress,
|
|
535
754
|
"shipping-address"
|
|
536
755
|
);
|
|
537
|
-
const
|
|
756
|
+
const orderItems = parseJsonArray(opts.products, "products");
|
|
538
757
|
const data = {
|
|
539
|
-
|
|
758
|
+
pgPaymentId: opts.paymentId,
|
|
540
759
|
orderNumber: opts.orderNumber,
|
|
541
760
|
customerSnapshot: {
|
|
542
761
|
email: opts.email,
|
|
@@ -545,7 +764,7 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
545
764
|
},
|
|
546
765
|
customer: opts.customer,
|
|
547
766
|
shippingAddress,
|
|
548
|
-
|
|
767
|
+
orderItems,
|
|
549
768
|
totalAmount: opts.totalAmount
|
|
550
769
|
};
|
|
551
770
|
if (opts.dryRun) {
|
|
@@ -556,9 +775,9 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
556
775
|
return;
|
|
557
776
|
}
|
|
558
777
|
const client = getClient2();
|
|
559
|
-
const result = await client.
|
|
778
|
+
const result = await client.commerce.orders.create({
|
|
560
779
|
...data,
|
|
561
|
-
|
|
780
|
+
orderItems
|
|
562
781
|
});
|
|
563
782
|
printResult(result, getFormat2());
|
|
564
783
|
} catch (e) {
|
|
@@ -568,8 +787,9 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
568
787
|
order.command("get <orderNumber>").description("Get an order by order number").action(async (orderNumber) => {
|
|
569
788
|
try {
|
|
570
789
|
const client = getClient2();
|
|
571
|
-
const
|
|
572
|
-
|
|
790
|
+
const { docs: [order2] } = await client.collections.from("orders").find({ where: { orderNumber: { equals: orderNumber } }, limit: 1, depth: 1 });
|
|
791
|
+
if (!order2) throw new Error("Order not found");
|
|
792
|
+
printResult(order2, getFormat2());
|
|
573
793
|
} catch (e) {
|
|
574
794
|
exitWithError(e);
|
|
575
795
|
}
|
|
@@ -585,7 +805,7 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
585
805
|
return;
|
|
586
806
|
}
|
|
587
807
|
const client = getClient2();
|
|
588
|
-
const result = await client.
|
|
808
|
+
const result = await client.commerce.orders.update(data);
|
|
589
809
|
printResult(result, getFormat2());
|
|
590
810
|
} catch (e) {
|
|
591
811
|
exitWithError(e);
|
|
@@ -596,7 +816,7 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
596
816
|
const customerSnapshot = parseJson(opts.customer, "customer");
|
|
597
817
|
const data = {
|
|
598
818
|
cartId: opts.cartId,
|
|
599
|
-
|
|
819
|
+
pgPaymentId: opts.paymentId,
|
|
600
820
|
orderNumber: opts.orderNumber,
|
|
601
821
|
customerSnapshot
|
|
602
822
|
};
|
|
@@ -608,7 +828,7 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
608
828
|
return;
|
|
609
829
|
}
|
|
610
830
|
const client = getClient2();
|
|
611
|
-
const result = await client.
|
|
831
|
+
const result = await client.commerce.orders.checkout({
|
|
612
832
|
...data,
|
|
613
833
|
customerSnapshot
|
|
614
834
|
});
|
|
@@ -634,7 +854,7 @@ function registerOrderCommands(program2, getClient2, getFormat2) {
|
|
|
634
854
|
return;
|
|
635
855
|
}
|
|
636
856
|
const client = getClient2();
|
|
637
|
-
const result = await client.
|
|
857
|
+
const result = await client.commerce.orders.createFulfillment({
|
|
638
858
|
...data,
|
|
639
859
|
items
|
|
640
860
|
});
|
|
@@ -653,10 +873,10 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
|
|
|
653
873
|
"Return reason (change_of_mind, defective, wrong_delivery, damaged, other)"
|
|
654
874
|
).option("--reason-detail <text>", "Detailed reason").option("--dry-run", "Validate inputs without executing").action(async (orderNumber, opts) => {
|
|
655
875
|
try {
|
|
656
|
-
const
|
|
876
|
+
const returnItems = parseJsonArray(opts.products, "products");
|
|
657
877
|
const data = {
|
|
658
878
|
orderNumber,
|
|
659
|
-
|
|
879
|
+
returnItems,
|
|
660
880
|
refundAmount: opts.refundAmount,
|
|
661
881
|
reason: opts.reason,
|
|
662
882
|
reasonDetail: opts.reasonDetail
|
|
@@ -669,9 +889,9 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
|
|
|
669
889
|
return;
|
|
670
890
|
}
|
|
671
891
|
const client = getClient2();
|
|
672
|
-
const result = await client.
|
|
892
|
+
const result = await client.commerce.orders.createReturn({
|
|
673
893
|
...data,
|
|
674
|
-
|
|
894
|
+
returnItems
|
|
675
895
|
});
|
|
676
896
|
printResult(result, getFormat2());
|
|
677
897
|
} catch (e) {
|
|
@@ -692,7 +912,7 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
|
|
|
692
912
|
return;
|
|
693
913
|
}
|
|
694
914
|
const client = getClient2();
|
|
695
|
-
const result = await client.
|
|
915
|
+
const result = await client.commerce.orders.updateReturn(data);
|
|
696
916
|
printResult(result, getFormat2());
|
|
697
917
|
} catch (e) {
|
|
698
918
|
exitWithError(e);
|
|
@@ -700,12 +920,12 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
|
|
|
700
920
|
});
|
|
701
921
|
ret.command("refund <orderNumber>").description("Return with refund").requiredOption("--products <json>", "Return products array (JSON)").requiredOption("--refund-amount <n>", "Refund amount", parseFloat).requiredOption("--payment-id <id>", "Payment ID").option("--reason <reason>", "Return reason").option("--reason-detail <text>", "Detailed reason").option("--refund-receipt-url <url>", "Refund receipt URL").option("--dry-run", "Validate inputs without executing").action(async (orderNumber, opts) => {
|
|
702
922
|
try {
|
|
703
|
-
const
|
|
923
|
+
const returnItems = parseJsonArray(opts.products, "products");
|
|
704
924
|
const data = {
|
|
705
925
|
orderNumber,
|
|
706
|
-
|
|
926
|
+
returnItems,
|
|
707
927
|
refundAmount: opts.refundAmount,
|
|
708
|
-
|
|
928
|
+
pgPaymentId: opts.paymentId,
|
|
709
929
|
reason: opts.reason,
|
|
710
930
|
reasonDetail: opts.reasonDetail,
|
|
711
931
|
refundReceiptUrl: opts.refundReceiptUrl
|
|
@@ -718,9 +938,9 @@ function registerReturnCommands(program2, getClient2, getFormat2) {
|
|
|
718
938
|
return;
|
|
719
939
|
}
|
|
720
940
|
const client = getClient2();
|
|
721
|
-
const result = await client.
|
|
941
|
+
const result = await client.commerce.orders.returnWithRefund({
|
|
722
942
|
...data,
|
|
723
|
-
|
|
943
|
+
returnItems
|
|
724
944
|
});
|
|
725
945
|
printResult(result, getFormat2());
|
|
726
946
|
} catch (e) {
|
|
@@ -753,7 +973,7 @@ function registerCartCommands(program2, getClient2, getFormat2) {
|
|
|
753
973
|
return;
|
|
754
974
|
}
|
|
755
975
|
const client = getClient2();
|
|
756
|
-
const result = await client.cart.addItem(data);
|
|
976
|
+
const result = await client.commerce.cart.addItem(data);
|
|
757
977
|
printResult(result, getFormat2());
|
|
758
978
|
} catch (e) {
|
|
759
979
|
exitWithError(e);
|
|
@@ -774,7 +994,7 @@ function registerCartCommands(program2, getClient2, getFormat2) {
|
|
|
774
994
|
return;
|
|
775
995
|
}
|
|
776
996
|
const client = getClient2();
|
|
777
|
-
const result = await client.cart.updateItem(data);
|
|
997
|
+
const result = await client.commerce.cart.updateItem(data);
|
|
778
998
|
printResult(result, getFormat2());
|
|
779
999
|
} catch (e) {
|
|
780
1000
|
exitWithError(e);
|
|
@@ -795,7 +1015,7 @@ function registerCartCommands(program2, getClient2, getFormat2) {
|
|
|
795
1015
|
return;
|
|
796
1016
|
}
|
|
797
1017
|
const client = getClient2();
|
|
798
|
-
const result = await client.cart.removeItem({ cartItemId });
|
|
1018
|
+
const result = await client.commerce.cart.removeItem({ cartItemId });
|
|
799
1019
|
printResult(result, getFormat2());
|
|
800
1020
|
} catch (e) {
|
|
801
1021
|
exitWithError(e);
|
|
@@ -813,7 +1033,7 @@ function registerStockCommands(program2, getClient2, getFormat2) {
|
|
|
813
1033
|
try {
|
|
814
1034
|
const client = getClient2();
|
|
815
1035
|
const items = parseJsonArray(opts.items, "items");
|
|
816
|
-
const result = await client.product.stockCheck({ items });
|
|
1036
|
+
const result = await client.commerce.product.stockCheck({ items });
|
|
817
1037
|
printResult(result, getFormat2());
|
|
818
1038
|
} catch (e) {
|
|
819
1039
|
exitWithError(e);
|
|
@@ -830,7 +1050,7 @@ function registerTransactionCommands(program2, getClient2, getFormat2) {
|
|
|
830
1050
|
).requiredOption("--payment-method <method>", "Payment method").requiredOption("--receipt-url <url>", "Receipt URL").option("--dry-run", "Validate inputs without executing").action(async (opts) => {
|
|
831
1051
|
try {
|
|
832
1052
|
const data = {
|
|
833
|
-
|
|
1053
|
+
pgPaymentId: opts.paymentId,
|
|
834
1054
|
status: opts.status,
|
|
835
1055
|
paymentMethod: opts.paymentMethod,
|
|
836
1056
|
receiptUrl: opts.receiptUrl
|
|
@@ -843,7 +1063,7 @@ function registerTransactionCommands(program2, getClient2, getFormat2) {
|
|
|
843
1063
|
return;
|
|
844
1064
|
}
|
|
845
1065
|
const client = getClient2();
|
|
846
|
-
const result = await client.
|
|
1066
|
+
const result = await client.commerce.orders.updateTransaction(data);
|
|
847
1067
|
printResult(result, getFormat2());
|
|
848
1068
|
} catch (e) {
|
|
849
1069
|
exitWithError(e);
|
|
@@ -857,7 +1077,7 @@ import { createServer } from "http";
|
|
|
857
1077
|
import { execFile, exec } from "child_process";
|
|
858
1078
|
import { platform } from "os";
|
|
859
1079
|
import { URL } from "url";
|
|
860
|
-
import
|
|
1080
|
+
import pc2 from "picocolors";
|
|
861
1081
|
var WEB_URL = process.env.SOFTWARE_WEB_URL || "https://01.software";
|
|
862
1082
|
var TIMEOUT_MS = 3 * 60 * 1e3;
|
|
863
1083
|
function escapeHtml(s) {
|
|
@@ -867,7 +1087,7 @@ function openBrowser(url) {
|
|
|
867
1087
|
const os = platform();
|
|
868
1088
|
const onError = () => {
|
|
869
1089
|
console.log(
|
|
870
|
-
|
|
1090
|
+
pc2.yellow(
|
|
871
1091
|
`Could not open browser automatically. Open this URL manually:
|
|
872
1092
|
${url}`
|
|
873
1093
|
)
|
|
@@ -913,7 +1133,7 @@ async function exchangeCode(code) {
|
|
|
913
1133
|
if (!res.ok) {
|
|
914
1134
|
const body = await res.text().catch(() => "");
|
|
915
1135
|
console.error(
|
|
916
|
-
|
|
1136
|
+
pc2.red(
|
|
917
1137
|
`Exchange failed: HTTP ${res.status} from ${url}${body ? ` \u2014 ${body.slice(0, 200)}` : ""}`
|
|
918
1138
|
)
|
|
919
1139
|
);
|
|
@@ -921,7 +1141,7 @@ async function exchangeCode(code) {
|
|
|
921
1141
|
}
|
|
922
1142
|
const data = await res.json();
|
|
923
1143
|
if (typeof data.publishableKey !== "string" || typeof data.secretKey !== "string" || typeof data.tenantName !== "string" || typeof data.tenantId !== "string") {
|
|
924
|
-
console.error(
|
|
1144
|
+
console.error(pc2.red(`Exchange failed: malformed response from ${url}`));
|
|
925
1145
|
return null;
|
|
926
1146
|
}
|
|
927
1147
|
return {
|
|
@@ -930,12 +1150,12 @@ async function exchangeCode(code) {
|
|
|
930
1150
|
tenantName: data.tenantName,
|
|
931
1151
|
tenantId: data.tenantId,
|
|
932
1152
|
tenants: Array.isArray(data.tenants) ? data.tenants.filter(
|
|
933
|
-
(
|
|
1153
|
+
(t2) => typeof t2?.id === "string" && typeof t2?.name === "string"
|
|
934
1154
|
) : void 0
|
|
935
1155
|
};
|
|
936
1156
|
} catch (err) {
|
|
937
1157
|
console.error(
|
|
938
|
-
|
|
1158
|
+
pc2.red(
|
|
939
1159
|
`Exchange request to ${url} failed: ${err instanceof Error ? err.message : String(err)}`
|
|
940
1160
|
)
|
|
941
1161
|
);
|
|
@@ -957,7 +1177,7 @@ function startAuthServer(options) {
|
|
|
957
1177
|
const error = url.searchParams.get("error");
|
|
958
1178
|
if (error) {
|
|
959
1179
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML(error));
|
|
960
|
-
console.error(
|
|
1180
|
+
console.error(pc2.red(`Login failed: ${error}`));
|
|
961
1181
|
cleanup(2);
|
|
962
1182
|
return;
|
|
963
1183
|
}
|
|
@@ -970,14 +1190,14 @@ function startAuthServer(options) {
|
|
|
970
1190
|
}
|
|
971
1191
|
if (receivedState !== options.state) {
|
|
972
1192
|
res.writeHead(403, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML('State mismatch. Start over with "01 login".'));
|
|
973
|
-
console.error(
|
|
1193
|
+
console.error(pc2.red("Login failed: state mismatch."));
|
|
974
1194
|
cleanup(2);
|
|
975
1195
|
return;
|
|
976
1196
|
}
|
|
977
1197
|
exchangeCode(code).then((creds) => {
|
|
978
1198
|
if (!creds) {
|
|
979
1199
|
res.writeHead(400, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(ERROR_HTML('Invalid or expired code. Start over with "01 login".'));
|
|
980
|
-
console.error(
|
|
1200
|
+
console.error(pc2.red("Login failed: code exchange failed."));
|
|
981
1201
|
cleanup(2);
|
|
982
1202
|
return;
|
|
983
1203
|
}
|
|
@@ -990,9 +1210,9 @@ function startAuthServer(options) {
|
|
|
990
1210
|
if (creds.tenants && creds.tenants.length > 0) {
|
|
991
1211
|
saveTenantList(creds.tenants);
|
|
992
1212
|
}
|
|
993
|
-
console.log(
|
|
1213
|
+
console.log(pc2.green(`
|
|
994
1214
|
Logged in successfully!`));
|
|
995
|
-
console.log(
|
|
1215
|
+
console.log(pc2.dim(`Tenant: ${creds.tenantName}`));
|
|
996
1216
|
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8", Connection: "close" }).end(SUCCESS_HTML);
|
|
997
1217
|
cleanup(0);
|
|
998
1218
|
});
|
|
@@ -1014,7 +1234,7 @@ Logged in successfully!`));
|
|
|
1014
1234
|
}
|
|
1015
1235
|
timeout = setTimeout(() => {
|
|
1016
1236
|
console.error(
|
|
1017
|
-
|
|
1237
|
+
pc2.red("\nLogin timed out (3 minutes). Please try again.")
|
|
1018
1238
|
);
|
|
1019
1239
|
cleanup(4);
|
|
1020
1240
|
}, TIMEOUT_MS);
|
|
@@ -1033,18 +1253,18 @@ function registerAuthCommands(program2) {
|
|
|
1033
1253
|
state,
|
|
1034
1254
|
saveFn: (creds) => {
|
|
1035
1255
|
saveCredentials(creds);
|
|
1036
|
-
console.log(
|
|
1256
|
+
console.log(pc2.dim(`Credentials saved to ${getCredentialsPath()}`));
|
|
1037
1257
|
}
|
|
1038
1258
|
});
|
|
1039
1259
|
const params = new URLSearchParams({ port: String(port), state });
|
|
1040
1260
|
const loginUrl = `${WEB_URL}/cli-auth?${params.toString()}`;
|
|
1041
|
-
console.log(
|
|
1042
|
-
console.log(
|
|
1261
|
+
console.log(pc2.dim("Opening browser for login..."));
|
|
1262
|
+
console.log(pc2.dim(`If the browser does not open, visit:
|
|
1043
1263
|
${loginUrl}`));
|
|
1044
1264
|
openBrowser(loginUrl);
|
|
1045
1265
|
} catch (err) {
|
|
1046
1266
|
console.error(
|
|
1047
|
-
|
|
1267
|
+
pc2.red(
|
|
1048
1268
|
`Server error: ${err instanceof Error ? err.message : String(err)}`
|
|
1049
1269
|
)
|
|
1050
1270
|
);
|
|
@@ -1054,9 +1274,9 @@ ${loginUrl}`));
|
|
|
1054
1274
|
program2.command("logout").description("Remove stored credentials").action(() => {
|
|
1055
1275
|
const deleted = deleteCredentials();
|
|
1056
1276
|
if (deleted) {
|
|
1057
|
-
console.log(
|
|
1277
|
+
console.log(pc2.green("Logged out. Credentials removed."));
|
|
1058
1278
|
} else {
|
|
1059
|
-
console.log(
|
|
1279
|
+
console.log(pc2.dim("No stored credentials found."));
|
|
1060
1280
|
}
|
|
1061
1281
|
});
|
|
1062
1282
|
program2.command("whoami").description("Show current authentication status").action(() => {
|
|
@@ -1065,23 +1285,23 @@ ${loginUrl}`));
|
|
|
1065
1285
|
const creds = localCreds || globalCreds;
|
|
1066
1286
|
const isLocal = !!localCreds;
|
|
1067
1287
|
if (!creds) {
|
|
1068
|
-
console.log(
|
|
1288
|
+
console.log(pc2.dim('Not logged in. Run "01 login" to authenticate.'));
|
|
1069
1289
|
return;
|
|
1070
1290
|
}
|
|
1071
1291
|
const masked = creds.publishableKey.length > 8 ? creds.publishableKey.slice(0, 4) + "..." + creds.publishableKey.slice(-4) : "****";
|
|
1072
|
-
const scope = isLocal ?
|
|
1073
|
-
console.log(`Tenant: ${
|
|
1074
|
-
console.log(`Publishable Key: ${
|
|
1075
|
-
console.log(`Stored at: ${
|
|
1292
|
+
const scope = isLocal ? pc2.cyan(" (local)") : "";
|
|
1293
|
+
console.log(`Tenant: ${pc2.bold(creds.tenantName)}${scope}`);
|
|
1294
|
+
console.log(`Publishable Key: ${pc2.dim(masked)}`);
|
|
1295
|
+
console.log(`Stored at: ${pc2.dim(creds.storedAt)}`);
|
|
1076
1296
|
console.log(
|
|
1077
|
-
`File: ${
|
|
1297
|
+
`File: ${pc2.dim(isLocal ? getLocalCredentialsPath() : getCredentialsPath())}`
|
|
1078
1298
|
);
|
|
1079
1299
|
});
|
|
1080
1300
|
const tenant = program2.command("tenant").description("Manage tenant switching");
|
|
1081
1301
|
tenant.command("list").description("Show cached tenant list").action(() => {
|
|
1082
1302
|
const tenants = loadTenantList();
|
|
1083
1303
|
if (!tenants || tenants.length === 0) {
|
|
1084
|
-
console.log(
|
|
1304
|
+
console.log(pc2.dim('No cached tenants. Run "01 login" first.'));
|
|
1085
1305
|
return;
|
|
1086
1306
|
}
|
|
1087
1307
|
const localCreds = loadLocalCredentials();
|
|
@@ -1089,30 +1309,30 @@ ${loginUrl}`));
|
|
|
1089
1309
|
const activeCreds = localCreds || globalCreds;
|
|
1090
1310
|
const activeId = activeCreds?.tenantId;
|
|
1091
1311
|
const activeName = activeCreds?.tenantName;
|
|
1092
|
-
console.log(
|
|
1093
|
-
for (const
|
|
1094
|
-
const active = (activeId ?
|
|
1095
|
-
console.log(` ${
|
|
1312
|
+
console.log(pc2.bold("Cached tenants:\n"));
|
|
1313
|
+
for (const t2 of tenants) {
|
|
1314
|
+
const active = (activeId ? t2.id === activeId : t2.name === activeName) ? pc2.green(" *") : "";
|
|
1315
|
+
console.log(` ${t2.name}${active}`);
|
|
1096
1316
|
}
|
|
1097
1317
|
console.log();
|
|
1098
1318
|
if (activeName) {
|
|
1099
1319
|
const scope = localCreds ? "(local)" : "(global)";
|
|
1100
|
-
console.log(
|
|
1320
|
+
console.log(pc2.dim(`* active ${scope}`));
|
|
1101
1321
|
}
|
|
1102
1322
|
});
|
|
1103
1323
|
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) => {
|
|
1104
1324
|
const tenants = loadTenantList();
|
|
1105
1325
|
if (!tenants || tenants.length === 0) {
|
|
1106
|
-
console.error(
|
|
1326
|
+
console.error(pc2.red('No cached tenants. Run "01 login" first.'));
|
|
1107
1327
|
process.exit(2);
|
|
1108
1328
|
}
|
|
1109
1329
|
const match = tenants.find(
|
|
1110
|
-
(
|
|
1330
|
+
(t2) => t2.name.toLowerCase() === name.toLowerCase()
|
|
1111
1331
|
);
|
|
1112
1332
|
if (!match) {
|
|
1113
|
-
console.error(
|
|
1333
|
+
console.error(pc2.red(`Tenant "${name}" not found in cache.`));
|
|
1114
1334
|
console.error(
|
|
1115
|
-
|
|
1335
|
+
pc2.dim(`Available: ${tenants.map((t2) => t2.name).join(", ")}`)
|
|
1116
1336
|
);
|
|
1117
1337
|
process.exit(3);
|
|
1118
1338
|
}
|
|
@@ -1125,12 +1345,12 @@ ${loginUrl}`));
|
|
|
1125
1345
|
if (isLocal) {
|
|
1126
1346
|
saveLocalCredentials(creds);
|
|
1127
1347
|
console.log(
|
|
1128
|
-
|
|
1348
|
+
pc2.dim(`Credentials saved to ${getLocalCredentialsPath()}`)
|
|
1129
1349
|
);
|
|
1130
1350
|
} else {
|
|
1131
1351
|
saveCredentials(creds);
|
|
1132
1352
|
console.log(
|
|
1133
|
-
|
|
1353
|
+
pc2.dim(`Credentials saved to ${getCredentialsPath()}`)
|
|
1134
1354
|
);
|
|
1135
1355
|
}
|
|
1136
1356
|
}
|
|
@@ -1141,13 +1361,13 @@ ${loginUrl}`));
|
|
|
1141
1361
|
tenantId: match.id
|
|
1142
1362
|
});
|
|
1143
1363
|
const loginUrl = `${WEB_URL}/cli-auth?${params.toString()}`;
|
|
1144
|
-
console.log(
|
|
1145
|
-
console.log(
|
|
1364
|
+
console.log(pc2.dim(`Switching to tenant "${match.name}"...`));
|
|
1365
|
+
console.log(pc2.dim(`If the browser does not open, visit:
|
|
1146
1366
|
${loginUrl}`));
|
|
1147
1367
|
openBrowser(loginUrl);
|
|
1148
1368
|
} catch (err) {
|
|
1149
1369
|
console.error(
|
|
1150
|
-
|
|
1370
|
+
pc2.red(
|
|
1151
1371
|
`Server error: ${err instanceof Error ? err.message : String(err)}`
|
|
1152
1372
|
)
|
|
1153
1373
|
);
|
|
@@ -1246,12 +1466,17 @@ function registerMcpCommands(program2) {
|
|
|
1246
1466
|
// src/index.ts
|
|
1247
1467
|
var require2 = createRequire(import.meta.url);
|
|
1248
1468
|
var { version } = require2("../package.json");
|
|
1249
|
-
process.on("unhandledRejection", (err) => {
|
|
1250
|
-
exitWithError(err);
|
|
1251
|
-
});
|
|
1252
1469
|
var program = new Command();
|
|
1253
|
-
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");
|
|
1470
|
+
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)");
|
|
1254
1471
|
var getFormat = () => program.opts().format ?? process.env.OUTPUT_FORMAT ?? "json";
|
|
1472
|
+
program.hook("preAction", () => {
|
|
1473
|
+
const lang = program.opts().lang;
|
|
1474
|
+
if (lang === "ko" || lang === "en") setLocale(lang);
|
|
1475
|
+
process.env.OUTPUT_FORMAT = getFormat();
|
|
1476
|
+
});
|
|
1477
|
+
process.on("unhandledRejection", (err) => {
|
|
1478
|
+
exitWithError(err, { format: getFormat() });
|
|
1479
|
+
});
|
|
1255
1480
|
var getClient = () => resolveClient(program.opts().apiKey);
|
|
1256
1481
|
registerCrudCommands(program, getClient, getFormat);
|
|
1257
1482
|
registerOrderCommands(program, getClient, getFormat);
|