numo-cli 1.3.0 → 1.5.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/cli.cjs +374 -337
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -3217,6 +3217,336 @@ var init_http = __esm({
|
|
|
3217
3217
|
}
|
|
3218
3218
|
});
|
|
3219
3219
|
|
|
3220
|
+
// src/cli/lib/dirs.ts
|
|
3221
|
+
function getConfigDir() {
|
|
3222
|
+
if (process.env.NUMO_CONFIG_DIR) {
|
|
3223
|
+
return process.env.NUMO_CONFIG_DIR;
|
|
3224
|
+
}
|
|
3225
|
+
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
3226
|
+
const xdgDir = path.join(xdgHome, "numo");
|
|
3227
|
+
if (fs.existsSync(xdgDir)) return xdgDir;
|
|
3228
|
+
if (fs.existsSync(LEGACY_DIR)) return LEGACY_DIR;
|
|
3229
|
+
return xdgDir;
|
|
3230
|
+
}
|
|
3231
|
+
function ensureConfigDir() {
|
|
3232
|
+
const dir = getConfigDir();
|
|
3233
|
+
if (!fs.existsSync(dir)) {
|
|
3234
|
+
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
3235
|
+
}
|
|
3236
|
+
return dir;
|
|
3237
|
+
}
|
|
3238
|
+
function getCredentialsPath() {
|
|
3239
|
+
return path.join(getConfigDir(), "credentials.json");
|
|
3240
|
+
}
|
|
3241
|
+
function migrateIfNeeded() {
|
|
3242
|
+
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
3243
|
+
const xdgDir = path.join(xdgHome, "numo");
|
|
3244
|
+
if (process.env.NUMO_CONFIG_DIR) return;
|
|
3245
|
+
const legacyCreds = path.join(LEGACY_DIR, "credentials.json");
|
|
3246
|
+
if (!fs.existsSync(legacyCreds) || fs.existsSync(xdgDir)) return;
|
|
3247
|
+
try {
|
|
3248
|
+
fs.mkdirSync(xdgDir, { recursive: true, mode: 448 });
|
|
3249
|
+
const data = fs.readFileSync(legacyCreds, "utf8");
|
|
3250
|
+
fs.writeFileSync(path.join(xdgDir, "credentials.json"), data, { mode: 384 });
|
|
3251
|
+
const legacyStreaks = path.join(LEGACY_DIR, "streaks.json");
|
|
3252
|
+
if (fs.existsSync(legacyStreaks)) {
|
|
3253
|
+
const streaksData = fs.readFileSync(legacyStreaks, "utf8");
|
|
3254
|
+
fs.writeFileSync(path.join(xdgDir, "streaks.json"), streaksData, { mode: 384 });
|
|
3255
|
+
}
|
|
3256
|
+
process.stderr.write(`Migrated config from ${LEGACY_DIR} to ${xdgDir}
|
|
3257
|
+
`);
|
|
3258
|
+
} catch {
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
var fs, path, os, LEGACY_DIR;
|
|
3262
|
+
var init_dirs = __esm({
|
|
3263
|
+
"src/cli/lib/dirs.ts"() {
|
|
3264
|
+
"use strict";
|
|
3265
|
+
fs = __toESM(require("fs"), 1);
|
|
3266
|
+
path = __toESM(require("path"), 1);
|
|
3267
|
+
os = __toESM(require("os"), 1);
|
|
3268
|
+
LEGACY_DIR = path.join(os.homedir(), ".numo");
|
|
3269
|
+
}
|
|
3270
|
+
});
|
|
3271
|
+
|
|
3272
|
+
// src/cli/lib/errors.ts
|
|
3273
|
+
function classifyError(err) {
|
|
3274
|
+
if (err instanceof CliError) return err;
|
|
3275
|
+
const axiosErr = err;
|
|
3276
|
+
if (axiosErr.code === "ECONNABORTED" || axiosErr.code === "ETIMEDOUT") return Errors.timeout();
|
|
3277
|
+
if (axiosErr.code === "ENOTFOUND" || axiosErr.code === "EAI_AGAIN") return Errors.networkError();
|
|
3278
|
+
if (axiosErr.code === "ECONNREFUSED" || axiosErr.code === "ECONNRESET") {
|
|
3279
|
+
return Errors.networkError("Service may be temporarily down. Try again in a moment.");
|
|
3280
|
+
}
|
|
3281
|
+
const status = axiosErr.response?.status;
|
|
3282
|
+
if (status === 401) return Errors.authRequired();
|
|
3283
|
+
if (status === 403) {
|
|
3284
|
+
return new CliError("AUTH_FORBIDDEN" /* AUTH_FORBIDDEN */, "Access denied", ExitCode.NO_PERM, {
|
|
3285
|
+
hint: "You don't have permission for this action."
|
|
3286
|
+
});
|
|
3287
|
+
}
|
|
3288
|
+
if (status === 404) return Errors.notFound("Resource");
|
|
3289
|
+
if (status === 429) {
|
|
3290
|
+
const retryAfter = parseInt(axiosErr.response?.headers?.["retry-after"] ?? "");
|
|
3291
|
+
return Errors.rateLimited(isNaN(retryAfter) ? void 0 : retryAfter);
|
|
3292
|
+
}
|
|
3293
|
+
if (status && status >= 500) {
|
|
3294
|
+
return new CliError("SERVICE_UNAVAILABLE" /* SERVICE_UNAVAILABLE */, "Server error", ExitCode.UNAVAILABLE, {
|
|
3295
|
+
hint: "This is on our end. Try again in a moment.",
|
|
3296
|
+
retryable: true
|
|
3297
|
+
});
|
|
3298
|
+
}
|
|
3299
|
+
const body = axiosErr.response?.data;
|
|
3300
|
+
const raw = body?.error?.message ?? axiosErr.message ?? "Unknown error";
|
|
3301
|
+
const message = sanitizeErrorMessage(raw);
|
|
3302
|
+
return new CliError("UNKNOWN" /* UNKNOWN */, message, ExitCode.GENERAL, { cause: err });
|
|
3303
|
+
}
|
|
3304
|
+
function sanitizeErrorMessage(msg) {
|
|
3305
|
+
return msg.replace(/https?:\/\/\S+/g, "<url>").replace(/\/(?:Users|home|var|tmp)\/\S+/g, "<path>").replace(/[A-Za-z0-9_-]{40,}/g, "<token>");
|
|
3306
|
+
}
|
|
3307
|
+
var ExitCode, CliError, Errors;
|
|
3308
|
+
var init_errors = __esm({
|
|
3309
|
+
"src/cli/lib/errors.ts"() {
|
|
3310
|
+
"use strict";
|
|
3311
|
+
ExitCode = {
|
|
3312
|
+
OK: 0,
|
|
3313
|
+
GENERAL: 1,
|
|
3314
|
+
USAGE: 2,
|
|
3315
|
+
UNAVAILABLE: 69,
|
|
3316
|
+
TEMP_FAIL: 75,
|
|
3317
|
+
NO_PERM: 77,
|
|
3318
|
+
CONFIG: 78,
|
|
3319
|
+
NOT_FOUND: 100,
|
|
3320
|
+
CONFLICT: 101
|
|
3321
|
+
};
|
|
3322
|
+
CliError = class extends Error {
|
|
3323
|
+
constructor(kind, message, exitCode = ExitCode.GENERAL, options = {}) {
|
|
3324
|
+
super(message);
|
|
3325
|
+
this.kind = kind;
|
|
3326
|
+
this.exitCode = exitCode;
|
|
3327
|
+
this.options = options;
|
|
3328
|
+
this.name = "CliError";
|
|
3329
|
+
}
|
|
3330
|
+
toJSON() {
|
|
3331
|
+
return {
|
|
3332
|
+
error: {
|
|
3333
|
+
kind: this.kind,
|
|
3334
|
+
code: this.exitCode,
|
|
3335
|
+
message: this.message,
|
|
3336
|
+
...this.options.suggestion && { suggestion: this.options.suggestion },
|
|
3337
|
+
...this.options.hint && { hint: this.options.hint },
|
|
3338
|
+
retryable: this.options.retryable ?? false,
|
|
3339
|
+
...this.options.retryAfter != null && { retryAfter: this.options.retryAfter }
|
|
3340
|
+
}
|
|
3341
|
+
};
|
|
3342
|
+
}
|
|
3343
|
+
};
|
|
3344
|
+
Errors = {
|
|
3345
|
+
authRequired: () => new CliError("AUTH_REQUIRED" /* AUTH_REQUIRED */, "Not logged in", ExitCode.NO_PERM, {
|
|
3346
|
+
suggestion: "numo login"
|
|
3347
|
+
}),
|
|
3348
|
+
notFound: (resource, id) => new CliError("NOT_FOUND" /* NOT_FOUND */, `${resource} not found${id ? `: ${id}` : ""}`, ExitCode.NOT_FOUND, {
|
|
3349
|
+
suggestion: `numo ${resource.toLowerCase()}s list`
|
|
3350
|
+
}),
|
|
3351
|
+
missingArg: (name, flag) => new CliError("MISSING_ARGUMENT" /* MISSING_ARGUMENT */, `${name} is required`, ExitCode.USAGE, {
|
|
3352
|
+
suggestion: `Use --${flag}`,
|
|
3353
|
+
hint: "Run with --help for all options."
|
|
3354
|
+
}),
|
|
3355
|
+
invalidInput: (message, hint) => new CliError("INVALID_INPUT" /* INVALID_INPUT */, message, ExitCode.USAGE, { hint }),
|
|
3356
|
+
configMissing: (key) => new CliError("CONFIG_ERROR" /* CONFIG_ERROR */, `${key} not set`, ExitCode.CONFIG, {
|
|
3357
|
+
suggestion: `export ${key}=<value>`
|
|
3358
|
+
}),
|
|
3359
|
+
networkError: (hint) => new CliError("NETWORK_ERROR" /* NETWORK_ERROR */, "Can't reach Numo servers", ExitCode.UNAVAILABLE, {
|
|
3360
|
+
hint: hint ?? "Check your internet connection.",
|
|
3361
|
+
retryable: true
|
|
3362
|
+
}),
|
|
3363
|
+
timeout: () => new CliError("TIMEOUT" /* TIMEOUT */, "Request timed out", ExitCode.TEMP_FAIL, {
|
|
3364
|
+
hint: "The server took too long to respond. Try again.",
|
|
3365
|
+
retryable: true
|
|
3366
|
+
}),
|
|
3367
|
+
rateLimited: (retryAfter) => new CliError("RATE_LIMITED" /* RATE_LIMITED */, "Too many requests", ExitCode.TEMP_FAIL, {
|
|
3368
|
+
hint: retryAfter ? `Wait ${retryAfter} seconds and try again.` : "Wait a moment and try again.",
|
|
3369
|
+
retryable: true,
|
|
3370
|
+
retryAfter
|
|
3371
|
+
})
|
|
3372
|
+
};
|
|
3373
|
+
}
|
|
3374
|
+
});
|
|
3375
|
+
|
|
3376
|
+
// src/cli/lib/api-client.ts
|
|
3377
|
+
var api_client_exports = {};
|
|
3378
|
+
__export(api_client_exports, {
|
|
3379
|
+
API_BASE: () => API_BASE,
|
|
3380
|
+
api: () => api
|
|
3381
|
+
});
|
|
3382
|
+
function toCliError(err) {
|
|
3383
|
+
if (err instanceof CliError) return err;
|
|
3384
|
+
const httpErr = err;
|
|
3385
|
+
if (httpErr.code === "ECONNABORTED" || httpErr.code === "ETIMEDOUT") {
|
|
3386
|
+
return new CliError("TIMEOUT" /* TIMEOUT */, "Request timed out", ExitCode.TEMP_FAIL, {
|
|
3387
|
+
hint: "The API server took too long to respond.",
|
|
3388
|
+
retryable: true
|
|
3389
|
+
});
|
|
3390
|
+
}
|
|
3391
|
+
if (httpErr.code === "ECONNREFUSED" || httpErr.code === "ECONNRESET" || httpErr.code === "ENOTFOUND") {
|
|
3392
|
+
return new CliError("NETWORK_ERROR" /* NETWORK_ERROR */, "Can't reach Numo API", ExitCode.UNAVAILABLE, {
|
|
3393
|
+
hint: "Is the API server running? Check NUMO_API_URL.",
|
|
3394
|
+
retryable: true
|
|
3395
|
+
});
|
|
3396
|
+
}
|
|
3397
|
+
const body = httpErr.response?.data;
|
|
3398
|
+
if (body?.error) {
|
|
3399
|
+
const e = body.error;
|
|
3400
|
+
const kind = e.kind ?? "UNKNOWN" /* UNKNOWN */;
|
|
3401
|
+
const exitCode = KIND_EXIT[kind] ?? ExitCode.GENERAL;
|
|
3402
|
+
return new CliError(kind, e.message ?? "Unknown error", exitCode, {
|
|
3403
|
+
retryable: e.retryable,
|
|
3404
|
+
retryAfter: e.retryAfter
|
|
3405
|
+
});
|
|
3406
|
+
}
|
|
3407
|
+
return new CliError("UNKNOWN" /* UNKNOWN */, httpErr.message ?? "Unknown error", ExitCode.GENERAL);
|
|
3408
|
+
}
|
|
3409
|
+
async function apiHeaders() {
|
|
3410
|
+
const token = await getIdToken();
|
|
3411
|
+
return {
|
|
3412
|
+
Authorization: `Bearer ${token}`,
|
|
3413
|
+
"Content-Type": "application/json"
|
|
3414
|
+
};
|
|
3415
|
+
}
|
|
3416
|
+
function url(path3, params) {
|
|
3417
|
+
const u2 = `${API_BASE}${path3}`;
|
|
3418
|
+
if (!params) return u2;
|
|
3419
|
+
const sp = new URLSearchParams();
|
|
3420
|
+
for (const [k2, v] of Object.entries(params)) {
|
|
3421
|
+
if (v !== void 0) sp.set(k2, v);
|
|
3422
|
+
}
|
|
3423
|
+
const qs = sp.toString();
|
|
3424
|
+
return qs ? `${u2}?${qs}` : u2;
|
|
3425
|
+
}
|
|
3426
|
+
var API_BASE, KIND_EXIT, api;
|
|
3427
|
+
var init_api_client = __esm({
|
|
3428
|
+
"src/cli/lib/api-client.ts"() {
|
|
3429
|
+
"use strict";
|
|
3430
|
+
init_credentials();
|
|
3431
|
+
init_http();
|
|
3432
|
+
init_errors();
|
|
3433
|
+
API_BASE = process.env.NUMO_API_URL ?? (true ? "https://api.numo.ai" : "http://localhost:3000");
|
|
3434
|
+
if (API_BASE !== "http://localhost:3000" && API_BASE.startsWith("http://")) {
|
|
3435
|
+
process.stderr.write("[warn] NUMO_API_URL uses HTTP \u2014 tokens sent unencrypted. Use HTTPS in production.\n");
|
|
3436
|
+
}
|
|
3437
|
+
KIND_EXIT = {
|
|
3438
|
+
AUTH_REQUIRED: ExitCode.NO_PERM,
|
|
3439
|
+
AUTH_EXPIRED: ExitCode.NO_PERM,
|
|
3440
|
+
AUTH_FORBIDDEN: ExitCode.NO_PERM,
|
|
3441
|
+
INVALID_INPUT: ExitCode.USAGE,
|
|
3442
|
+
MISSING_ARGUMENT: ExitCode.USAGE,
|
|
3443
|
+
NOT_FOUND: ExitCode.NOT_FOUND,
|
|
3444
|
+
CONFLICT: ExitCode.CONFLICT,
|
|
3445
|
+
RATE_LIMITED: ExitCode.TEMP_FAIL,
|
|
3446
|
+
NETWORK_ERROR: ExitCode.UNAVAILABLE,
|
|
3447
|
+
TIMEOUT: ExitCode.TEMP_FAIL,
|
|
3448
|
+
SERVICE_UNAVAILABLE: ExitCode.UNAVAILABLE
|
|
3449
|
+
};
|
|
3450
|
+
api = {
|
|
3451
|
+
async get(path3, params) {
|
|
3452
|
+
try {
|
|
3453
|
+
const resp = await http.get(url(path3, params), { headers: await apiHeaders() });
|
|
3454
|
+
return resp.data;
|
|
3455
|
+
} catch (err) {
|
|
3456
|
+
throw toCliError(err);
|
|
3457
|
+
}
|
|
3458
|
+
},
|
|
3459
|
+
async post(path3, body) {
|
|
3460
|
+
try {
|
|
3461
|
+
const resp = await http.post(url(path3), body, { headers: await apiHeaders() });
|
|
3462
|
+
return resp.data;
|
|
3463
|
+
} catch (err) {
|
|
3464
|
+
throw toCliError(err);
|
|
3465
|
+
}
|
|
3466
|
+
},
|
|
3467
|
+
async patch(path3, body) {
|
|
3468
|
+
try {
|
|
3469
|
+
const resp = await http.patch(url(path3), body, { headers: await apiHeaders() });
|
|
3470
|
+
return resp.data;
|
|
3471
|
+
} catch (err) {
|
|
3472
|
+
throw toCliError(err);
|
|
3473
|
+
}
|
|
3474
|
+
},
|
|
3475
|
+
async del(path3) {
|
|
3476
|
+
try {
|
|
3477
|
+
const resp = await http.delete(url(path3), { headers: await apiHeaders() });
|
|
3478
|
+
return resp.data;
|
|
3479
|
+
} catch (err) {
|
|
3480
|
+
throw toCliError(err);
|
|
3481
|
+
}
|
|
3482
|
+
}
|
|
3483
|
+
};
|
|
3484
|
+
}
|
|
3485
|
+
});
|
|
3486
|
+
|
|
3487
|
+
// src/cli/auth/credentials.ts
|
|
3488
|
+
function loadCredentials() {
|
|
3489
|
+
try {
|
|
3490
|
+
const data = JSON.parse(fs2.readFileSync(getCredentialsPath(), "utf8"));
|
|
3491
|
+
if (typeof data?.refreshToken !== "string" || typeof data?.uid !== "string" || typeof data?.email !== "string") {
|
|
3492
|
+
return null;
|
|
3493
|
+
}
|
|
3494
|
+
return data;
|
|
3495
|
+
} catch {
|
|
3496
|
+
return null;
|
|
3497
|
+
}
|
|
3498
|
+
}
|
|
3499
|
+
function saveCredentials(creds) {
|
|
3500
|
+
ensureConfigDir();
|
|
3501
|
+
fs2.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
3502
|
+
}
|
|
3503
|
+
function clearCredentials() {
|
|
3504
|
+
try {
|
|
3505
|
+
const credPath = getCredentialsPath();
|
|
3506
|
+
const stat = fs2.statSync(credPath);
|
|
3507
|
+
fs2.writeFileSync(credPath, crypto.randomBytes(stat.size));
|
|
3508
|
+
fs2.unlinkSync(credPath);
|
|
3509
|
+
} catch {
|
|
3510
|
+
}
|
|
3511
|
+
}
|
|
3512
|
+
async function getIdToken() {
|
|
3513
|
+
const envToken = process.env.NUMO_TOKEN;
|
|
3514
|
+
if (envToken) return envToken;
|
|
3515
|
+
const creds = loadCredentials();
|
|
3516
|
+
if (!creds) throw new Error("Not logged in. Run: numo login");
|
|
3517
|
+
if (creds.idToken && creds.idTokenExpiry && Date.now() < creds.idTokenExpiry - 6e4) {
|
|
3518
|
+
return creds.idToken;
|
|
3519
|
+
}
|
|
3520
|
+
if (refreshInFlight) return refreshInFlight;
|
|
3521
|
+
refreshInFlight = performRefresh(creds).finally(() => {
|
|
3522
|
+
refreshInFlight = null;
|
|
3523
|
+
});
|
|
3524
|
+
return refreshInFlight;
|
|
3525
|
+
}
|
|
3526
|
+
async function performRefresh(creds) {
|
|
3527
|
+
const { API_BASE: apiBase } = await Promise.resolve().then(() => (init_api_client(), api_client_exports));
|
|
3528
|
+
const { http: http2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
3529
|
+
const resp = await http2.post(
|
|
3530
|
+
`${apiBase}/api/auth/refresh`,
|
|
3531
|
+
{ refreshToken: creds.refreshToken }
|
|
3532
|
+
);
|
|
3533
|
+
creds.idToken = resp.data.idToken;
|
|
3534
|
+
creds.refreshToken = resp.data.refreshToken ?? creds.refreshToken;
|
|
3535
|
+
creds.idTokenExpiry = Date.now() + (resp.data.expiresIn || 3600) * 1e3;
|
|
3536
|
+
saveCredentials(creds);
|
|
3537
|
+
return creds.idToken;
|
|
3538
|
+
}
|
|
3539
|
+
var fs2, crypto, refreshInFlight;
|
|
3540
|
+
var init_credentials = __esm({
|
|
3541
|
+
"src/cli/auth/credentials.ts"() {
|
|
3542
|
+
"use strict";
|
|
3543
|
+
fs2 = __toESM(require("fs"), 1);
|
|
3544
|
+
crypto = __toESM(require("crypto"), 1);
|
|
3545
|
+
init_dirs();
|
|
3546
|
+
refreshInFlight = null;
|
|
3547
|
+
}
|
|
3548
|
+
});
|
|
3549
|
+
|
|
3220
3550
|
// src/cli/lib/tty.ts
|
|
3221
3551
|
function isInteractive() {
|
|
3222
3552
|
if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
|
|
@@ -4993,132 +5323,28 @@ async function promptMultiSelect(opts) {
|
|
|
4993
5323
|
const p = await loadClack();
|
|
4994
5324
|
const value = await p.multiselect({
|
|
4995
5325
|
message: opts.message,
|
|
4996
|
-
options: opts.options,
|
|
4997
|
-
required: opts.required ?? false
|
|
4998
|
-
});
|
|
4999
|
-
if (p.isCancel(value)) {
|
|
5000
|
-
process.exit(130);
|
|
5001
|
-
}
|
|
5002
|
-
return value;
|
|
5003
|
-
}
|
|
5004
|
-
async function promptForMissing(opts) {
|
|
5005
|
-
if (opts.value !== void 0 && opts.value !== "") {
|
|
5006
|
-
return opts.value;
|
|
5007
|
-
}
|
|
5008
|
-
return promptText({
|
|
5009
|
-
message: opts.message,
|
|
5010
|
-
placeholder: opts.placeholder,
|
|
5011
|
-
required: opts.required ?? true
|
|
5012
|
-
});
|
|
5013
|
-
}
|
|
5014
|
-
var init_prompts = __esm({
|
|
5015
|
-
"src/cli/lib/prompts.ts"() {
|
|
5016
|
-
"use strict";
|
|
5017
|
-
init_tty();
|
|
5018
|
-
}
|
|
5019
|
-
});
|
|
5020
|
-
|
|
5021
|
-
// src/cli/lib/errors.ts
|
|
5022
|
-
function classifyError(err) {
|
|
5023
|
-
if (err instanceof CliError) return err;
|
|
5024
|
-
const axiosErr = err;
|
|
5025
|
-
if (axiosErr.code === "ECONNABORTED" || axiosErr.code === "ETIMEDOUT") return Errors.timeout();
|
|
5026
|
-
if (axiosErr.code === "ENOTFOUND" || axiosErr.code === "EAI_AGAIN") return Errors.networkError();
|
|
5027
|
-
if (axiosErr.code === "ECONNREFUSED" || axiosErr.code === "ECONNRESET") {
|
|
5028
|
-
return Errors.networkError("Service may be temporarily down. Try again in a moment.");
|
|
5029
|
-
}
|
|
5030
|
-
const status = axiosErr.response?.status;
|
|
5031
|
-
if (status === 401) return Errors.authRequired();
|
|
5032
|
-
if (status === 403) {
|
|
5033
|
-
return new CliError("AUTH_FORBIDDEN" /* AUTH_FORBIDDEN */, "Access denied", ExitCode.NO_PERM, {
|
|
5034
|
-
hint: "You don't have permission for this action."
|
|
5035
|
-
});
|
|
5036
|
-
}
|
|
5037
|
-
if (status === 404) return Errors.notFound("Resource");
|
|
5038
|
-
if (status === 429) {
|
|
5039
|
-
const retryAfter = parseInt(axiosErr.response?.headers?.["retry-after"] ?? "");
|
|
5040
|
-
return Errors.rateLimited(isNaN(retryAfter) ? void 0 : retryAfter);
|
|
5041
|
-
}
|
|
5042
|
-
if (status && status >= 500) {
|
|
5043
|
-
return new CliError("SERVICE_UNAVAILABLE" /* SERVICE_UNAVAILABLE */, "Server error", ExitCode.UNAVAILABLE, {
|
|
5044
|
-
hint: "This is on our end. Try again in a moment.",
|
|
5045
|
-
retryable: true
|
|
5046
|
-
});
|
|
5326
|
+
options: opts.options,
|
|
5327
|
+
required: opts.required ?? false
|
|
5328
|
+
});
|
|
5329
|
+
if (p.isCancel(value)) {
|
|
5330
|
+
process.exit(130);
|
|
5047
5331
|
}
|
|
5048
|
-
|
|
5049
|
-
const raw = body?.error?.message ?? axiosErr.message ?? "Unknown error";
|
|
5050
|
-
const message = sanitizeErrorMessage(raw);
|
|
5051
|
-
return new CliError("UNKNOWN" /* UNKNOWN */, message, ExitCode.GENERAL, { cause: err });
|
|
5332
|
+
return value;
|
|
5052
5333
|
}
|
|
5053
|
-
function
|
|
5054
|
-
|
|
5334
|
+
async function promptForMissing(opts) {
|
|
5335
|
+
if (opts.value !== void 0 && opts.value !== "") {
|
|
5336
|
+
return opts.value;
|
|
5337
|
+
}
|
|
5338
|
+
return promptText({
|
|
5339
|
+
message: opts.message,
|
|
5340
|
+
placeholder: opts.placeholder,
|
|
5341
|
+
required: opts.required ?? true
|
|
5342
|
+
});
|
|
5055
5343
|
}
|
|
5056
|
-
var
|
|
5057
|
-
|
|
5058
|
-
"src/cli/lib/errors.ts"() {
|
|
5344
|
+
var init_prompts = __esm({
|
|
5345
|
+
"src/cli/lib/prompts.ts"() {
|
|
5059
5346
|
"use strict";
|
|
5060
|
-
|
|
5061
|
-
OK: 0,
|
|
5062
|
-
GENERAL: 1,
|
|
5063
|
-
USAGE: 2,
|
|
5064
|
-
UNAVAILABLE: 69,
|
|
5065
|
-
TEMP_FAIL: 75,
|
|
5066
|
-
NO_PERM: 77,
|
|
5067
|
-
CONFIG: 78,
|
|
5068
|
-
NOT_FOUND: 100,
|
|
5069
|
-
CONFLICT: 101
|
|
5070
|
-
};
|
|
5071
|
-
CliError = class extends Error {
|
|
5072
|
-
constructor(kind, message, exitCode = ExitCode.GENERAL, options = {}) {
|
|
5073
|
-
super(message);
|
|
5074
|
-
this.kind = kind;
|
|
5075
|
-
this.exitCode = exitCode;
|
|
5076
|
-
this.options = options;
|
|
5077
|
-
this.name = "CliError";
|
|
5078
|
-
}
|
|
5079
|
-
toJSON() {
|
|
5080
|
-
return {
|
|
5081
|
-
error: {
|
|
5082
|
-
kind: this.kind,
|
|
5083
|
-
code: this.exitCode,
|
|
5084
|
-
message: this.message,
|
|
5085
|
-
...this.options.suggestion && { suggestion: this.options.suggestion },
|
|
5086
|
-
...this.options.hint && { hint: this.options.hint },
|
|
5087
|
-
retryable: this.options.retryable ?? false,
|
|
5088
|
-
...this.options.retryAfter != null && { retryAfter: this.options.retryAfter }
|
|
5089
|
-
}
|
|
5090
|
-
};
|
|
5091
|
-
}
|
|
5092
|
-
};
|
|
5093
|
-
Errors = {
|
|
5094
|
-
authRequired: () => new CliError("AUTH_REQUIRED" /* AUTH_REQUIRED */, "Not logged in", ExitCode.NO_PERM, {
|
|
5095
|
-
suggestion: "numo login"
|
|
5096
|
-
}),
|
|
5097
|
-
notFound: (resource, id) => new CliError("NOT_FOUND" /* NOT_FOUND */, `${resource} not found${id ? `: ${id}` : ""}`, ExitCode.NOT_FOUND, {
|
|
5098
|
-
suggestion: `numo ${resource.toLowerCase()}s list`
|
|
5099
|
-
}),
|
|
5100
|
-
missingArg: (name, flag) => new CliError("MISSING_ARGUMENT" /* MISSING_ARGUMENT */, `${name} is required`, ExitCode.USAGE, {
|
|
5101
|
-
suggestion: `Use --${flag}`,
|
|
5102
|
-
hint: "Run with --help for all options."
|
|
5103
|
-
}),
|
|
5104
|
-
invalidInput: (message, hint) => new CliError("INVALID_INPUT" /* INVALID_INPUT */, message, ExitCode.USAGE, { hint }),
|
|
5105
|
-
configMissing: (key) => new CliError("CONFIG_ERROR" /* CONFIG_ERROR */, `${key} not set`, ExitCode.CONFIG, {
|
|
5106
|
-
suggestion: `export ${key}=<value>`
|
|
5107
|
-
}),
|
|
5108
|
-
networkError: (hint) => new CliError("NETWORK_ERROR" /* NETWORK_ERROR */, "Can't reach Numo servers", ExitCode.UNAVAILABLE, {
|
|
5109
|
-
hint: hint ?? "Check your internet connection.",
|
|
5110
|
-
retryable: true
|
|
5111
|
-
}),
|
|
5112
|
-
timeout: () => new CliError("TIMEOUT" /* TIMEOUT */, "Request timed out", ExitCode.TEMP_FAIL, {
|
|
5113
|
-
hint: "The server took too long to respond. Try again.",
|
|
5114
|
-
retryable: true
|
|
5115
|
-
}),
|
|
5116
|
-
rateLimited: (retryAfter) => new CliError("RATE_LIMITED" /* RATE_LIMITED */, "Too many requests", ExitCode.TEMP_FAIL, {
|
|
5117
|
-
hint: retryAfter ? `Wait ${retryAfter} seconds and try again.` : "Wait a moment and try again.",
|
|
5118
|
-
retryable: true,
|
|
5119
|
-
retryAfter
|
|
5120
|
-
})
|
|
5121
|
-
};
|
|
5347
|
+
init_tty();
|
|
5122
5348
|
}
|
|
5123
5349
|
});
|
|
5124
5350
|
|
|
@@ -5171,7 +5397,7 @@ async function authenticateWithPhone(spinner) {
|
|
|
5171
5397
|
}
|
|
5172
5398
|
throw Errors.networkError("Phone verification timed out. Try again.");
|
|
5173
5399
|
}
|
|
5174
|
-
var import_picocolors,
|
|
5400
|
+
var import_picocolors, POLL_INTERVAL, POLL_TIMEOUT;
|
|
5175
5401
|
var init_phone_login = __esm({
|
|
5176
5402
|
"src/cli/auth/phone-login.ts"() {
|
|
5177
5403
|
"use strict";
|
|
@@ -5179,7 +5405,7 @@ var init_phone_login = __esm({
|
|
|
5179
5405
|
import_picocolors = __toESM(require_picocolors(), 1);
|
|
5180
5406
|
init_errors();
|
|
5181
5407
|
init_prompts();
|
|
5182
|
-
|
|
5408
|
+
init_api_client();
|
|
5183
5409
|
POLL_INTERVAL = 2e3;
|
|
5184
5410
|
POLL_TIMEOUT = 5 * 60 * 1e3;
|
|
5185
5411
|
}
|
|
@@ -5208,121 +5434,16 @@ var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
|
5208
5434
|
// src/cli/auth/login.ts
|
|
5209
5435
|
init_http();
|
|
5210
5436
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
5211
|
-
|
|
5212
|
-
// src/cli/auth/credentials.ts
|
|
5213
|
-
var fs2 = __toESM(require("fs"), 1);
|
|
5214
|
-
var crypto = __toESM(require("crypto"), 1);
|
|
5215
|
-
|
|
5216
|
-
// src/cli/lib/dirs.ts
|
|
5217
|
-
var fs = __toESM(require("fs"), 1);
|
|
5218
|
-
var path = __toESM(require("path"), 1);
|
|
5219
|
-
var os = __toESM(require("os"), 1);
|
|
5220
|
-
var LEGACY_DIR = path.join(os.homedir(), ".numo");
|
|
5221
|
-
function getConfigDir() {
|
|
5222
|
-
if (process.env.NUMO_CONFIG_DIR) {
|
|
5223
|
-
return process.env.NUMO_CONFIG_DIR;
|
|
5224
|
-
}
|
|
5225
|
-
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
5226
|
-
const xdgDir = path.join(xdgHome, "numo");
|
|
5227
|
-
if (fs.existsSync(xdgDir)) return xdgDir;
|
|
5228
|
-
if (fs.existsSync(LEGACY_DIR)) return LEGACY_DIR;
|
|
5229
|
-
return xdgDir;
|
|
5230
|
-
}
|
|
5231
|
-
function ensureConfigDir() {
|
|
5232
|
-
const dir = getConfigDir();
|
|
5233
|
-
if (!fs.existsSync(dir)) {
|
|
5234
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
5235
|
-
}
|
|
5236
|
-
return dir;
|
|
5237
|
-
}
|
|
5238
|
-
function getCredentialsPath() {
|
|
5239
|
-
return path.join(getConfigDir(), "credentials.json");
|
|
5240
|
-
}
|
|
5241
|
-
function migrateIfNeeded() {
|
|
5242
|
-
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
5243
|
-
const xdgDir = path.join(xdgHome, "numo");
|
|
5244
|
-
if (process.env.NUMO_CONFIG_DIR) return;
|
|
5245
|
-
const legacyCreds = path.join(LEGACY_DIR, "credentials.json");
|
|
5246
|
-
if (!fs.existsSync(legacyCreds) || fs.existsSync(xdgDir)) return;
|
|
5247
|
-
try {
|
|
5248
|
-
fs.mkdirSync(xdgDir, { recursive: true, mode: 448 });
|
|
5249
|
-
const data = fs.readFileSync(legacyCreds, "utf8");
|
|
5250
|
-
fs.writeFileSync(path.join(xdgDir, "credentials.json"), data, { mode: 384 });
|
|
5251
|
-
const legacyStreaks = path.join(LEGACY_DIR, "streaks.json");
|
|
5252
|
-
if (fs.existsSync(legacyStreaks)) {
|
|
5253
|
-
const streaksData = fs.readFileSync(legacyStreaks, "utf8");
|
|
5254
|
-
fs.writeFileSync(path.join(xdgDir, "streaks.json"), streaksData, { mode: 384 });
|
|
5255
|
-
}
|
|
5256
|
-
process.stderr.write(`Migrated config from ${LEGACY_DIR} to ${xdgDir}
|
|
5257
|
-
`);
|
|
5258
|
-
} catch {
|
|
5259
|
-
}
|
|
5260
|
-
}
|
|
5261
|
-
|
|
5262
|
-
// src/cli/auth/credentials.ts
|
|
5263
|
-
function loadCredentials() {
|
|
5264
|
-
try {
|
|
5265
|
-
const data = JSON.parse(fs2.readFileSync(getCredentialsPath(), "utf8"));
|
|
5266
|
-
if (typeof data?.refreshToken !== "string" || typeof data?.uid !== "string" || typeof data?.email !== "string") {
|
|
5267
|
-
return null;
|
|
5268
|
-
}
|
|
5269
|
-
return data;
|
|
5270
|
-
} catch {
|
|
5271
|
-
return null;
|
|
5272
|
-
}
|
|
5273
|
-
}
|
|
5274
|
-
function saveCredentials(creds) {
|
|
5275
|
-
ensureConfigDir();
|
|
5276
|
-
fs2.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
5277
|
-
}
|
|
5278
|
-
function clearCredentials() {
|
|
5279
|
-
try {
|
|
5280
|
-
const credPath = getCredentialsPath();
|
|
5281
|
-
const stat = fs2.statSync(credPath);
|
|
5282
|
-
fs2.writeFileSync(credPath, crypto.randomBytes(stat.size));
|
|
5283
|
-
fs2.unlinkSync(credPath);
|
|
5284
|
-
} catch {
|
|
5285
|
-
}
|
|
5286
|
-
}
|
|
5287
|
-
var refreshInFlight = null;
|
|
5288
|
-
async function getIdToken() {
|
|
5289
|
-
const envToken = process.env.NUMO_TOKEN;
|
|
5290
|
-
if (envToken) return envToken;
|
|
5291
|
-
const creds = loadCredentials();
|
|
5292
|
-
if (!creds) throw new Error("Not logged in. Run: numo login");
|
|
5293
|
-
if (creds.idToken && creds.idTokenExpiry && Date.now() < creds.idTokenExpiry - 6e4) {
|
|
5294
|
-
return creds.idToken;
|
|
5295
|
-
}
|
|
5296
|
-
if (refreshInFlight) return refreshInFlight;
|
|
5297
|
-
refreshInFlight = performRefresh(creds).finally(() => {
|
|
5298
|
-
refreshInFlight = null;
|
|
5299
|
-
});
|
|
5300
|
-
return refreshInFlight;
|
|
5301
|
-
}
|
|
5302
|
-
async function performRefresh(creds) {
|
|
5303
|
-
const apiBase = process.env.NUMO_API_URL ?? "http://localhost:3000";
|
|
5304
|
-
const { http: http2 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
5305
|
-
const resp = await http2.post(
|
|
5306
|
-
`${apiBase}/api/auth/refresh`,
|
|
5307
|
-
{ refreshToken: creds.refreshToken }
|
|
5308
|
-
);
|
|
5309
|
-
creds.idToken = resp.data.idToken;
|
|
5310
|
-
creds.refreshToken = resp.data.refreshToken ?? creds.refreshToken;
|
|
5311
|
-
creds.idTokenExpiry = Date.now() + (resp.data.expiresIn || 3600) * 1e3;
|
|
5312
|
-
saveCredentials(creds);
|
|
5313
|
-
return creds.idToken;
|
|
5314
|
-
}
|
|
5315
|
-
|
|
5316
|
-
// src/cli/auth/login.ts
|
|
5437
|
+
init_credentials();
|
|
5317
5438
|
init_prompts();
|
|
5318
5439
|
init_errors();
|
|
5319
|
-
|
|
5440
|
+
init_api_client();
|
|
5320
5441
|
async function authenticateWithEmail(spinner) {
|
|
5321
5442
|
const email = await promptText({ message: "Email", required: true });
|
|
5322
5443
|
const password = await promptPassword({ message: "Password" });
|
|
5323
5444
|
spinner.start("Signing in...");
|
|
5324
5445
|
const resp = await http.post(
|
|
5325
|
-
`${
|
|
5446
|
+
`${API_BASE}/api/auth/login`,
|
|
5326
5447
|
{ email, password }
|
|
5327
5448
|
);
|
|
5328
5449
|
return {
|
|
@@ -5388,10 +5509,11 @@ async function login(options = {}) {
|
|
|
5388
5509
|
// src/cli/auth/register.ts
|
|
5389
5510
|
init_http();
|
|
5390
5511
|
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
5512
|
+
init_credentials();
|
|
5391
5513
|
init_prompts();
|
|
5392
5514
|
init_errors();
|
|
5393
5515
|
init_tty();
|
|
5394
|
-
|
|
5516
|
+
init_api_client();
|
|
5395
5517
|
function validateEmail(email) {
|
|
5396
5518
|
const trimmed = email.trim();
|
|
5397
5519
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
|
@@ -5426,7 +5548,7 @@ function classifySignUpError(err) {
|
|
|
5426
5548
|
}
|
|
5427
5549
|
async function signUp(email, password) {
|
|
5428
5550
|
try {
|
|
5429
|
-
const resp = await http.post(`${
|
|
5551
|
+
const resp = await http.post(`${API_BASE}/api/auth/register`, {
|
|
5430
5552
|
email,
|
|
5431
5553
|
password,
|
|
5432
5554
|
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
@@ -5482,6 +5604,9 @@ async function register(options = {}) {
|
|
|
5482
5604
|
}
|
|
5483
5605
|
}
|
|
5484
5606
|
|
|
5607
|
+
// src/cli/cli.ts
|
|
5608
|
+
init_credentials();
|
|
5609
|
+
|
|
5485
5610
|
// src/cli/commands/tasks.ts
|
|
5486
5611
|
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
5487
5612
|
|
|
@@ -5864,6 +5989,7 @@ async function runDelete(opts) {
|
|
|
5864
5989
|
}
|
|
5865
5990
|
|
|
5866
5991
|
// src/cli/lib/uid.ts
|
|
5992
|
+
init_credentials();
|
|
5867
5993
|
init_errors();
|
|
5868
5994
|
function requireUid() {
|
|
5869
5995
|
const creds = loadCredentials();
|
|
@@ -5871,106 +5997,8 @@ function requireUid() {
|
|
|
5871
5997
|
return creds.uid;
|
|
5872
5998
|
}
|
|
5873
5999
|
|
|
5874
|
-
// src/cli/lib/api-client.ts
|
|
5875
|
-
init_http();
|
|
5876
|
-
init_errors();
|
|
5877
|
-
var API_BASE4 = process.env.NUMO_API_URL ?? "http://localhost:3000";
|
|
5878
|
-
if (API_BASE4 !== "http://localhost:3000" && API_BASE4.startsWith("http://")) {
|
|
5879
|
-
process.stderr.write("[warn] NUMO_API_URL uses HTTP \u2014 tokens sent unencrypted. Use HTTPS in production.\n");
|
|
5880
|
-
}
|
|
5881
|
-
var KIND_EXIT = {
|
|
5882
|
-
AUTH_REQUIRED: ExitCode.NO_PERM,
|
|
5883
|
-
AUTH_EXPIRED: ExitCode.NO_PERM,
|
|
5884
|
-
AUTH_FORBIDDEN: ExitCode.NO_PERM,
|
|
5885
|
-
INVALID_INPUT: ExitCode.USAGE,
|
|
5886
|
-
MISSING_ARGUMENT: ExitCode.USAGE,
|
|
5887
|
-
NOT_FOUND: ExitCode.NOT_FOUND,
|
|
5888
|
-
CONFLICT: ExitCode.CONFLICT,
|
|
5889
|
-
RATE_LIMITED: ExitCode.TEMP_FAIL,
|
|
5890
|
-
NETWORK_ERROR: ExitCode.UNAVAILABLE,
|
|
5891
|
-
TIMEOUT: ExitCode.TEMP_FAIL,
|
|
5892
|
-
SERVICE_UNAVAILABLE: ExitCode.UNAVAILABLE
|
|
5893
|
-
};
|
|
5894
|
-
function toCliError(err) {
|
|
5895
|
-
if (err instanceof CliError) return err;
|
|
5896
|
-
const httpErr = err;
|
|
5897
|
-
if (httpErr.code === "ECONNABORTED" || httpErr.code === "ETIMEDOUT") {
|
|
5898
|
-
return new CliError("TIMEOUT" /* TIMEOUT */, "Request timed out", ExitCode.TEMP_FAIL, {
|
|
5899
|
-
hint: "The API server took too long to respond.",
|
|
5900
|
-
retryable: true
|
|
5901
|
-
});
|
|
5902
|
-
}
|
|
5903
|
-
if (httpErr.code === "ECONNREFUSED" || httpErr.code === "ECONNRESET" || httpErr.code === "ENOTFOUND") {
|
|
5904
|
-
return new CliError("NETWORK_ERROR" /* NETWORK_ERROR */, "Can't reach Numo API", ExitCode.UNAVAILABLE, {
|
|
5905
|
-
hint: "Is the API server running? Check NUMO_API_URL.",
|
|
5906
|
-
retryable: true
|
|
5907
|
-
});
|
|
5908
|
-
}
|
|
5909
|
-
const body = httpErr.response?.data;
|
|
5910
|
-
if (body?.error) {
|
|
5911
|
-
const e = body.error;
|
|
5912
|
-
const kind = e.kind ?? "UNKNOWN" /* UNKNOWN */;
|
|
5913
|
-
const exitCode = KIND_EXIT[kind] ?? ExitCode.GENERAL;
|
|
5914
|
-
return new CliError(kind, e.message ?? "Unknown error", exitCode, {
|
|
5915
|
-
retryable: e.retryable,
|
|
5916
|
-
retryAfter: e.retryAfter
|
|
5917
|
-
});
|
|
5918
|
-
}
|
|
5919
|
-
return new CliError("UNKNOWN" /* UNKNOWN */, httpErr.message ?? "Unknown error", ExitCode.GENERAL);
|
|
5920
|
-
}
|
|
5921
|
-
async function apiHeaders() {
|
|
5922
|
-
const token = await getIdToken();
|
|
5923
|
-
return {
|
|
5924
|
-
Authorization: `Bearer ${token}`,
|
|
5925
|
-
"Content-Type": "application/json"
|
|
5926
|
-
};
|
|
5927
|
-
}
|
|
5928
|
-
function url(path3, params) {
|
|
5929
|
-
const u2 = `${API_BASE4}${path3}`;
|
|
5930
|
-
if (!params) return u2;
|
|
5931
|
-
const sp = new URLSearchParams();
|
|
5932
|
-
for (const [k2, v] of Object.entries(params)) {
|
|
5933
|
-
if (v !== void 0) sp.set(k2, v);
|
|
5934
|
-
}
|
|
5935
|
-
const qs = sp.toString();
|
|
5936
|
-
return qs ? `${u2}?${qs}` : u2;
|
|
5937
|
-
}
|
|
5938
|
-
var api = {
|
|
5939
|
-
async get(path3, params) {
|
|
5940
|
-
try {
|
|
5941
|
-
const resp = await http.get(url(path3, params), { headers: await apiHeaders() });
|
|
5942
|
-
return resp.data;
|
|
5943
|
-
} catch (err) {
|
|
5944
|
-
throw toCliError(err);
|
|
5945
|
-
}
|
|
5946
|
-
},
|
|
5947
|
-
async post(path3, body) {
|
|
5948
|
-
try {
|
|
5949
|
-
const resp = await http.post(url(path3), body, { headers: await apiHeaders() });
|
|
5950
|
-
return resp.data;
|
|
5951
|
-
} catch (err) {
|
|
5952
|
-
throw toCliError(err);
|
|
5953
|
-
}
|
|
5954
|
-
},
|
|
5955
|
-
async patch(path3, body) {
|
|
5956
|
-
try {
|
|
5957
|
-
const resp = await http.patch(url(path3), body, { headers: await apiHeaders() });
|
|
5958
|
-
return resp.data;
|
|
5959
|
-
} catch (err) {
|
|
5960
|
-
throw toCliError(err);
|
|
5961
|
-
}
|
|
5962
|
-
},
|
|
5963
|
-
async del(path3) {
|
|
5964
|
-
try {
|
|
5965
|
-
const resp = await http.delete(url(path3), { headers: await apiHeaders() });
|
|
5966
|
-
return resp.data;
|
|
5967
|
-
} catch (err) {
|
|
5968
|
-
throw toCliError(err);
|
|
5969
|
-
}
|
|
5970
|
-
}
|
|
5971
|
-
};
|
|
5972
|
-
|
|
5973
6000
|
// src/cli/services/tasks.ts
|
|
6001
|
+
init_api_client();
|
|
5974
6002
|
async function listTasks(uid, opts) {
|
|
5975
6003
|
return api.get("/api/tasks", {
|
|
5976
6004
|
date: opts.date,
|
|
@@ -9505,6 +9533,7 @@ ${import_picocolors9.default.dim("Next page:")} ${import_picocolors9.default.dim
|
|
|
9505
9533
|
}
|
|
9506
9534
|
|
|
9507
9535
|
// src/cli/services/posts.ts
|
|
9536
|
+
init_api_client();
|
|
9508
9537
|
async function listPosts(opts) {
|
|
9509
9538
|
return api.get("/api/posts", {
|
|
9510
9539
|
cursor: opts.cursor,
|
|
@@ -9525,6 +9554,7 @@ async function deletePost(uid, id) {
|
|
|
9525
9554
|
}
|
|
9526
9555
|
|
|
9527
9556
|
// src/cli/services/comments.ts
|
|
9557
|
+
init_api_client();
|
|
9528
9558
|
async function listComments(postId, opts) {
|
|
9529
9559
|
return api.get(`/api/posts/${encodeURIComponent(postId)}/comments`, {
|
|
9530
9560
|
cursor: opts.cursor,
|
|
@@ -9539,6 +9569,7 @@ async function deleteComment(uid, postId, commentId) {
|
|
|
9539
9569
|
}
|
|
9540
9570
|
|
|
9541
9571
|
// src/cli/services/replies.ts
|
|
9572
|
+
init_api_client();
|
|
9542
9573
|
async function listReplies(postId, commentId, opts) {
|
|
9543
9574
|
return api.get(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, {
|
|
9544
9575
|
cursor: opts.cursor,
|
|
@@ -9854,6 +9885,7 @@ Examples:
|
|
|
9854
9885
|
}
|
|
9855
9886
|
|
|
9856
9887
|
// src/cli/services/profile.ts
|
|
9888
|
+
init_api_client();
|
|
9857
9889
|
async function getProfile() {
|
|
9858
9890
|
return api.get("/api/profile");
|
|
9859
9891
|
}
|
|
@@ -9880,8 +9912,9 @@ function registerProfileCommands(program3) {
|
|
|
9880
9912
|
|
|
9881
9913
|
// src/cli/commands/doctor.ts
|
|
9882
9914
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
9915
|
+
init_credentials();
|
|
9916
|
+
init_api_client();
|
|
9883
9917
|
init_tty();
|
|
9884
|
-
var API_BASE5 = process.env.NUMO_API_URL ?? "http://localhost:3000";
|
|
9885
9918
|
async function runChecks() {
|
|
9886
9919
|
const checks = [];
|
|
9887
9920
|
const nodeVersion = process.version;
|
|
@@ -9894,7 +9927,7 @@ async function runChecks() {
|
|
|
9894
9927
|
checks.push({
|
|
9895
9928
|
name: "api_url",
|
|
9896
9929
|
status: process.env.NUMO_API_URL ? "ok" : "warn",
|
|
9897
|
-
message: process.env.NUMO_API_URL ? `API URL: ${
|
|
9930
|
+
message: process.env.NUMO_API_URL ? `API URL: ${API_BASE}` : `NUMO_API_URL not set (using default: ${API_BASE})`
|
|
9898
9931
|
});
|
|
9899
9932
|
const creds = loadCredentials();
|
|
9900
9933
|
checks.push({
|
|
@@ -9913,7 +9946,7 @@ async function runChecks() {
|
|
|
9913
9946
|
checks.push({ name: "token", status: "fail", message: "Skipped (no credentials)" });
|
|
9914
9947
|
}
|
|
9915
9948
|
try {
|
|
9916
|
-
const resp = await fetch(`${
|
|
9949
|
+
const resp = await fetch(`${API_BASE}/api/health`, { signal: AbortSignal.timeout(5e3) });
|
|
9917
9950
|
checks.push({ name: "api_reachable", status: "ok", message: `API server reachable (HTTP ${resp.status})` });
|
|
9918
9951
|
} catch (err) {
|
|
9919
9952
|
checks.push({ name: "api_reachable", status: "fail", message: `API server unreachable: ${err.message}` });
|
|
@@ -9946,10 +9979,14 @@ function registerDoctorCommand(program3) {
|
|
|
9946
9979
|
});
|
|
9947
9980
|
}
|
|
9948
9981
|
|
|
9982
|
+
// src/cli/cli.ts
|
|
9983
|
+
init_dirs();
|
|
9984
|
+
|
|
9949
9985
|
// src/cli/lib/update-check.ts
|
|
9950
9986
|
var fs4 = __toESM(require("fs"), 1);
|
|
9951
9987
|
var path2 = __toESM(require("path"), 1);
|
|
9952
9988
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
9989
|
+
init_dirs();
|
|
9953
9990
|
init_tty();
|
|
9954
9991
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
9955
9992
|
var PACKAGE_NAME = "numo-cli";
|
|
@@ -10015,7 +10052,7 @@ function fetchLatestVersion(state) {
|
|
|
10015
10052
|
// src/cli/cli.ts
|
|
10016
10053
|
init_tty();
|
|
10017
10054
|
init_errors();
|
|
10018
|
-
var CLI_VERSION = true ? "1.
|
|
10055
|
+
var CLI_VERSION = true ? "1.5.0" : "0.0.0-dev";
|
|
10019
10056
|
var program2 = new Command();
|
|
10020
10057
|
program2.name("numo").description("CLI for Numo \u2014 programmatic access for humans and AI agents").version(CLI_VERSION).option("--json [fields]", "Output as JSON (optionally: comma-separated field names)").option("-q, --quiet", "Suppress interactive output, implies --json").hook("preAction", (thisCommand) => {
|
|
10021
10058
|
const opts = thisCommand.optsWithGlobals();
|