numo-cli 1.2.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 +660 -1928
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -33,27 +33,9 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
33
33
|
mod
|
|
34
34
|
));
|
|
35
35
|
|
|
36
|
-
// <define:__ADMIN_UIDS__>
|
|
37
|
-
var define_ADMIN_UIDS_default;
|
|
38
|
-
var init_define_ADMIN_UIDS = __esm({
|
|
39
|
-
"<define:__ADMIN_UIDS__>"() {
|
|
40
|
-
define_ADMIN_UIDS_default = [
|
|
41
|
-
"m4FaWpKYc6QMceKV6wiyx9uLi8n1",
|
|
42
|
-
"yQ36wj8NXKQvcAfp8TtQ4hp8HPi1",
|
|
43
|
-
"koOPCRPPNaZcQZp1icPq8KlluZk2",
|
|
44
|
-
"VuT0LASAitWqB0L1Vn1aL6nAwaw2",
|
|
45
|
-
"ODc6kNR5jMVV8cFWdKueNSUkMUm1",
|
|
46
|
-
"usrkanvWkzdB8CwFnpSLgt9kiGD3",
|
|
47
|
-
"akSj1Q2GUOZopvYaLLTDtXBBOYE2",
|
|
48
|
-
"61SQrwQAp7N182uwv7M2cCismdh1"
|
|
49
|
-
];
|
|
50
|
-
}
|
|
51
|
-
});
|
|
52
|
-
|
|
53
36
|
// node_modules/commander/lib/error.js
|
|
54
37
|
var require_error = __commonJS({
|
|
55
38
|
"node_modules/commander/lib/error.js"(exports2) {
|
|
56
|
-
init_define_ADMIN_UIDS();
|
|
57
39
|
var CommanderError2 = class extends Error {
|
|
58
40
|
/**
|
|
59
41
|
* Constructs the CommanderError class
|
|
@@ -89,7 +71,6 @@ var require_error = __commonJS({
|
|
|
89
71
|
// node_modules/commander/lib/argument.js
|
|
90
72
|
var require_argument = __commonJS({
|
|
91
73
|
"node_modules/commander/lib/argument.js"(exports2) {
|
|
92
|
-
init_define_ADMIN_UIDS();
|
|
93
74
|
var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
94
75
|
var Argument2 = class {
|
|
95
76
|
/**
|
|
@@ -217,7 +198,6 @@ var require_argument = __commonJS({
|
|
|
217
198
|
// node_modules/commander/lib/help.js
|
|
218
199
|
var require_help = __commonJS({
|
|
219
200
|
"node_modules/commander/lib/help.js"(exports2) {
|
|
220
|
-
init_define_ADMIN_UIDS();
|
|
221
201
|
var { humanReadableArgName } = require_argument();
|
|
222
202
|
var Help2 = class {
|
|
223
203
|
constructor() {
|
|
@@ -632,7 +612,6 @@ var require_help = __commonJS({
|
|
|
632
612
|
// node_modules/commander/lib/option.js
|
|
633
613
|
var require_option = __commonJS({
|
|
634
614
|
"node_modules/commander/lib/option.js"(exports2) {
|
|
635
|
-
init_define_ADMIN_UIDS();
|
|
636
615
|
var { InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
637
616
|
var Option2 = class {
|
|
638
617
|
/**
|
|
@@ -905,7 +884,6 @@ var require_option = __commonJS({
|
|
|
905
884
|
// node_modules/commander/lib/suggestSimilar.js
|
|
906
885
|
var require_suggestSimilar = __commonJS({
|
|
907
886
|
"node_modules/commander/lib/suggestSimilar.js"(exports2) {
|
|
908
|
-
init_define_ADMIN_UIDS();
|
|
909
887
|
var maxDistance = 3;
|
|
910
888
|
function editDistance(a, b) {
|
|
911
889
|
if (Math.abs(a.length - b.length) > maxDistance)
|
|
@@ -986,11 +964,10 @@ var require_suggestSimilar = __commonJS({
|
|
|
986
964
|
// node_modules/commander/lib/command.js
|
|
987
965
|
var require_command = __commonJS({
|
|
988
966
|
"node_modules/commander/lib/command.js"(exports2) {
|
|
989
|
-
init_define_ADMIN_UIDS();
|
|
990
967
|
var EventEmitter = require("node:events").EventEmitter;
|
|
991
968
|
var childProcess = require("node:child_process");
|
|
992
|
-
var
|
|
993
|
-
var
|
|
969
|
+
var path3 = require("node:path");
|
|
970
|
+
var fs5 = require("node:fs");
|
|
994
971
|
var process2 = require("node:process");
|
|
995
972
|
var { Argument: Argument2, humanReadableArgName } = require_argument();
|
|
996
973
|
var { CommanderError: CommanderError2 } = require_error();
|
|
@@ -1922,11 +1899,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1922
1899
|
let launchWithNode = false;
|
|
1923
1900
|
const sourceExt = [".js", ".ts", ".tsx", ".mjs", ".cjs"];
|
|
1924
1901
|
function findFile(baseDir, baseName) {
|
|
1925
|
-
const localBin =
|
|
1926
|
-
if (
|
|
1927
|
-
if (sourceExt.includes(
|
|
1902
|
+
const localBin = path3.resolve(baseDir, baseName);
|
|
1903
|
+
if (fs5.existsSync(localBin)) return localBin;
|
|
1904
|
+
if (sourceExt.includes(path3.extname(baseName))) return void 0;
|
|
1928
1905
|
const foundExt = sourceExt.find(
|
|
1929
|
-
(ext) =>
|
|
1906
|
+
(ext) => fs5.existsSync(`${localBin}${ext}`)
|
|
1930
1907
|
);
|
|
1931
1908
|
if (foundExt) return `${localBin}${foundExt}`;
|
|
1932
1909
|
return void 0;
|
|
@@ -1938,21 +1915,21 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1938
1915
|
if (this._scriptPath) {
|
|
1939
1916
|
let resolvedScriptPath;
|
|
1940
1917
|
try {
|
|
1941
|
-
resolvedScriptPath =
|
|
1918
|
+
resolvedScriptPath = fs5.realpathSync(this._scriptPath);
|
|
1942
1919
|
} catch (err) {
|
|
1943
1920
|
resolvedScriptPath = this._scriptPath;
|
|
1944
1921
|
}
|
|
1945
|
-
executableDir =
|
|
1946
|
-
|
|
1922
|
+
executableDir = path3.resolve(
|
|
1923
|
+
path3.dirname(resolvedScriptPath),
|
|
1947
1924
|
executableDir
|
|
1948
1925
|
);
|
|
1949
1926
|
}
|
|
1950
1927
|
if (executableDir) {
|
|
1951
1928
|
let localFile = findFile(executableDir, executableFile);
|
|
1952
1929
|
if (!localFile && !subcommand._executableFile && this._scriptPath) {
|
|
1953
|
-
const legacyName =
|
|
1930
|
+
const legacyName = path3.basename(
|
|
1954
1931
|
this._scriptPath,
|
|
1955
|
-
|
|
1932
|
+
path3.extname(this._scriptPath)
|
|
1956
1933
|
);
|
|
1957
1934
|
if (legacyName !== this._name) {
|
|
1958
1935
|
localFile = findFile(
|
|
@@ -1963,7 +1940,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
1963
1940
|
}
|
|
1964
1941
|
executableFile = localFile || executableFile;
|
|
1965
1942
|
}
|
|
1966
|
-
launchWithNode = sourceExt.includes(
|
|
1943
|
+
launchWithNode = sourceExt.includes(path3.extname(executableFile));
|
|
1967
1944
|
let proc;
|
|
1968
1945
|
if (process2.platform !== "win32") {
|
|
1969
1946
|
if (launchWithNode) {
|
|
@@ -2803,7 +2780,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2803
2780
|
* @return {Command}
|
|
2804
2781
|
*/
|
|
2805
2782
|
nameFromFilename(filename) {
|
|
2806
|
-
this._name =
|
|
2783
|
+
this._name = path3.basename(filename, path3.extname(filename));
|
|
2807
2784
|
return this;
|
|
2808
2785
|
}
|
|
2809
2786
|
/**
|
|
@@ -2817,9 +2794,9 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
2817
2794
|
* @param {string} [path]
|
|
2818
2795
|
* @return {(string|null|Command)}
|
|
2819
2796
|
*/
|
|
2820
|
-
executableDir(
|
|
2821
|
-
if (
|
|
2822
|
-
this._executableDir =
|
|
2797
|
+
executableDir(path4) {
|
|
2798
|
+
if (path4 === void 0) return this._executableDir;
|
|
2799
|
+
this._executableDir = path4;
|
|
2823
2800
|
return this;
|
|
2824
2801
|
}
|
|
2825
2802
|
/**
|
|
@@ -3030,7 +3007,6 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
3030
3007
|
// node_modules/commander/index.js
|
|
3031
3008
|
var require_commander = __commonJS({
|
|
3032
3009
|
"node_modules/commander/index.js"(exports2) {
|
|
3033
|
-
init_define_ADMIN_UIDS();
|
|
3034
3010
|
var { Argument: Argument2 } = require_argument();
|
|
3035
3011
|
var { Command: Command2 } = require_command();
|
|
3036
3012
|
var { CommanderError: CommanderError2, InvalidArgumentError: InvalidArgumentError2 } = require_error();
|
|
@@ -3053,7 +3029,6 @@ var require_commander = __commonJS({
|
|
|
3053
3029
|
// node_modules/picocolors/picocolors.js
|
|
3054
3030
|
var require_picocolors = __commonJS({
|
|
3055
3031
|
"node_modules/picocolors/picocolors.js"(exports2, module2) {
|
|
3056
|
-
init_define_ADMIN_UIDS();
|
|
3057
3032
|
var p = process || {};
|
|
3058
3033
|
var argv = p.argv || [];
|
|
3059
3034
|
var env = p.env || {};
|
|
@@ -3211,7 +3186,6 @@ var DEFAULT_TIMEOUT, MAX_RETRIES, RETRYABLE_STATUS, RETRYABLE_NETWORK_CODES, htt
|
|
|
3211
3186
|
var init_http = __esm({
|
|
3212
3187
|
"src/cli/lib/http.ts"() {
|
|
3213
3188
|
"use strict";
|
|
3214
|
-
init_define_ADMIN_UIDS();
|
|
3215
3189
|
DEFAULT_TIMEOUT = 3e4;
|
|
3216
3190
|
MAX_RETRIES = 3;
|
|
3217
3191
|
RETRYABLE_STATUS = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
|
|
@@ -3243,31 +3217,333 @@ var init_http = __esm({
|
|
|
3243
3217
|
}
|
|
3244
3218
|
});
|
|
3245
3219
|
|
|
3246
|
-
// src/cli/lib/
|
|
3247
|
-
|
|
3248
|
-
|
|
3249
|
-
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
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
|
+
}
|
|
3253
3485
|
});
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
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 });
|
|
3257
3502
|
}
|
|
3258
|
-
function
|
|
3259
|
-
|
|
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
|
+
}
|
|
3260
3511
|
}
|
|
3261
|
-
function
|
|
3262
|
-
|
|
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;
|
|
3263
3525
|
}
|
|
3264
|
-
function
|
|
3265
|
-
|
|
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;
|
|
3266
3538
|
}
|
|
3267
|
-
var
|
|
3268
|
-
|
|
3539
|
+
var fs2, crypto, refreshInFlight;
|
|
3540
|
+
var init_credentials = __esm({
|
|
3541
|
+
"src/cli/auth/credentials.ts"() {
|
|
3269
3542
|
"use strict";
|
|
3270
|
-
|
|
3543
|
+
fs2 = __toESM(require("fs"), 1);
|
|
3544
|
+
crypto = __toESM(require("crypto"), 1);
|
|
3545
|
+
init_dirs();
|
|
3546
|
+
refreshInFlight = null;
|
|
3271
3547
|
}
|
|
3272
3548
|
});
|
|
3273
3549
|
|
|
@@ -3282,7 +3558,6 @@ var isUnicodeSupported;
|
|
|
3282
3558
|
var init_tty = __esm({
|
|
3283
3559
|
"src/cli/lib/tty.ts"() {
|
|
3284
3560
|
"use strict";
|
|
3285
|
-
init_define_ADMIN_UIDS();
|
|
3286
3561
|
isUnicodeSupported = process.platform !== "win32" || !!process.env.WT_SESSION || process.env.TERM_PROGRAM === "vscode";
|
|
3287
3562
|
}
|
|
3288
3563
|
});
|
|
@@ -3291,7 +3566,6 @@ var init_tty = __esm({
|
|
|
3291
3566
|
var require_src = __commonJS({
|
|
3292
3567
|
"node_modules/sisteransi/src/index.js"(exports2, module2) {
|
|
3293
3568
|
"use strict";
|
|
3294
|
-
init_define_ADMIN_UIDS();
|
|
3295
3569
|
var ESC = "\x1B";
|
|
3296
3570
|
var CSI = `${ESC}[`;
|
|
3297
3571
|
var beep = "\x07";
|
|
@@ -3433,7 +3707,6 @@ function St(t2, e) {
|
|
|
3433
3707
|
var import_node_util, import_node_process, k, import_node_readline, import_sisteransi, import_node_tty, at, lt, ht, O, y, L, P, M, ct, ft, X, pt, S, T, Z, Ft, j, Q, dt, tt, U, et, mt, st, it, gt, G, vt, Et, At, _, bt, z, rt, nt, B, Vt, kt, yt, Lt, Mt, Tt, Wt, $t;
|
|
3434
3708
|
var init_dist = __esm({
|
|
3435
3709
|
"node_modules/@clack/core/dist/index.mjs"() {
|
|
3436
|
-
init_define_ADMIN_UIDS();
|
|
3437
3710
|
import_node_util = require("node:util");
|
|
3438
3711
|
import_node_process = require("node:process");
|
|
3439
3712
|
k = __toESM(require("node:readline"), 1);
|
|
@@ -4100,7 +4373,6 @@ function qt({ style: e = "heavy", max: r = 100, size: s = 40, ...i } = {}) {
|
|
|
4100
4373
|
var import_node_util2, import_node_process2, import_node_fs, import_node_path, import_sisteransi2, ee, ce, Me, I2, Re, $e, de, V, he, h, x2, Oe, Pe, z2, H2, te, U2, q2, Ne, se, pe, We, me, ge, Ge, fe, Fe, ye, Ee, W2, ve, mt2, gt2, ft2, we, re, ie, Ae, ne, Ft2, yt2, Le, Et2, D2, ae, je, vt2, Ce, ke, wt2, Ve, Se, He, At2, Ue, Ke, Ct2, Ie, St2, It2, bt2, X2, Xe, xt2, _t2, Dt2, Tt2, Mt2, Rt, Ot, Pt, R2, Nt, Wt2, Gt, Q2, Lt2, jt, kt2, Vt2, Ht, Ut, Kt, be, ze, oe, Jt, Xt, Qe, K2, Yt, zt, Qt, Zt;
|
|
4101
4374
|
var init_dist2 = __esm({
|
|
4102
4375
|
"node_modules/@clack/prompts/dist/index.mjs"() {
|
|
4103
|
-
init_define_ADMIN_UIDS();
|
|
4104
4376
|
init_dist();
|
|
4105
4377
|
init_dist();
|
|
4106
4378
|
import_node_util2 = require("node:util");
|
|
@@ -5072,318 +5344,74 @@ async function promptForMissing(opts) {
|
|
|
5072
5344
|
var init_prompts = __esm({
|
|
5073
5345
|
"src/cli/lib/prompts.ts"() {
|
|
5074
5346
|
"use strict";
|
|
5075
|
-
init_define_ADMIN_UIDS();
|
|
5076
5347
|
init_tty();
|
|
5077
5348
|
}
|
|
5078
5349
|
});
|
|
5079
5350
|
|
|
5080
|
-
// src/cli/
|
|
5081
|
-
|
|
5082
|
-
|
|
5083
|
-
|
|
5084
|
-
|
|
5085
|
-
|
|
5086
|
-
|
|
5087
|
-
|
|
5351
|
+
// src/cli/auth/phone-login.ts
|
|
5352
|
+
var phone_login_exports = {};
|
|
5353
|
+
__export(phone_login_exports, {
|
|
5354
|
+
authenticateWithPhone: () => authenticateWithPhone
|
|
5355
|
+
});
|
|
5356
|
+
async function authenticateWithPhone(spinner) {
|
|
5357
|
+
const p = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
5358
|
+
const phone = await promptText({
|
|
5359
|
+
message: "Phone number (with country code)",
|
|
5360
|
+
placeholder: "+380501234567",
|
|
5361
|
+
required: true
|
|
5362
|
+
});
|
|
5363
|
+
if (!/^\+[0-9]{7,15}$/.test(phone)) {
|
|
5364
|
+
throw Errors.invalidInput("Invalid phone number. Use E.164 format: +<country code><number>", "Example: +380501234567");
|
|
5088
5365
|
}
|
|
5089
|
-
|
|
5090
|
-
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
5096
|
-
|
|
5097
|
-
|
|
5098
|
-
|
|
5099
|
-
|
|
5100
|
-
|
|
5101
|
-
|
|
5102
|
-
|
|
5103
|
-
|
|
5104
|
-
|
|
5105
|
-
|
|
5106
|
-
|
|
5107
|
-
const body = axiosErr.response?.data;
|
|
5108
|
-
const raw = body?.error?.message ?? axiosErr.message ?? "Unknown error";
|
|
5109
|
-
const message = sanitizeErrorMessage(raw);
|
|
5110
|
-
return new CliError("UNKNOWN" /* UNKNOWN */, message, ExitCode.GENERAL, { cause: err });
|
|
5111
|
-
}
|
|
5112
|
-
function sanitizeErrorMessage(msg) {
|
|
5113
|
-
return msg.replace(/https?:\/\/\S+/g, "<url>").replace(/\/(?:Users|home|var|tmp)\/\S+/g, "<path>").replace(/[A-Za-z0-9_-]{40,}/g, "<token>");
|
|
5114
|
-
}
|
|
5115
|
-
var ExitCode, CliError, Errors;
|
|
5116
|
-
var init_errors = __esm({
|
|
5117
|
-
"src/cli/lib/errors.ts"() {
|
|
5118
|
-
"use strict";
|
|
5119
|
-
init_define_ADMIN_UIDS();
|
|
5120
|
-
ExitCode = {
|
|
5121
|
-
OK: 0,
|
|
5122
|
-
GENERAL: 1,
|
|
5123
|
-
USAGE: 2,
|
|
5124
|
-
UNAVAILABLE: 69,
|
|
5125
|
-
TEMP_FAIL: 75,
|
|
5126
|
-
NO_PERM: 77,
|
|
5127
|
-
CONFIG: 78,
|
|
5128
|
-
NOT_FOUND: 100,
|
|
5129
|
-
CONFLICT: 101
|
|
5130
|
-
};
|
|
5131
|
-
CliError = class extends Error {
|
|
5132
|
-
constructor(kind, message, exitCode = ExitCode.GENERAL, options = {}) {
|
|
5133
|
-
super(message);
|
|
5134
|
-
this.kind = kind;
|
|
5135
|
-
this.exitCode = exitCode;
|
|
5136
|
-
this.options = options;
|
|
5137
|
-
this.name = "CliError";
|
|
5138
|
-
}
|
|
5139
|
-
toJSON() {
|
|
5366
|
+
spinner.start("Starting phone verification...");
|
|
5367
|
+
const startResp = await http.post(`${API_BASE}/api/auth/phone/start`, {
|
|
5368
|
+
phoneNumber: phone
|
|
5369
|
+
});
|
|
5370
|
+
const { sessionId, verifyUrl } = startResp.data;
|
|
5371
|
+
spinner.stop("");
|
|
5372
|
+
p.log.info("Opening browser for phone verification...");
|
|
5373
|
+
p.log.info(import_picocolors.default.dim(`If the browser does not open, visit: ${verifyUrl}`));
|
|
5374
|
+
const { default: open } = await import("open");
|
|
5375
|
+
const cp = await open(verifyUrl);
|
|
5376
|
+
cp.unref();
|
|
5377
|
+
spinner.start("Waiting for verification in browser...");
|
|
5378
|
+
const deadline = Date.now() + POLL_TIMEOUT;
|
|
5379
|
+
while (Date.now() < deadline) {
|
|
5380
|
+
await new Promise((r) => setTimeout(r, POLL_INTERVAL));
|
|
5381
|
+
try {
|
|
5382
|
+
const pollResp = await http.get(`${API_BASE}/api/auth/phone/poll?session=${sessionId}`);
|
|
5383
|
+
if (pollResp.status === 200 && pollResp.data.idToken) {
|
|
5140
5384
|
return {
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
...this.options.hint && { hint: this.options.hint },
|
|
5147
|
-
retryable: this.options.retryable ?? false,
|
|
5148
|
-
...this.options.retryAfter != null && { retryAfter: this.options.retryAfter }
|
|
5149
|
-
}
|
|
5385
|
+
refreshToken: pollResp.data.refreshToken,
|
|
5386
|
+
uid: pollResp.data.uid,
|
|
5387
|
+
displayName: phone,
|
|
5388
|
+
idToken: pollResp.data.idToken,
|
|
5389
|
+
idTokenExpiry: Date.now() + (pollResp.data.expiresIn || 3600) * 1e3
|
|
5150
5390
|
};
|
|
5151
5391
|
}
|
|
5152
|
-
}
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
suggestion: "numo login"
|
|
5156
|
-
}),
|
|
5157
|
-
notFound: (resource, id) => new CliError("NOT_FOUND" /* NOT_FOUND */, `${resource} not found${id ? `: ${id}` : ""}`, ExitCode.NOT_FOUND, {
|
|
5158
|
-
suggestion: `numo ${resource.toLowerCase()}s list`
|
|
5159
|
-
}),
|
|
5160
|
-
missingArg: (name, flag) => new CliError("MISSING_ARGUMENT" /* MISSING_ARGUMENT */, `${name} is required`, ExitCode.USAGE, {
|
|
5161
|
-
suggestion: `Use --${flag}`,
|
|
5162
|
-
hint: "Run with --help for all options."
|
|
5163
|
-
}),
|
|
5164
|
-
invalidInput: (message, hint) => new CliError("INVALID_INPUT" /* INVALID_INPUT */, message, ExitCode.USAGE, { hint }),
|
|
5165
|
-
configMissing: (key) => new CliError("CONFIG_ERROR" /* CONFIG_ERROR */, `${key} not set`, ExitCode.CONFIG, {
|
|
5166
|
-
suggestion: `export ${key}=<value>`
|
|
5167
|
-
}),
|
|
5168
|
-
networkError: (hint) => new CliError("NETWORK_ERROR" /* NETWORK_ERROR */, "Can't reach Numo servers", ExitCode.UNAVAILABLE, {
|
|
5169
|
-
hint: hint ?? "Check your internet connection.",
|
|
5170
|
-
retryable: true
|
|
5171
|
-
}),
|
|
5172
|
-
timeout: () => new CliError("TIMEOUT" /* TIMEOUT */, "Request timed out", ExitCode.TEMP_FAIL, {
|
|
5173
|
-
hint: "The server took too long to respond. Try again.",
|
|
5174
|
-
retryable: true
|
|
5175
|
-
}),
|
|
5176
|
-
rateLimited: (retryAfter) => new CliError("RATE_LIMITED" /* RATE_LIMITED */, "Too many requests", ExitCode.TEMP_FAIL, {
|
|
5177
|
-
hint: retryAfter ? `Wait ${retryAfter} seconds and try again.` : "Wait a moment and try again.",
|
|
5178
|
-
retryable: true,
|
|
5179
|
-
retryAfter
|
|
5180
|
-
})
|
|
5181
|
-
};
|
|
5182
|
-
}
|
|
5183
|
-
});
|
|
5184
|
-
|
|
5185
|
-
// src/cli/auth/local-server.ts
|
|
5186
|
-
function findFreePort() {
|
|
5187
|
-
return new Promise((resolve, reject) => {
|
|
5188
|
-
const server = net.createServer();
|
|
5189
|
-
server.listen(0, "127.0.0.1", () => {
|
|
5190
|
-
const address = server.address();
|
|
5191
|
-
server.close(() => resolve(address.port));
|
|
5192
|
-
});
|
|
5193
|
-
server.on("error", reject);
|
|
5194
|
-
});
|
|
5195
|
-
}
|
|
5196
|
-
function serveHtmlAndWaitForCallback(port, html, expectedState) {
|
|
5197
|
-
return new Promise((resolve, reject) => {
|
|
5198
|
-
const connections = /* @__PURE__ */ new Set();
|
|
5199
|
-
const server = http2.createServer((req, res) => {
|
|
5200
|
-
const parsed = url.parse(req.url ?? "", true);
|
|
5201
|
-
if (parsed.pathname === "/callback") {
|
|
5202
|
-
if (parsed.query.state !== expectedState) {
|
|
5203
|
-
res.writeHead(403, { "Content-Type": "text/html", "Connection": "close" });
|
|
5204
|
-
res.end("<html><body><h2>Invalid state parameter. Authentication rejected.</h2></body></html>");
|
|
5205
|
-
return;
|
|
5206
|
-
}
|
|
5207
|
-
res.writeHead(200, { "Content-Type": "text/html", "Connection": "close", "Cache-Control": "no-store" });
|
|
5208
|
-
res.end("<html><body><h2>Authentication complete. You can close this tab.</h2></body></html>");
|
|
5209
|
-
clearTimeout(timer);
|
|
5210
|
-
server.close();
|
|
5211
|
-
for (const conn of connections) conn.destroy();
|
|
5212
|
-
resolve(parsed.query);
|
|
5213
|
-
return;
|
|
5214
|
-
}
|
|
5215
|
-
if (parsed.pathname !== "/") {
|
|
5216
|
-
res.writeHead(404, { "Content-Type": "text/plain", "Connection": "close" });
|
|
5217
|
-
res.end("Not Found");
|
|
5218
|
-
return;
|
|
5392
|
+
} catch (err) {
|
|
5393
|
+
if (err.response?.status === 404) {
|
|
5394
|
+
throw Errors.networkError("Verification session expired. Try again.");
|
|
5219
5395
|
}
|
|
5220
|
-
res.writeHead(200, {
|
|
5221
|
-
"Content-Type": "text/html",
|
|
5222
|
-
"Cache-Control": "no-store",
|
|
5223
|
-
"Content-Security-Policy": "default-src 'self' https://www.gstatic.com 'unsafe-inline'"
|
|
5224
|
-
});
|
|
5225
|
-
res.end(html);
|
|
5226
|
-
});
|
|
5227
|
-
server.on("connection", (conn) => {
|
|
5228
|
-
connections.add(conn);
|
|
5229
|
-
conn.on("close", () => connections.delete(conn));
|
|
5230
|
-
});
|
|
5231
|
-
server.listen(port, "127.0.0.1");
|
|
5232
|
-
server.on("error", reject);
|
|
5233
|
-
const timer = setTimeout(() => {
|
|
5234
|
-
server.close();
|
|
5235
|
-
for (const conn of connections) conn.destroy();
|
|
5236
|
-
reject(new Error("Timeout waiting for authentication (5 min)"));
|
|
5237
|
-
}, 5 * 60 * 1e3);
|
|
5238
|
-
timer.unref();
|
|
5239
|
-
});
|
|
5240
|
-
}
|
|
5241
|
-
var http2, net, url;
|
|
5242
|
-
var init_local_server = __esm({
|
|
5243
|
-
"src/cli/auth/local-server.ts"() {
|
|
5244
|
-
"use strict";
|
|
5245
|
-
init_define_ADMIN_UIDS();
|
|
5246
|
-
http2 = __toESM(require("http"), 1);
|
|
5247
|
-
net = __toESM(require("net"), 1);
|
|
5248
|
-
url = __toESM(require("url"), 1);
|
|
5249
|
-
}
|
|
5250
|
-
});
|
|
5251
|
-
|
|
5252
|
-
// src/cli/auth/phone-login.ts
|
|
5253
|
-
var phone_login_exports = {};
|
|
5254
|
-
__export(phone_login_exports, {
|
|
5255
|
-
authenticateWithPhone: () => authenticateWithPhone
|
|
5256
|
-
});
|
|
5257
|
-
function buildSmsPageHtml(apiKey, projectId, appId, phoneNumber, callbackUrl, state) {
|
|
5258
|
-
const configJson = JSON.stringify({ apiKey, projectId, appId, phoneNumber, callbackUrl, state });
|
|
5259
|
-
return `<!DOCTYPE html>
|
|
5260
|
-
<html>
|
|
5261
|
-
<head>
|
|
5262
|
-
<meta charset="UTF-8">
|
|
5263
|
-
<title>Numo \u2014 Sending SMS</title>
|
|
5264
|
-
<style>
|
|
5265
|
-
body { font-family: sans-serif; max-width: 420px; margin: 80px auto; padding: 20px; text-align: center; }
|
|
5266
|
-
.spinner { display: inline-block; width: 20px; height: 20px; border: 3px solid #ccc; border-top-color: #4285f4; border-radius: 50%; animation: spin 0.8s linear infinite; vertical-align: middle; margin-right: 8px; }
|
|
5267
|
-
@keyframes spin { to { transform: rotate(360deg); } }
|
|
5268
|
-
.error { color: #d32f2f; font-size: 14px; margin-top: 16px; }
|
|
5269
|
-
.success { color: #2e7d32; }
|
|
5270
|
-
</style>
|
|
5271
|
-
</head>
|
|
5272
|
-
<body>
|
|
5273
|
-
<h2>Numo</h2>
|
|
5274
|
-
<p id="status"><span class="spinner"></span> Sending SMS...</p>
|
|
5275
|
-
<p id="error" class="error"></p>
|
|
5276
|
-
|
|
5277
|
-
<div id="recaptcha-container"></div>
|
|
5278
|
-
|
|
5279
|
-
<script>window.__NUMO__ = ${configJson};</script>
|
|
5280
|
-
<script type="module">
|
|
5281
|
-
import { initializeApp } from 'https://www.gstatic.com/firebasejs/10.7.0/firebase-app.js';
|
|
5282
|
-
import { getAuth, RecaptchaVerifier, signInWithPhoneNumber } from 'https://www.gstatic.com/firebasejs/10.7.0/firebase-auth.js';
|
|
5283
|
-
|
|
5284
|
-
const cfg = window.__NUMO__;
|
|
5285
|
-
const app = initializeApp({
|
|
5286
|
-
apiKey: cfg.apiKey,
|
|
5287
|
-
projectId: cfg.projectId,
|
|
5288
|
-
appId: cfg.appId,
|
|
5289
|
-
authDomain: cfg.projectId + '.firebaseapp.com',
|
|
5290
|
-
});
|
|
5291
|
-
const auth = getAuth(app);
|
|
5292
|
-
|
|
5293
|
-
try {
|
|
5294
|
-
const verifier = new RecaptchaVerifier(auth, 'recaptcha-container', { size: 'invisible' });
|
|
5295
|
-
const confirmationResult = await signInWithPhoneNumber(auth, cfg.phoneNumber, verifier);
|
|
5296
|
-
|
|
5297
|
-
const params = new URLSearchParams({ verificationId: confirmationResult.verificationId, state: cfg.state });
|
|
5298
|
-
document.getElementById('status').innerHTML = '<span class="success">SMS sent! Return to the terminal to enter the code.</span>';
|
|
5299
|
-
window.location.href = cfg.callbackUrl + '?' + params.toString();
|
|
5300
|
-
} catch (e) {
|
|
5301
|
-
document.getElementById('status').textContent = '';
|
|
5302
|
-
document.getElementById('error').textContent = e.message;
|
|
5303
5396
|
}
|
|
5304
|
-
</script>
|
|
5305
|
-
</body>
|
|
5306
|
-
</html>`;
|
|
5307
|
-
}
|
|
5308
|
-
async function authenticateWithPhone(spinner) {
|
|
5309
|
-
const fbApiKey = getFirebaseApiKey();
|
|
5310
|
-
const projectId = getFirebaseProjectId();
|
|
5311
|
-
const appId = getFirebaseAppId();
|
|
5312
|
-
if (!fbApiKey) {
|
|
5313
|
-
throw Errors.configMissing("NUMO_FIREBASE_API_KEY");
|
|
5314
|
-
}
|
|
5315
|
-
if (!projectId) {
|
|
5316
|
-
throw Errors.configMissing("NUMO_FIREBASE_PROJECT_ID");
|
|
5317
|
-
}
|
|
5318
|
-
if (!appId) {
|
|
5319
|
-
throw Errors.configMissing("NUMO_FIREBASE_APP_ID");
|
|
5320
|
-
}
|
|
5321
|
-
const p = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
5322
|
-
const phone = await promptText({
|
|
5323
|
-
message: "Phone number (with country code)",
|
|
5324
|
-
placeholder: "+380501234567",
|
|
5325
|
-
required: true
|
|
5326
|
-
});
|
|
5327
|
-
if (!/^\+[0-9]{7,15}$/.test(phone)) {
|
|
5328
|
-
throw Errors.invalidInput("Invalid phone number. Use E.164 format: +<country code><number>", "Example: +380501234567");
|
|
5329
|
-
}
|
|
5330
|
-
const port = await findFreePort();
|
|
5331
|
-
const callbackUrl = `http://localhost:${port}/callback`;
|
|
5332
|
-
const state = crypto2.randomUUID();
|
|
5333
|
-
const html = buildSmsPageHtml(fbApiKey, projectId, appId, phone, callbackUrl, state);
|
|
5334
|
-
p.log.info("Opening browser to send SMS...");
|
|
5335
|
-
p.log.info(import_picocolors.default.dim(`If the browser does not open, visit: http://localhost:${port}`));
|
|
5336
|
-
const { default: open } = await import("open");
|
|
5337
|
-
const cp = await open(`http://localhost:${port}`);
|
|
5338
|
-
cp.unref();
|
|
5339
|
-
const callbackParams = await serveHtmlAndWaitForCallback(port, html, state);
|
|
5340
|
-
const verificationId = callbackParams.verificationId;
|
|
5341
|
-
if (!verificationId) {
|
|
5342
|
-
throw Errors.networkError("Failed to send SMS. Try again.");
|
|
5343
|
-
}
|
|
5344
|
-
p.log.success("SMS sent");
|
|
5345
|
-
const otp = await promptText({
|
|
5346
|
-
message: "Enter the 6-digit code from SMS",
|
|
5347
|
-
placeholder: "123456",
|
|
5348
|
-
required: true
|
|
5349
|
-
});
|
|
5350
|
-
spinner.start("Verifying code...");
|
|
5351
|
-
const resp = await http.post(
|
|
5352
|
-
`https://identitytoolkit.googleapis.com/v1/accounts:signInWithPhoneNumber?key=${fbApiKey}`,
|
|
5353
|
-
{ sessionInfo: verificationId, code: otp }
|
|
5354
|
-
);
|
|
5355
|
-
const { refreshToken, localId: uid, idToken, phoneNumber } = resp.data;
|
|
5356
|
-
if (!refreshToken || !uid) {
|
|
5357
|
-
throw new Error("Incomplete credentials received");
|
|
5358
5397
|
}
|
|
5359
|
-
|
|
5360
|
-
refreshToken,
|
|
5361
|
-
uid,
|
|
5362
|
-
displayName: phoneNumber || phone,
|
|
5363
|
-
idToken,
|
|
5364
|
-
idTokenExpiry: idToken ? Date.now() + 3600 * 1e3 : void 0
|
|
5365
|
-
};
|
|
5398
|
+
throw Errors.networkError("Phone verification timed out. Try again.");
|
|
5366
5399
|
}
|
|
5367
|
-
var
|
|
5400
|
+
var import_picocolors, POLL_INTERVAL, POLL_TIMEOUT;
|
|
5368
5401
|
var init_phone_login = __esm({
|
|
5369
5402
|
"src/cli/auth/phone-login.ts"() {
|
|
5370
5403
|
"use strict";
|
|
5371
|
-
init_define_ADMIN_UIDS();
|
|
5372
|
-
crypto2 = __toESM(require("crypto"), 1);
|
|
5373
5404
|
init_http();
|
|
5374
5405
|
import_picocolors = __toESM(require_picocolors(), 1);
|
|
5375
5406
|
init_errors();
|
|
5376
|
-
init_local_server();
|
|
5377
|
-
init_config();
|
|
5378
5407
|
init_prompts();
|
|
5408
|
+
init_api_client();
|
|
5409
|
+
POLL_INTERVAL = 2e3;
|
|
5410
|
+
POLL_TIMEOUT = 5 * 60 * 1e3;
|
|
5379
5411
|
}
|
|
5380
5412
|
});
|
|
5381
5413
|
|
|
5382
|
-
// src/cli/cli.ts
|
|
5383
|
-
init_define_ADMIN_UIDS();
|
|
5384
|
-
|
|
5385
5414
|
// node_modules/commander/esm.mjs
|
|
5386
|
-
init_define_ADMIN_UIDS();
|
|
5387
5415
|
var import_index = __toESM(require_commander(), 1);
|
|
5388
5416
|
var {
|
|
5389
5417
|
program,
|
|
@@ -5404,178 +5432,34 @@ var {
|
|
|
5404
5432
|
var import_picocolors13 = __toESM(require_picocolors(), 1);
|
|
5405
5433
|
|
|
5406
5434
|
// src/cli/auth/login.ts
|
|
5407
|
-
init_define_ADMIN_UIDS();
|
|
5408
5435
|
init_http();
|
|
5409
5436
|
var import_picocolors2 = __toESM(require_picocolors(), 1);
|
|
5410
|
-
|
|
5411
|
-
// src/cli/auth/credentials.ts
|
|
5412
|
-
init_define_ADMIN_UIDS();
|
|
5413
|
-
var fs2 = __toESM(require("fs"), 1);
|
|
5414
|
-
var crypto = __toESM(require("crypto"), 1);
|
|
5415
|
-
|
|
5416
|
-
// src/cli/lib/dirs.ts
|
|
5417
|
-
init_define_ADMIN_UIDS();
|
|
5418
|
-
var fs = __toESM(require("fs"), 1);
|
|
5419
|
-
var path = __toESM(require("path"), 1);
|
|
5420
|
-
var os = __toESM(require("os"), 1);
|
|
5421
|
-
var LEGACY_DIR = path.join(os.homedir(), ".numo");
|
|
5422
|
-
function getConfigDir() {
|
|
5423
|
-
if (process.env.NUMO_CONFIG_DIR) {
|
|
5424
|
-
return process.env.NUMO_CONFIG_DIR;
|
|
5425
|
-
}
|
|
5426
|
-
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
5427
|
-
const xdgDir = path.join(xdgHome, "numo");
|
|
5428
|
-
if (fs.existsSync(xdgDir)) return xdgDir;
|
|
5429
|
-
if (fs.existsSync(LEGACY_DIR)) return LEGACY_DIR;
|
|
5430
|
-
return xdgDir;
|
|
5431
|
-
}
|
|
5432
|
-
function ensureConfigDir() {
|
|
5433
|
-
const dir = getConfigDir();
|
|
5434
|
-
if (!fs.existsSync(dir)) {
|
|
5435
|
-
fs.mkdirSync(dir, { recursive: true, mode: 448 });
|
|
5436
|
-
}
|
|
5437
|
-
return dir;
|
|
5438
|
-
}
|
|
5439
|
-
function getCredentialsPath() {
|
|
5440
|
-
return path.join(getConfigDir(), "credentials.json");
|
|
5441
|
-
}
|
|
5442
|
-
function migrateIfNeeded() {
|
|
5443
|
-
const xdgHome = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), ".config");
|
|
5444
|
-
const xdgDir = path.join(xdgHome, "numo");
|
|
5445
|
-
if (process.env.NUMO_CONFIG_DIR) return;
|
|
5446
|
-
const legacyCreds = path.join(LEGACY_DIR, "credentials.json");
|
|
5447
|
-
if (!fs.existsSync(legacyCreds) || fs.existsSync(xdgDir)) return;
|
|
5448
|
-
try {
|
|
5449
|
-
fs.mkdirSync(xdgDir, { recursive: true, mode: 448 });
|
|
5450
|
-
const data = fs.readFileSync(legacyCreds, "utf8");
|
|
5451
|
-
fs.writeFileSync(path.join(xdgDir, "credentials.json"), data, { mode: 384 });
|
|
5452
|
-
const legacyStreaks = path.join(LEGACY_DIR, "streaks.json");
|
|
5453
|
-
if (fs.existsSync(legacyStreaks)) {
|
|
5454
|
-
const streaksData = fs.readFileSync(legacyStreaks, "utf8");
|
|
5455
|
-
fs.writeFileSync(path.join(xdgDir, "streaks.json"), streaksData, { mode: 384 });
|
|
5456
|
-
}
|
|
5457
|
-
process.stderr.write(`Migrated config from ${LEGACY_DIR} to ${xdgDir}
|
|
5458
|
-
`);
|
|
5459
|
-
} catch {
|
|
5460
|
-
}
|
|
5461
|
-
}
|
|
5462
|
-
|
|
5463
|
-
// src/cli/auth/credentials.ts
|
|
5464
|
-
function loadCredentials() {
|
|
5465
|
-
try {
|
|
5466
|
-
const data = JSON.parse(fs2.readFileSync(getCredentialsPath(), "utf8"));
|
|
5467
|
-
if (typeof data?.refreshToken !== "string" || typeof data?.uid !== "string" || typeof data?.email !== "string") {
|
|
5468
|
-
return null;
|
|
5469
|
-
}
|
|
5470
|
-
return data;
|
|
5471
|
-
} catch {
|
|
5472
|
-
return null;
|
|
5473
|
-
}
|
|
5474
|
-
}
|
|
5475
|
-
function saveCredentials(creds) {
|
|
5476
|
-
ensureConfigDir();
|
|
5477
|
-
fs2.writeFileSync(getCredentialsPath(), JSON.stringify(creds, null, 2), { mode: 384 });
|
|
5478
|
-
}
|
|
5479
|
-
function clearCredentials() {
|
|
5480
|
-
try {
|
|
5481
|
-
const credPath = getCredentialsPath();
|
|
5482
|
-
const stat = fs2.statSync(credPath);
|
|
5483
|
-
fs2.writeFileSync(credPath, crypto.randomBytes(stat.size));
|
|
5484
|
-
fs2.unlinkSync(credPath);
|
|
5485
|
-
} catch {
|
|
5486
|
-
}
|
|
5487
|
-
}
|
|
5488
|
-
var refreshInFlight = null;
|
|
5489
|
-
async function getIdToken() {
|
|
5490
|
-
const envToken = process.env.NUMO_TOKEN;
|
|
5491
|
-
if (envToken) return envToken;
|
|
5492
|
-
const creds = loadCredentials();
|
|
5493
|
-
if (!creds) throw new Error("Not logged in. Run: numo login");
|
|
5494
|
-
if (creds.idToken && creds.idTokenExpiry && Date.now() < creds.idTokenExpiry - 6e4) {
|
|
5495
|
-
return creds.idToken;
|
|
5496
|
-
}
|
|
5497
|
-
if (refreshInFlight) return refreshInFlight;
|
|
5498
|
-
refreshInFlight = performRefresh(creds).finally(() => {
|
|
5499
|
-
refreshInFlight = null;
|
|
5500
|
-
});
|
|
5501
|
-
return refreshInFlight;
|
|
5502
|
-
}
|
|
5503
|
-
async function performRefresh(creds) {
|
|
5504
|
-
const { getFirebaseApiKey: getFirebaseApiKey2 } = await Promise.resolve().then(() => (init_config(), config_exports));
|
|
5505
|
-
const fbApiKey = getFirebaseApiKey2();
|
|
5506
|
-
if (!fbApiKey) throw new Error("NUMO_FIREBASE_API_KEY not set");
|
|
5507
|
-
const { http: http3 } = await Promise.resolve().then(() => (init_http(), http_exports));
|
|
5508
|
-
const resp = await http3.post(
|
|
5509
|
-
`https://securetoken.googleapis.com/v1/token?key=${fbApiKey}`,
|
|
5510
|
-
{ grant_type: "refresh_token", refresh_token: creds.refreshToken },
|
|
5511
|
-
{ headers: { "Content-Type": "application/json" } }
|
|
5512
|
-
);
|
|
5513
|
-
creds.idToken = resp.data.id_token;
|
|
5514
|
-
creds.idTokenExpiry = Date.now() + (parseInt(resp.data.expires_in) || 3600) * 1e3;
|
|
5515
|
-
saveCredentials(creds);
|
|
5516
|
-
return creds.idToken;
|
|
5517
|
-
}
|
|
5518
|
-
|
|
5519
|
-
// src/cli/auth/login.ts
|
|
5520
|
-
init_config();
|
|
5437
|
+
init_credentials();
|
|
5521
5438
|
init_prompts();
|
|
5522
5439
|
init_errors();
|
|
5523
|
-
|
|
5524
|
-
// src/cli/lib/uid.ts
|
|
5525
|
-
init_define_ADMIN_UIDS();
|
|
5526
|
-
init_errors();
|
|
5527
|
-
var ADMIN_UIDS = typeof define_ADMIN_UIDS_default !== "undefined" ? define_ADMIN_UIDS_default : [];
|
|
5528
|
-
function requireUid() {
|
|
5529
|
-
const creds = loadCredentials();
|
|
5530
|
-
if (!creds) throw Errors.authRequired();
|
|
5531
|
-
return creds.uid;
|
|
5532
|
-
}
|
|
5533
|
-
function isAdmin() {
|
|
5534
|
-
const creds = loadCredentials();
|
|
5535
|
-
return !!creds && ADMIN_UIDS.includes(creds.uid);
|
|
5536
|
-
}
|
|
5537
|
-
function requireAdmin() {
|
|
5538
|
-
const creds = loadCredentials();
|
|
5539
|
-
if (!creds) throw Errors.authRequired();
|
|
5540
|
-
if (!ADMIN_UIDS.includes(creds.uid)) {
|
|
5541
|
-
throw new CliError("AUTH_FORBIDDEN" /* AUTH_FORBIDDEN */, "Admin access required", ExitCode.NO_PERM, {
|
|
5542
|
-
hint: "Your account does not have admin privileges."
|
|
5543
|
-
});
|
|
5544
|
-
}
|
|
5545
|
-
return creds.uid;
|
|
5546
|
-
}
|
|
5547
|
-
|
|
5548
|
-
// src/cli/auth/login.ts
|
|
5440
|
+
init_api_client();
|
|
5549
5441
|
async function authenticateWithEmail(spinner) {
|
|
5550
|
-
const fbApiKey = getFirebaseApiKey();
|
|
5551
|
-
if (!fbApiKey) {
|
|
5552
|
-
throw Errors.configMissing("NUMO_FIREBASE_API_KEY");
|
|
5553
|
-
}
|
|
5554
5442
|
const email = await promptText({ message: "Email", required: true });
|
|
5555
5443
|
const password = await promptPassword({ message: "Password" });
|
|
5556
5444
|
spinner.start("Signing in...");
|
|
5557
5445
|
const resp = await http.post(
|
|
5558
|
-
|
|
5559
|
-
{ email, password
|
|
5560
|
-
{ headers: { "Content-Type": "application/json" } }
|
|
5446
|
+
`${API_BASE}/api/auth/login`,
|
|
5447
|
+
{ email, password }
|
|
5561
5448
|
);
|
|
5562
5449
|
return {
|
|
5563
5450
|
refreshToken: resp.data.refreshToken,
|
|
5564
|
-
uid: resp.data.
|
|
5565
|
-
displayName: resp.data.email,
|
|
5451
|
+
uid: resp.data.uid,
|
|
5452
|
+
displayName: resp.data.email ?? email,
|
|
5566
5453
|
idToken: resp.data.idToken,
|
|
5567
|
-
idTokenExpiry: Date.now() + (
|
|
5454
|
+
idTokenExpiry: Date.now() + (resp.data.expiresIn || 3600) * 1e3
|
|
5568
5455
|
};
|
|
5569
5456
|
}
|
|
5570
5457
|
function printSuccess(displayName) {
|
|
5571
5458
|
const lines = [
|
|
5572
5459
|
` ${import_picocolors2.default.dim("$")} numo tasks list --date YYYY-MM-DD List tasks for a date`,
|
|
5573
|
-
` ${import_picocolors2.default.dim("$")} numo tasks create --text "..." Create a task
|
|
5460
|
+
` ${import_picocolors2.default.dim("$")} numo tasks create --text "..." Create a task`,
|
|
5461
|
+
` ${import_picocolors2.default.dim("$")} numo profile View your profile`
|
|
5574
5462
|
];
|
|
5575
|
-
if (isAdmin()) {
|
|
5576
|
-
lines.push(` ${import_picocolors2.default.dim("$")} numo posts list Browse community posts`);
|
|
5577
|
-
}
|
|
5578
|
-
lines.push(` ${import_picocolors2.default.dim("$")} numo profile View your profile`);
|
|
5579
5463
|
console.log(`
|
|
5580
5464
|
${import_picocolors2.default.bold("Available commands:")}
|
|
5581
5465
|
${lines.join("\n")}
|
|
@@ -5623,176 +5507,13 @@ async function login(options = {}) {
|
|
|
5623
5507
|
}
|
|
5624
5508
|
|
|
5625
5509
|
// src/cli/auth/register.ts
|
|
5626
|
-
init_define_ADMIN_UIDS();
|
|
5627
5510
|
init_http();
|
|
5628
5511
|
var import_picocolors3 = __toESM(require_picocolors(), 1);
|
|
5629
|
-
|
|
5512
|
+
init_credentials();
|
|
5630
5513
|
init_prompts();
|
|
5631
5514
|
init_errors();
|
|
5632
|
-
|
|
5633
|
-
// src/cli/lib/firestore.ts
|
|
5634
|
-
init_define_ADMIN_UIDS();
|
|
5635
|
-
init_http();
|
|
5636
|
-
init_config();
|
|
5637
|
-
function toValue(v) {
|
|
5638
|
-
if (v === null || v === void 0) return { nullValue: null };
|
|
5639
|
-
if (typeof v === "string") return { stringValue: v };
|
|
5640
|
-
if (typeof v === "boolean") return { booleanValue: v };
|
|
5641
|
-
if (typeof v === "number") {
|
|
5642
|
-
return Number.isInteger(v) ? { integerValue: String(v) } : { doubleValue: v };
|
|
5643
|
-
}
|
|
5644
|
-
if (Array.isArray(v)) {
|
|
5645
|
-
return { arrayValue: { values: v.map(toValue) } };
|
|
5646
|
-
}
|
|
5647
|
-
if (typeof v === "object") {
|
|
5648
|
-
return { mapValue: { fields: toFirestoreFields(v) } };
|
|
5649
|
-
}
|
|
5650
|
-
return { stringValue: String(v) };
|
|
5651
|
-
}
|
|
5652
|
-
function toFirestoreFields(obj) {
|
|
5653
|
-
const fields = {};
|
|
5654
|
-
for (const [k2, v] of Object.entries(obj)) {
|
|
5655
|
-
fields[k2] = toValue(v);
|
|
5656
|
-
}
|
|
5657
|
-
return fields;
|
|
5658
|
-
}
|
|
5659
|
-
function fromValue(v) {
|
|
5660
|
-
if ("stringValue" in v) return v.stringValue;
|
|
5661
|
-
if ("integerValue" in v) return parseInt(v.integerValue, 10);
|
|
5662
|
-
if ("doubleValue" in v) return v.doubleValue;
|
|
5663
|
-
if ("booleanValue" in v) return v.booleanValue;
|
|
5664
|
-
if ("nullValue" in v) return null;
|
|
5665
|
-
if ("arrayValue" in v) return (v.arrayValue.values ?? []).map(fromValue);
|
|
5666
|
-
if ("mapValue" in v) return fromFirestoreFields(v.mapValue.fields ?? {});
|
|
5667
|
-
return null;
|
|
5668
|
-
}
|
|
5669
|
-
function fromFirestoreFields(fields) {
|
|
5670
|
-
const obj = {};
|
|
5671
|
-
for (const [k2, v] of Object.entries(fields)) {
|
|
5672
|
-
obj[k2] = fromValue(v);
|
|
5673
|
-
}
|
|
5674
|
-
return obj;
|
|
5675
|
-
}
|
|
5676
|
-
function fromFirestoreDoc(doc) {
|
|
5677
|
-
const id = doc.name.split("/").pop();
|
|
5678
|
-
return { id, ...doc.fields ? fromFirestoreFields(doc.fields) : {} };
|
|
5679
|
-
}
|
|
5680
|
-
async function authHeaders() {
|
|
5681
|
-
const idToken = await getIdToken();
|
|
5682
|
-
return { Authorization: `Bearer ${idToken}`, "Content-Type": "application/json" };
|
|
5683
|
-
}
|
|
5684
|
-
async function getDoc(path4) {
|
|
5685
|
-
const url2 = `${getFirestoreBaseUrl()}/${path4}`;
|
|
5686
|
-
const { data } = await http.get(url2, { headers: await authHeaders() });
|
|
5687
|
-
return fromFirestoreDoc(data);
|
|
5688
|
-
}
|
|
5689
|
-
async function createDoc(collectionPath, data) {
|
|
5690
|
-
const url2 = `${getFirestoreBaseUrl()}/${collectionPath}`;
|
|
5691
|
-
const resp = await http.post(url2, { fields: toFirestoreFields(data) }, { headers: await authHeaders() });
|
|
5692
|
-
return fromFirestoreDoc(resp.data);
|
|
5693
|
-
}
|
|
5694
|
-
async function setDoc(docPath, data) {
|
|
5695
|
-
const url2 = `${getFirestoreBaseUrl()}/${docPath}`;
|
|
5696
|
-
const resp = await http.patch(url2, { fields: toFirestoreFields(data) }, { headers: await authHeaders() });
|
|
5697
|
-
return fromFirestoreDoc(resp.data);
|
|
5698
|
-
}
|
|
5699
|
-
async function updateDoc(path4, data, fieldMask) {
|
|
5700
|
-
const url2 = `${getFirestoreBaseUrl()}/${path4}`;
|
|
5701
|
-
const qs = new URLSearchParams();
|
|
5702
|
-
for (const f of fieldMask) qs.append("updateMask.fieldPaths", f);
|
|
5703
|
-
const resp = await http.patch(`${url2}?${qs}`, { fields: toFirestoreFields(data) }, { headers: await authHeaders() });
|
|
5704
|
-
return fromFirestoreDoc(resp.data);
|
|
5705
|
-
}
|
|
5706
|
-
async function deleteDoc(path4) {
|
|
5707
|
-
const url2 = `${getFirestoreBaseUrl()}/${path4}`;
|
|
5708
|
-
await http.delete(url2, { headers: await authHeaders() });
|
|
5709
|
-
}
|
|
5710
|
-
async function runQuery(parentPath, collectionId, opts) {
|
|
5711
|
-
const base = getFirestoreBaseUrl();
|
|
5712
|
-
const url2 = parentPath ? `${base}/${parentPath}:runQuery` : `${base}:runQuery`;
|
|
5713
|
-
const structuredQuery = {
|
|
5714
|
-
from: [{ collectionId }]
|
|
5715
|
-
};
|
|
5716
|
-
if (opts.where && opts.where.length > 0) {
|
|
5717
|
-
if (opts.where.length === 1) {
|
|
5718
|
-
const w = opts.where[0];
|
|
5719
|
-
structuredQuery.where = {
|
|
5720
|
-
fieldFilter: {
|
|
5721
|
-
field: { fieldPath: w.field },
|
|
5722
|
-
op: w.op,
|
|
5723
|
-
value: toValue(w.value)
|
|
5724
|
-
}
|
|
5725
|
-
};
|
|
5726
|
-
} else {
|
|
5727
|
-
structuredQuery.where = {
|
|
5728
|
-
compositeFilter: {
|
|
5729
|
-
op: "AND",
|
|
5730
|
-
filters: opts.where.map((w) => ({
|
|
5731
|
-
fieldFilter: {
|
|
5732
|
-
field: { fieldPath: w.field },
|
|
5733
|
-
op: w.op,
|
|
5734
|
-
value: toValue(w.value)
|
|
5735
|
-
}
|
|
5736
|
-
}))
|
|
5737
|
-
}
|
|
5738
|
-
};
|
|
5739
|
-
}
|
|
5740
|
-
}
|
|
5741
|
-
if (opts.orderBy && opts.orderBy.length > 0) {
|
|
5742
|
-
structuredQuery.orderBy = opts.orderBy.map((o) => ({
|
|
5743
|
-
field: { fieldPath: o.field },
|
|
5744
|
-
direction: o.direction ?? "ASCENDING"
|
|
5745
|
-
}));
|
|
5746
|
-
}
|
|
5747
|
-
if (opts.limit) {
|
|
5748
|
-
structuredQuery.limit = opts.limit;
|
|
5749
|
-
}
|
|
5750
|
-
if (opts.startAfter && opts.startAfter.length > 0) {
|
|
5751
|
-
structuredQuery.startAt = {
|
|
5752
|
-
values: opts.startAfter.map(toValue),
|
|
5753
|
-
before: false
|
|
5754
|
-
};
|
|
5755
|
-
}
|
|
5756
|
-
const headers = await authHeaders();
|
|
5757
|
-
const { data } = await http.post(url2, { structuredQuery }, { headers });
|
|
5758
|
-
return data.filter((r) => r.document).map((r) => fromFirestoreDoc(r.document));
|
|
5759
|
-
}
|
|
5760
|
-
async function commit(writes) {
|
|
5761
|
-
const baseUrl = getFirestoreBaseUrl();
|
|
5762
|
-
const dbUrl = baseUrl.replace("/documents", "");
|
|
5763
|
-
const url2 = `${dbUrl}/documents:commit`;
|
|
5764
|
-
const toResourceName = (path4) => `${baseUrl}/${path4}`.replace("https://firestore.googleapis.com/v1/", "");
|
|
5765
|
-
const ops = writes.map((w) => {
|
|
5766
|
-
if (w.type === "delete") {
|
|
5767
|
-
return { delete: toResourceName(w.path) };
|
|
5768
|
-
}
|
|
5769
|
-
const op = {};
|
|
5770
|
-
if (w.type === "update" && w.data) {
|
|
5771
|
-
op.update = {
|
|
5772
|
-
name: toResourceName(w.path),
|
|
5773
|
-
fields: toFirestoreFields(w.data)
|
|
5774
|
-
};
|
|
5775
|
-
if (w.fieldMask) {
|
|
5776
|
-
op.updateMask = { fieldPaths: w.fieldMask };
|
|
5777
|
-
}
|
|
5778
|
-
}
|
|
5779
|
-
if (w.type === "transform") {
|
|
5780
|
-
op.update = {
|
|
5781
|
-
name: toResourceName(w.path),
|
|
5782
|
-
fields: {}
|
|
5783
|
-
};
|
|
5784
|
-
op.updateTransforms = (w.transforms ?? []).map((t2) => ({
|
|
5785
|
-
fieldPath: t2.field,
|
|
5786
|
-
increment: toValue(t2.increment)
|
|
5787
|
-
}));
|
|
5788
|
-
}
|
|
5789
|
-
return op;
|
|
5790
|
-
});
|
|
5791
|
-
await http.post(url2, { writes: ops }, { headers: await authHeaders() });
|
|
5792
|
-
}
|
|
5793
|
-
|
|
5794
|
-
// src/cli/auth/register.ts
|
|
5795
5515
|
init_tty();
|
|
5516
|
+
init_api_client();
|
|
5796
5517
|
function validateEmail(email) {
|
|
5797
5518
|
const trimmed = email.trim();
|
|
5798
5519
|
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
|
|
@@ -5806,92 +5527,44 @@ function validatePassword(password) {
|
|
|
5806
5527
|
}
|
|
5807
5528
|
return password;
|
|
5808
5529
|
}
|
|
5809
|
-
function generateUsername(email, uid) {
|
|
5810
|
-
const emailPart = email.split("@")[0].slice(0, 7);
|
|
5811
|
-
const uidPart = uid.slice(0, 4);
|
|
5812
|
-
return `${emailPart}-${uidPart}`;
|
|
5813
|
-
}
|
|
5814
|
-
function randomAvatar() {
|
|
5815
|
-
const index = Math.floor(Math.random() * 6) + 1;
|
|
5816
|
-
return `assets/avatar${index}-min.png`;
|
|
5817
|
-
}
|
|
5818
|
-
function buildUserDoc(email, uid, username, avatar, now2 = Date.now()) {
|
|
5819
|
-
return {
|
|
5820
|
-
email,
|
|
5821
|
-
userId: uid,
|
|
5822
|
-
signUpTag: "Numo Sign up",
|
|
5823
|
-
createdAt: now2,
|
|
5824
|
-
username,
|
|
5825
|
-
defaultAvatar: avatar,
|
|
5826
|
-
preference: {
|
|
5827
|
-
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
5828
|
-
timezones: [(/* @__PURE__ */ new Date()).getTimezoneOffset()]
|
|
5829
|
-
},
|
|
5830
|
-
fcmToken: [],
|
|
5831
|
-
badges: [],
|
|
5832
|
-
reminderPlatform: "imessage",
|
|
5833
|
-
onboarding: {},
|
|
5834
|
-
postOnboardingV1Status: "not-completed"
|
|
5835
|
-
};
|
|
5836
|
-
}
|
|
5837
5530
|
function classifySignUpError(err) {
|
|
5838
5531
|
if (err instanceof CliError) return err;
|
|
5839
5532
|
const resp = err?.response?.data?.error;
|
|
5533
|
+
const kind = resp?.kind ?? "";
|
|
5840
5534
|
const msg = resp?.message ?? "";
|
|
5841
|
-
if (msg.includes("
|
|
5535
|
+
if (kind === "CONFLICT" || msg.includes("already in use")) {
|
|
5842
5536
|
return Errors.invalidInput(
|
|
5843
5537
|
"Email already in use",
|
|
5844
5538
|
"Already have an account? Run: numo login"
|
|
5845
5539
|
);
|
|
5846
5540
|
}
|
|
5847
|
-
if (msg.includes("
|
|
5541
|
+
if (msg.includes("Invalid email")) {
|
|
5848
5542
|
return Errors.invalidInput("Invalid email address");
|
|
5849
5543
|
}
|
|
5850
|
-
if (msg.includes("
|
|
5544
|
+
if (msg.includes("Password too weak") || msg.includes("min 6")) {
|
|
5851
5545
|
return Errors.invalidInput("Password is too weak (min 6 characters)");
|
|
5852
5546
|
}
|
|
5853
|
-
if (msg.includes("OPERATION_NOT_ALLOWED")) {
|
|
5854
|
-
return new CliError(
|
|
5855
|
-
"AUTH_FORBIDDEN" /* AUTH_FORBIDDEN */,
|
|
5856
|
-
"Email registration is disabled",
|
|
5857
|
-
ExitCode.NO_PERM
|
|
5858
|
-
);
|
|
5859
|
-
}
|
|
5860
5547
|
return classifyError(err);
|
|
5861
5548
|
}
|
|
5862
5549
|
async function signUp(email, password) {
|
|
5863
|
-
const fbApiKey = getFirebaseApiKey();
|
|
5864
|
-
if (!fbApiKey) throw Errors.configMissing("NUMO_FIREBASE_API_KEY");
|
|
5865
5550
|
try {
|
|
5866
|
-
const resp = await http.post(
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5551
|
+
const resp = await http.post(`${API_BASE}/api/auth/register`, {
|
|
5552
|
+
email,
|
|
5553
|
+
password,
|
|
5554
|
+
tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
5555
|
+
tzOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset()
|
|
5556
|
+
});
|
|
5871
5557
|
return {
|
|
5872
5558
|
refreshToken: resp.data.refreshToken,
|
|
5873
|
-
uid: resp.data.
|
|
5874
|
-
displayName: resp.data.email,
|
|
5559
|
+
uid: resp.data.uid,
|
|
5560
|
+
displayName: resp.data.email ?? email,
|
|
5875
5561
|
idToken: resp.data.idToken,
|
|
5876
|
-
idTokenExpiry: Date.now() + (
|
|
5562
|
+
idTokenExpiry: Date.now() + (resp.data.expiresIn || 3600) * 1e3
|
|
5877
5563
|
};
|
|
5878
5564
|
} catch (err) {
|
|
5879
5565
|
throw classifySignUpError(err);
|
|
5880
5566
|
}
|
|
5881
5567
|
}
|
|
5882
|
-
async function setupUserProfile(uid, email) {
|
|
5883
|
-
const now2 = Date.now();
|
|
5884
|
-
const username = generateUsername(email, uid);
|
|
5885
|
-
const avatar = randomAvatar();
|
|
5886
|
-
await setDoc(`users/${uid}`, buildUserDoc(email, uid, username, avatar, now2));
|
|
5887
|
-
await commit([
|
|
5888
|
-
{
|
|
5889
|
-
type: "transform",
|
|
5890
|
-
path: "appLinks/users",
|
|
5891
|
-
transforms: [{ field: "count", increment: 1 }]
|
|
5892
|
-
}
|
|
5893
|
-
]);
|
|
5894
|
-
}
|
|
5895
5568
|
async function register(options = {}) {
|
|
5896
5569
|
const p = await Promise.resolve().then(() => (init_dist2(), dist_exports));
|
|
5897
5570
|
p.intro(import_picocolors3.default.bold("Numo \u2014 Register"));
|
|
@@ -5919,8 +5592,6 @@ async function register(options = {}) {
|
|
|
5919
5592
|
idToken: result.idToken,
|
|
5920
5593
|
idTokenExpiry: result.idTokenExpiry
|
|
5921
5594
|
});
|
|
5922
|
-
s.message("Setting up profile...");
|
|
5923
|
-
await setupUserProfile(result.uid, email);
|
|
5924
5595
|
s.stop(`Registered as ${import_picocolors3.default.green(result.displayName)}`);
|
|
5925
5596
|
p.outro("Welcome to Numo!");
|
|
5926
5597
|
printSuccess(result.displayName);
|
|
@@ -5933,86 +5604,20 @@ async function register(options = {}) {
|
|
|
5933
5604
|
}
|
|
5934
5605
|
}
|
|
5935
5606
|
|
|
5936
|
-
// src/cli/
|
|
5937
|
-
|
|
5938
|
-
var fs3 = __toESM(require("fs"), 1);
|
|
5939
|
-
var path2 = __toESM(require("path"), 1);
|
|
5940
|
-
function getStreaksPath() {
|
|
5941
|
-
return path2.join(getConfigDir(), "streaks.json");
|
|
5942
|
-
}
|
|
5943
|
-
function loadStreaks() {
|
|
5944
|
-
try {
|
|
5945
|
-
const raw = fs3.readFileSync(getStreaksPath(), "utf-8");
|
|
5946
|
-
return JSON.parse(raw);
|
|
5947
|
-
} catch {
|
|
5948
|
-
return [];
|
|
5949
|
-
}
|
|
5950
|
-
}
|
|
5951
|
-
function saveStreaks(entries) {
|
|
5952
|
-
const dir = path2.dirname(getStreaksPath());
|
|
5953
|
-
if (!fs3.existsSync(dir)) fs3.mkdirSync(dir, { recursive: true });
|
|
5954
|
-
fs3.writeFileSync(getStreaksPath(), JSON.stringify(entries, null, 2), { mode: 384 });
|
|
5955
|
-
}
|
|
5956
|
-
function clearStreaks() {
|
|
5957
|
-
try {
|
|
5958
|
-
fs3.unlinkSync(getStreaksPath());
|
|
5959
|
-
} catch {
|
|
5960
|
-
}
|
|
5961
|
-
}
|
|
5962
|
-
function recordDailyStreak(taskId, date) {
|
|
5963
|
-
const entries = loadStreaks();
|
|
5964
|
-
const dateStr = date.slice(0, 10);
|
|
5965
|
-
const existing = entries.find((e) => e.taskId === taskId);
|
|
5966
|
-
if (existing) {
|
|
5967
|
-
const lastDate = new Date(existing.lastCheck);
|
|
5968
|
-
const thisDate = new Date(dateStr);
|
|
5969
|
-
const diffMs = thisDate.getTime() - lastDate.getTime();
|
|
5970
|
-
const diffDays2 = Math.floor(diffMs / 864e5);
|
|
5971
|
-
if (diffDays2 === 1) {
|
|
5972
|
-
existing.checksInRow += 1;
|
|
5973
|
-
} else if (diffDays2 > 1) {
|
|
5974
|
-
existing.checksInRow = 1;
|
|
5975
|
-
}
|
|
5976
|
-
existing.lastCheck = dateStr;
|
|
5977
|
-
saveStreaks(entries);
|
|
5978
|
-
return existing.checksInRow;
|
|
5979
|
-
}
|
|
5980
|
-
entries.push({ taskId, lastCheck: dateStr, checksInRow: 1 });
|
|
5981
|
-
saveStreaks(entries);
|
|
5982
|
-
return 1;
|
|
5983
|
-
}
|
|
5984
|
-
function revertDailyStreak(taskId) {
|
|
5985
|
-
const entries = loadStreaks();
|
|
5986
|
-
const idx = entries.findIndex((e) => e.taskId === taskId);
|
|
5987
|
-
if (idx !== -1) {
|
|
5988
|
-
entries[idx].checksInRow = Math.max(1, entries[idx].checksInRow - 1);
|
|
5989
|
-
saveStreaks(entries);
|
|
5990
|
-
}
|
|
5991
|
-
}
|
|
5992
|
-
function removeDailyStreak(taskId) {
|
|
5993
|
-
const entries = loadStreaks().filter((e) => e.taskId !== taskId);
|
|
5994
|
-
saveStreaks(entries);
|
|
5995
|
-
}
|
|
5996
|
-
function getCompletedTodayCount() {
|
|
5997
|
-
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
5998
|
-
return loadStreaks().filter((e) => e.lastCheck === today2).length;
|
|
5999
|
-
}
|
|
5607
|
+
// src/cli/cli.ts
|
|
5608
|
+
init_credentials();
|
|
6000
5609
|
|
|
6001
5610
|
// src/cli/commands/tasks.ts
|
|
6002
|
-
init_define_ADMIN_UIDS();
|
|
6003
5611
|
var import_picocolors8 = __toESM(require_picocolors(), 1);
|
|
6004
5612
|
|
|
6005
5613
|
// src/cli/lib/actions.ts
|
|
6006
|
-
init_define_ADMIN_UIDS();
|
|
6007
5614
|
init_tty();
|
|
6008
5615
|
|
|
6009
5616
|
// src/cli/lib/output.ts
|
|
6010
|
-
init_define_ADMIN_UIDS();
|
|
6011
5617
|
var import_picocolors5 = __toESM(require_picocolors(), 1);
|
|
6012
5618
|
init_tty();
|
|
6013
5619
|
|
|
6014
5620
|
// src/cli/lib/table.ts
|
|
6015
|
-
init_define_ADMIN_UIDS();
|
|
6016
5621
|
var import_picocolors4 = __toESM(require_picocolors(), 1);
|
|
6017
5622
|
init_tty();
|
|
6018
5623
|
var BOX = isUnicodeSupported ? {
|
|
@@ -6155,12 +5760,10 @@ ${import_picocolors5.default.red("Error")}: ${structured.message}`);
|
|
|
6155
5760
|
}
|
|
6156
5761
|
|
|
6157
5762
|
// src/cli/lib/spinner.ts
|
|
6158
|
-
init_define_ADMIN_UIDS();
|
|
6159
5763
|
var import_picocolors6 = __toESM(require_picocolors(), 1);
|
|
6160
5764
|
init_tty();
|
|
6161
5765
|
|
|
6162
5766
|
// src/cli/lib/symbols.ts
|
|
6163
|
-
init_define_ADMIN_UIDS();
|
|
6164
5767
|
init_tty();
|
|
6165
5768
|
var u = isUnicodeSupported;
|
|
6166
5769
|
var SYM = {
|
|
@@ -6348,679 +5951,83 @@ async function runCreate(opts) {
|
|
|
6348
5951
|
outputError(err, useJson(opts.global));
|
|
6349
5952
|
}
|
|
6350
5953
|
}
|
|
6351
|
-
async function runWrite(opts) {
|
|
6352
|
-
try {
|
|
6353
|
-
const payload = await withSpinner(
|
|
6354
|
-
useSpinner(opts.global),
|
|
6355
|
-
opts.spinnerMessage ?? "Updating...",
|
|
6356
|
-
opts.fn
|
|
6357
|
-
);
|
|
6358
|
-
const item = opts.dataKey ? payload[opts.dataKey] : payload;
|
|
6359
|
-
if (useJson(opts.global)) {
|
|
6360
|
-
printJson(selectFields(payload, opts.global.json));
|
|
6361
|
-
} else if (opts.onInteractive) {
|
|
6362
|
-
opts.onInteractive(item);
|
|
6363
|
-
} else {
|
|
6364
|
-
if (opts.successMessage) console.log(opts.successMessage);
|
|
6365
|
-
if (item && Object.keys(item).length > 0) {
|
|
6366
|
-
outputResult(item, false);
|
|
6367
|
-
}
|
|
6368
|
-
}
|
|
6369
|
-
} catch (err) {
|
|
6370
|
-
outputError(err, useJson(opts.global));
|
|
6371
|
-
}
|
|
6372
|
-
}
|
|
6373
|
-
async function runDelete(opts) {
|
|
6374
|
-
try {
|
|
6375
|
-
await withSpinner(
|
|
6376
|
-
useSpinner(opts.global),
|
|
6377
|
-
opts.spinnerMessage ?? "Deleting...",
|
|
6378
|
-
opts.fn
|
|
6379
|
-
);
|
|
6380
|
-
if (!opts.global.quiet) {
|
|
6381
|
-
console.log(opts.successMessage);
|
|
6382
|
-
}
|
|
6383
|
-
} catch (err) {
|
|
6384
|
-
outputError(err, useJson(opts.global));
|
|
6385
|
-
}
|
|
6386
|
-
}
|
|
6387
|
-
|
|
6388
|
-
// src/cli/services/tasks.ts
|
|
6389
|
-
init_define_ADMIN_UIDS();
|
|
6390
|
-
var crypto3 = __toESM(require("crypto"), 1);
|
|
6391
|
-
|
|
6392
|
-
// src/shared/index.ts
|
|
6393
|
-
init_define_ADMIN_UIDS();
|
|
6394
|
-
|
|
6395
|
-
// src/shared/types/task.ts
|
|
6396
|
-
init_define_ADMIN_UIDS();
|
|
6397
|
-
|
|
6398
|
-
// src/shared/types/post.ts
|
|
6399
|
-
init_define_ADMIN_UIDS();
|
|
6400
|
-
|
|
6401
|
-
// src/shared/types/comment.ts
|
|
6402
|
-
init_define_ADMIN_UIDS();
|
|
6403
|
-
|
|
6404
|
-
// src/shared/types/reply.ts
|
|
6405
|
-
init_define_ADMIN_UIDS();
|
|
6406
|
-
|
|
6407
|
-
// src/shared/constants.ts
|
|
6408
|
-
init_define_ADMIN_UIDS();
|
|
6409
|
-
var POST_TAGS = [
|
|
6410
|
-
"general",
|
|
6411
|
-
"hack",
|
|
6412
|
-
"story",
|
|
6413
|
-
"meme",
|
|
6414
|
-
"other",
|
|
6415
|
-
"question",
|
|
6416
|
-
"hack-tip",
|
|
6417
|
-
"activity"
|
|
6418
|
-
];
|
|
6419
|
-
var MAX_TASKS_PER_REQUEST = 200;
|
|
6420
|
-
var KARMA_POINTS = {
|
|
6421
|
-
addTask: 2,
|
|
6422
|
-
completeTask: 5,
|
|
6423
|
-
completeSubtask: [2, 5],
|
|
6424
|
-
splitTask: 10,
|
|
6425
|
-
createPost: 10,
|
|
6426
|
-
addComment: 10
|
|
6427
|
-
};
|
|
6428
|
-
var DIFFICULTY_BONUS = [0, 5, 15, 45];
|
|
6429
|
-
var MAX_KARMA_PER_COMPLETE = 100;
|
|
6430
|
-
var MAX_POSTS_PER_REQUEST = 50;
|
|
6431
|
-
var MAX_COMMENTS_PER_REQUEST = 100;
|
|
6432
|
-
var MAX_REPLIES_PER_REQUEST = 100;
|
|
6433
|
-
|
|
6434
|
-
// src/cli/lib/validation.ts
|
|
6435
|
-
init_define_ADMIN_UIDS();
|
|
6436
|
-
init_errors();
|
|
6437
|
-
function validateDocId(id, label = "Document ID") {
|
|
6438
|
-
if (!id || typeof id !== "string") {
|
|
6439
|
-
throw new Error(`${label} is required`);
|
|
6440
|
-
}
|
|
6441
|
-
const trimmed = id.trim();
|
|
6442
|
-
if (trimmed.length === 0) {
|
|
6443
|
-
throw new Error(`${label} cannot be empty`);
|
|
6444
|
-
}
|
|
6445
|
-
if (trimmed.length > 1500) {
|
|
6446
|
-
throw new Error(`${label} is too long (max 1500 characters)`);
|
|
6447
|
-
}
|
|
6448
|
-
if (trimmed.includes("/")) {
|
|
6449
|
-
throw new Error(`${label} cannot contain '/'`);
|
|
6450
|
-
}
|
|
6451
|
-
if (trimmed === "." || trimmed === "..") {
|
|
6452
|
-
throw new Error(`${label} cannot be '.' or '..'`);
|
|
6453
|
-
}
|
|
6454
|
-
if (trimmed.startsWith("__") && trimmed.endsWith("__")) {
|
|
6455
|
-
throw new Error(`${label} cannot be a reserved Firestore ID (double underscore wrapped)`);
|
|
6456
|
-
}
|
|
6457
|
-
return trimmed;
|
|
6458
|
-
}
|
|
6459
|
-
function checkOwnership(doc, uid, action, userField = "userId") {
|
|
6460
|
-
const docUid = doc[userField] ?? doc.authorId;
|
|
6461
|
-
if (docUid && docUid !== uid) {
|
|
6462
|
-
throw new CliError("AUTH_FORBIDDEN" /* AUTH_FORBIDDEN */, `You can only ${action} your own content`, ExitCode.NO_PERM);
|
|
6463
|
-
}
|
|
6464
|
-
}
|
|
6465
|
-
async function incrementField(path4, field, delta) {
|
|
6466
|
-
await commit([{
|
|
6467
|
-
type: "transform",
|
|
6468
|
-
path: path4,
|
|
6469
|
-
transforms: [{ field, increment: delta }]
|
|
6470
|
-
}]);
|
|
6471
|
-
}
|
|
6472
|
-
|
|
6473
|
-
// src/cli/lib/karma.ts
|
|
6474
|
-
init_define_ADMIN_UIDS();
|
|
6475
|
-
function karmaPath(uid, id) {
|
|
6476
|
-
return `users/${uid}/karma/${id}`;
|
|
6477
|
-
}
|
|
6478
|
-
async function giveKarma(uid, entity, entityId, karma, text) {
|
|
6479
|
-
const id = `${entity}_${entityId}`;
|
|
6480
|
-
const record = { entity, entityId, karma, text, createdAt: Date.now(), userId: uid };
|
|
6481
|
-
await commit([
|
|
6482
|
-
{ type: "update", path: karmaPath(uid, id), data: record },
|
|
6483
|
-
{ type: "transform", path: `users/${uid}`, transforms: [{ field: "karmaCount", increment: karma }] }
|
|
6484
|
-
]);
|
|
6485
|
-
}
|
|
6486
|
-
async function removeKarma(uid, entity, entityId) {
|
|
6487
|
-
const id = `${entity}_${entityId}`;
|
|
6488
|
-
try {
|
|
6489
|
-
const doc = await getDoc(karmaPath(uid, id));
|
|
6490
|
-
const karma = doc.karma ?? 0;
|
|
6491
|
-
const writes = [
|
|
6492
|
-
{ type: "delete", path: karmaPath(uid, id) }
|
|
6493
|
-
];
|
|
6494
|
-
if (karma > 0) {
|
|
6495
|
-
writes.push({
|
|
6496
|
-
type: "transform",
|
|
6497
|
-
path: `users/${uid}`,
|
|
6498
|
-
transforms: [{ field: "karmaCount", increment: -karma }]
|
|
6499
|
-
});
|
|
6500
|
-
}
|
|
6501
|
-
await commit(writes);
|
|
6502
|
-
} catch {
|
|
6503
|
-
}
|
|
6504
|
-
}
|
|
6505
|
-
|
|
6506
|
-
// src/cli/services/tasks.ts
|
|
6507
|
-
var WEEKDAY_MAP = { Sun: 0, Mon: 1, Tue: 2, Wed: 3, Thu: 4, Fri: 5, Sat: 6 };
|
|
6508
|
-
function parseDate(s) {
|
|
6509
|
-
const [datePart, timePart] = s.split(" ");
|
|
6510
|
-
const [y2, m, d] = datePart.split("-").map(Number);
|
|
6511
|
-
if (timePart) {
|
|
6512
|
-
const [h2, min] = timePart.split(":").map(Number);
|
|
6513
|
-
return new Date(y2, m - 1, d, h2, min);
|
|
6514
|
-
}
|
|
6515
|
-
return new Date(y2, m - 1, d, 0, 0);
|
|
6516
|
-
}
|
|
6517
|
-
function formatDate(d) {
|
|
6518
|
-
const yyyy = d.getFullYear();
|
|
6519
|
-
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
6520
|
-
const dd = String(d.getDate()).padStart(2, "0");
|
|
6521
|
-
const hh = String(d.getHours()).padStart(2, "0");
|
|
6522
|
-
const min = String(d.getMinutes()).padStart(2, "0");
|
|
6523
|
-
return `${yyyy}-${mm}-${dd} ${hh}:${min}`;
|
|
6524
|
-
}
|
|
6525
|
-
function endOfDay(dateStr) {
|
|
6526
|
-
return dateStr.slice(0, 10) + " 23:59";
|
|
6527
|
-
}
|
|
6528
|
-
function todayFormatted() {
|
|
6529
|
-
return formatDate(startOfDay(/* @__PURE__ */ new Date()));
|
|
6530
|
-
}
|
|
6531
|
-
function startOfDay(d) {
|
|
6532
|
-
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 0, 0, 0, 0);
|
|
6533
|
-
}
|
|
6534
|
-
function endOfDayDate(d) {
|
|
6535
|
-
return new Date(d.getFullYear(), d.getMonth(), d.getDate(), 23, 59, 59, 999);
|
|
6536
|
-
}
|
|
6537
|
-
function addDays(d, n) {
|
|
6538
|
-
const r = new Date(d);
|
|
6539
|
-
r.setDate(r.getDate() + n);
|
|
6540
|
-
return r;
|
|
6541
|
-
}
|
|
6542
|
-
function addMonths(d, n) {
|
|
6543
|
-
const r = new Date(d);
|
|
6544
|
-
r.setMonth(r.getMonth() + n);
|
|
6545
|
-
return r;
|
|
6546
|
-
}
|
|
6547
|
-
function diffDays(a, b) {
|
|
6548
|
-
return Math.floor((a.getTime() - b.getTime()) / 864e5);
|
|
6549
|
-
}
|
|
6550
|
-
function diffWeeks(a, b) {
|
|
6551
|
-
return Math.floor(diffDays(a, b) / 7);
|
|
6552
|
-
}
|
|
6553
|
-
function diffMonths(a, b) {
|
|
6554
|
-
return (a.getFullYear() - b.getFullYear()) * 12 + (a.getMonth() - b.getMonth());
|
|
6555
|
-
}
|
|
6556
|
-
function getClosestNextWeekDay(baseDate, weekDays) {
|
|
6557
|
-
const dayNums = new Set(weekDays.map((d) => WEEKDAY_MAP[d]));
|
|
6558
|
-
for (let offset = 1; offset <= 7; offset++) {
|
|
6559
|
-
const candidate = addDays(baseDate, offset);
|
|
6560
|
-
if (dayNums.has(candidate.getDay())) {
|
|
6561
|
-
candidate.setHours(baseDate.getHours(), baseDate.getMinutes(), 0, 0);
|
|
6562
|
-
return candidate;
|
|
6563
|
-
}
|
|
6564
|
-
}
|
|
6565
|
-
return addDays(baseDate, 7);
|
|
6566
|
-
}
|
|
6567
|
-
function isRepeating(task) {
|
|
6568
|
-
return task.repeat?.type !== "none";
|
|
6569
|
-
}
|
|
6570
|
-
function getTaskRemindDate(task) {
|
|
6571
|
-
if (!task.dueDate || task.completed) return null;
|
|
6572
|
-
const d = parseDate(task.dueDate);
|
|
6573
|
-
if (d.getHours() === 0 && d.getMinutes() === 0) return null;
|
|
6574
|
-
const utcY = d.getUTCFullYear();
|
|
6575
|
-
const utcM = String(d.getUTCMonth() + 1).padStart(2, "0");
|
|
6576
|
-
const utcD = String(d.getUTCDate()).padStart(2, "0");
|
|
6577
|
-
const utcH = String(d.getUTCHours()).padStart(2, "0");
|
|
6578
|
-
const utcMin = String(d.getUTCMinutes()).padStart(2, "0");
|
|
6579
|
-
return `${utcY}-${utcM}-${utcD} ${utcH}:${utcMin}`;
|
|
6580
|
-
}
|
|
6581
|
-
function getNextDueDate(task) {
|
|
6582
|
-
if (!task.dueDate) return todayFormatted();
|
|
6583
|
-
const { repeat } = task;
|
|
6584
|
-
const every = repeat.every ?? 1;
|
|
6585
|
-
const now2 = endOfDayDate(/* @__PURE__ */ new Date());
|
|
6586
|
-
let nextDate = parseDate(task.dueDate);
|
|
6587
|
-
const calculatePeriods = (diffFn) => {
|
|
6588
|
-
const diff = diffFn(now2, nextDate);
|
|
6589
|
-
const passedPeriods = Math.max(0, Math.floor(diff / every));
|
|
6590
|
-
return (passedPeriods + 1) * every;
|
|
6591
|
-
};
|
|
6592
|
-
if (repeat.type === "daily") {
|
|
6593
|
-
nextDate = addDays(nextDate, calculatePeriods(diffDays));
|
|
6594
|
-
} else if (repeat.type === "weekly") {
|
|
6595
|
-
if (repeat.weekDays && repeat.weekDays.length > 0) {
|
|
6596
|
-
const closestNext = getClosestNextWeekDay(parseDate(task.dueDate), repeat.weekDays);
|
|
6597
|
-
if (now2 < closestNext) {
|
|
6598
|
-
nextDate = closestNext;
|
|
6599
|
-
} else {
|
|
6600
|
-
const periods = calculatePeriods(diffWeeks);
|
|
6601
|
-
const advanced = addDays(nextDate, periods * 7);
|
|
6602
|
-
const dayDiff = (closestNext.getDay() - advanced.getDay() + 7) % 7;
|
|
6603
|
-
nextDate = addDays(advanced, dayDiff);
|
|
6604
|
-
nextDate.setHours(parseDate(task.dueDate).getHours(), parseDate(task.dueDate).getMinutes(), 0, 0);
|
|
6605
|
-
}
|
|
6606
|
-
} else {
|
|
6607
|
-
nextDate = addDays(nextDate, calculatePeriods(diffWeeks) * 7);
|
|
6608
|
-
}
|
|
6609
|
-
} else if (repeat.type === "monthly") {
|
|
6610
|
-
nextDate = addMonths(nextDate, calculatePeriods(diffMonths));
|
|
6611
|
-
}
|
|
6612
|
-
return formatDate(nextDate);
|
|
6613
|
-
}
|
|
6614
|
-
function taskPath(uid, id) {
|
|
6615
|
-
return `users/${uid}/tasks/${id}`;
|
|
6616
|
-
}
|
|
6617
|
-
function taskHistoryPath(uid, id) {
|
|
6618
|
-
return `users/${uid}/tasksHistory/${id}`;
|
|
6619
|
-
}
|
|
6620
|
-
function orderingPath(uid) {
|
|
6621
|
-
return `users/${uid}/order/tasks`;
|
|
6622
|
-
}
|
|
6623
|
-
function progressPath(uid, date) {
|
|
6624
|
-
return `users/${uid}/progress/${date}`;
|
|
6625
|
-
}
|
|
6626
|
-
function routineStreakPath(uid, taskId) {
|
|
6627
|
-
return `users/${uid}/routineStreaks/${taskId}`;
|
|
6628
|
-
}
|
|
6629
|
-
function archivePath(uid, taskId) {
|
|
6630
|
-
return `archive/${uid}/tasks/${taskId}`;
|
|
6631
|
-
}
|
|
6632
|
-
function archiveCounterPath(uid) {
|
|
6633
|
-
return `archive/${uid}`;
|
|
6634
|
-
}
|
|
6635
|
-
function activityTotalsPath(uid) {
|
|
6636
|
-
return `users/${uid}/activity/totals`;
|
|
6637
|
-
}
|
|
6638
|
-
function reversedTimestamp(len) {
|
|
6639
|
-
const maxTs = 9999999999999;
|
|
6640
|
-
const reversed = String(maxTs - Date.now());
|
|
6641
|
-
return reversed.slice(0, len);
|
|
6642
|
-
}
|
|
6643
|
-
function randomAlphanumeric(len) {
|
|
6644
|
-
const chars = "abcdefghijklmnopqrstuvwxyz0123456789";
|
|
6645
|
-
const limit = 252;
|
|
6646
|
-
let result = "";
|
|
6647
|
-
while (result.length < len) {
|
|
6648
|
-
const bytes = crypto3.randomBytes(len - result.length);
|
|
6649
|
-
for (let i = 0; i < bytes.length && result.length < len; i++) {
|
|
6650
|
-
if (bytes[i] < limit) result += chars[bytes[i] % chars.length];
|
|
6651
|
-
}
|
|
6652
|
-
}
|
|
6653
|
-
return result;
|
|
6654
|
-
}
|
|
6655
|
-
function generateTaskId(task) {
|
|
6656
|
-
const repeatType = task.repeat?.type === "none" ? "simple" : task.repeat?.type;
|
|
6657
|
-
const textSlug = String(task.text ?? "").slice(0, 8).toLowerCase().replace(/[^a-z0-9]/g, "-");
|
|
6658
|
-
const uniquePart = reversedTimestamp(8) + randomAlphanumeric(7);
|
|
6659
|
-
if (!task.dueDate) return `${repeatType}_${textSlug}_${uniquePart}`;
|
|
6660
|
-
const dateSlug = String(task.dueDate).replace(/[^a-z0-9]/g, "-");
|
|
6661
|
-
return `${repeatType}_${textSlug}_${dateSlug}_${uniquePart}`;
|
|
6662
|
-
}
|
|
6663
|
-
function logSideEffectResults(label, results) {
|
|
6664
|
-
for (const r of results) {
|
|
6665
|
-
if (r.status === "rejected") {
|
|
6666
|
-
const msg = r.reason instanceof Error ? r.reason.message : String(r.reason);
|
|
6667
|
-
process.stderr.write(`[warn] ${label}: ${msg}
|
|
6668
|
-
`);
|
|
6669
|
-
}
|
|
6670
|
-
}
|
|
6671
|
-
}
|
|
6672
|
-
async function addToOrdering(uid, taskId, position = "end") {
|
|
6673
|
-
let ordering = [];
|
|
6674
|
-
try {
|
|
6675
|
-
const doc = await getDoc(orderingPath(uid));
|
|
6676
|
-
if (Array.isArray(doc.tasksListOrdering)) ordering = doc.tasksListOrdering;
|
|
6677
|
-
} catch {
|
|
6678
|
-
}
|
|
6679
|
-
if (!ordering.includes(taskId)) {
|
|
6680
|
-
if (position === "start") ordering.unshift(taskId);
|
|
6681
|
-
else ordering.push(taskId);
|
|
6682
|
-
}
|
|
6683
|
-
await updateDoc(orderingPath(uid), { tasksListOrdering: ordering }, ["tasksListOrdering"]);
|
|
6684
|
-
}
|
|
6685
|
-
async function removeFromOrdering(uid, taskId) {
|
|
6686
|
-
try {
|
|
6687
|
-
const doc = await getDoc(orderingPath(uid));
|
|
6688
|
-
if (Array.isArray(doc.tasksListOrdering)) {
|
|
6689
|
-
const ordering = doc.tasksListOrdering.filter((id) => id !== taskId);
|
|
6690
|
-
await updateDoc(orderingPath(uid), { tasksListOrdering: ordering }, ["tasksListOrdering"]);
|
|
6691
|
-
}
|
|
6692
|
-
} catch {
|
|
6693
|
-
}
|
|
6694
|
-
}
|
|
6695
|
-
async function addToProgress(uid, taskId, date) {
|
|
6696
|
-
const dateKey = date.slice(0, 10);
|
|
6697
|
-
let tasks = [];
|
|
6698
|
-
try {
|
|
6699
|
-
const doc = await getDoc(progressPath(uid, dateKey));
|
|
6700
|
-
if (Array.isArray(doc.tasks)) tasks = doc.tasks;
|
|
6701
|
-
} catch {
|
|
6702
|
-
}
|
|
6703
|
-
if (!tasks.includes(taskId)) tasks.push(taskId);
|
|
6704
|
-
await setDoc(progressPath(uid, dateKey), { tasks });
|
|
6705
|
-
}
|
|
6706
|
-
async function removeFromProgress(uid, taskId, date) {
|
|
6707
|
-
const dateKey = date.slice(0, 10);
|
|
6708
|
-
try {
|
|
6709
|
-
const doc = await getDoc(progressPath(uid, dateKey));
|
|
6710
|
-
if (Array.isArray(doc.tasks)) {
|
|
6711
|
-
const tasks = doc.tasks.filter((id) => id !== taskId);
|
|
6712
|
-
await setDoc(progressPath(uid, dateKey), { tasks });
|
|
6713
|
-
}
|
|
6714
|
-
} catch {
|
|
6715
|
-
}
|
|
6716
|
-
}
|
|
6717
|
-
async function recordRoutineStreak(uid, taskId) {
|
|
6718
|
-
let streak = {};
|
|
6719
|
-
try {
|
|
6720
|
-
streak = await getDoc(routineStreakPath(uid, taskId));
|
|
6721
|
-
} catch {
|
|
6722
|
-
}
|
|
6723
|
-
const newStreak = (streak.streak ?? 0) + 1;
|
|
6724
|
-
const longestStreak = Math.max(streak.longestStreak ?? 0, newStreak);
|
|
6725
|
-
await setDoc(routineStreakPath(uid, taskId), {
|
|
6726
|
-
taskId,
|
|
6727
|
-
userId: uid,
|
|
6728
|
-
streak: newStreak,
|
|
6729
|
-
longestStreak,
|
|
6730
|
-
lastCompletedAt: Date.now()
|
|
6731
|
-
});
|
|
6732
|
-
}
|
|
6733
|
-
async function revertRoutineStreak(uid, taskId) {
|
|
5954
|
+
async function runWrite(opts) {
|
|
6734
5955
|
try {
|
|
6735
|
-
const
|
|
6736
|
-
|
|
6737
|
-
|
|
6738
|
-
|
|
6739
|
-
|
|
6740
|
-
|
|
6741
|
-
|
|
6742
|
-
|
|
5956
|
+
const payload = await withSpinner(
|
|
5957
|
+
useSpinner(opts.global),
|
|
5958
|
+
opts.spinnerMessage ?? "Updating...",
|
|
5959
|
+
opts.fn
|
|
5960
|
+
);
|
|
5961
|
+
const item = opts.dataKey ? payload[opts.dataKey] : payload;
|
|
5962
|
+
if (useJson(opts.global)) {
|
|
5963
|
+
printJson(selectFields(payload, opts.global.json));
|
|
5964
|
+
} else if (opts.onInteractive) {
|
|
5965
|
+
opts.onInteractive(payload);
|
|
5966
|
+
} else {
|
|
5967
|
+
if (opts.successMessage) console.log(opts.successMessage);
|
|
5968
|
+
if (item && Object.keys(item).length > 0) {
|
|
5969
|
+
outputResult(item, false);
|
|
5970
|
+
}
|
|
5971
|
+
}
|
|
5972
|
+
} catch (err) {
|
|
5973
|
+
outputError(err, useJson(opts.global));
|
|
6743
5974
|
}
|
|
6744
5975
|
}
|
|
6745
|
-
async function
|
|
5976
|
+
async function runDelete(opts) {
|
|
6746
5977
|
try {
|
|
6747
|
-
await
|
|
6748
|
-
|
|
5978
|
+
await withSpinner(
|
|
5979
|
+
useSpinner(opts.global),
|
|
5980
|
+
opts.spinnerMessage ?? "Deleting...",
|
|
5981
|
+
opts.fn
|
|
5982
|
+
);
|
|
5983
|
+
if (!opts.global.quiet) {
|
|
5984
|
+
console.log(opts.successMessage);
|
|
5985
|
+
}
|
|
5986
|
+
} catch (err) {
|
|
5987
|
+
outputError(err, useJson(opts.global));
|
|
6749
5988
|
}
|
|
6750
5989
|
}
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
5990
|
+
|
|
5991
|
+
// src/cli/lib/uid.ts
|
|
5992
|
+
init_credentials();
|
|
5993
|
+
init_errors();
|
|
5994
|
+
function requireUid() {
|
|
5995
|
+
const creds = loadCredentials();
|
|
5996
|
+
if (!creds) throw Errors.authRequired();
|
|
5997
|
+
return creds.uid;
|
|
6756
5998
|
}
|
|
5999
|
+
|
|
6000
|
+
// src/cli/services/tasks.ts
|
|
6001
|
+
init_api_client();
|
|
6757
6002
|
async function listTasks(uid, opts) {
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
opts.backlog ?
|
|
6761
|
-
|
|
6762
|
-
let pending = activeTasks;
|
|
6763
|
-
let completed = historyTasks;
|
|
6764
|
-
if (opts.backlog) {
|
|
6765
|
-
pending = pending.filter((t2) => t2.backlog === true);
|
|
6766
|
-
} else if (opts.date) {
|
|
6767
|
-
const today2 = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
6768
|
-
const isToday = opts.date === today2;
|
|
6769
|
-
if (isToday) {
|
|
6770
|
-
const cutoff = endOfDay(opts.date);
|
|
6771
|
-
pending = pending.filter((t2) => typeof t2.dueDate === "string" && t2.dueDate <= cutoff);
|
|
6772
|
-
} else {
|
|
6773
|
-
pending = pending.filter((t2) => typeof t2.dueDate === "string" && t2.dueDate.slice(0, 10) === opts.date);
|
|
6774
|
-
}
|
|
6775
|
-
completed = completed.filter((t2) => {
|
|
6776
|
-
if (typeof t2.dueDate !== "string") return false;
|
|
6777
|
-
return t2.dueDate.slice(0, 10) === opts.date;
|
|
6778
|
-
});
|
|
6779
|
-
}
|
|
6780
|
-
const applyTag = (items) => opts.tag ? items.filter((t2) => Array.isArray(t2.tags) && t2.tags.includes(opts.tag)) : items;
|
|
6781
|
-
completed.forEach((t2) => {
|
|
6782
|
-
t2.completed = true;
|
|
6003
|
+
return api.get("/api/tasks", {
|
|
6004
|
+
date: opts.date,
|
|
6005
|
+
backlog: opts.backlog ? "true" : void 0,
|
|
6006
|
+
tag: opts.tag
|
|
6783
6007
|
});
|
|
6784
|
-
const filteredPending = applyTag(pending);
|
|
6785
|
-
const filteredCompleted = applyTag(completed);
|
|
6786
|
-
const allTasks = [...filteredPending, ...filteredCompleted];
|
|
6787
|
-
return { tasks: allTasks, count: allTasks.length, pendingCount: filteredPending.length, completedCount: filteredCompleted.length };
|
|
6788
6008
|
}
|
|
6789
6009
|
async function getTask(uid, id) {
|
|
6790
|
-
|
|
6791
|
-
return getDoc(taskPath(uid, id));
|
|
6010
|
+
return api.get(`/api/tasks/${encodeURIComponent(id)}`);
|
|
6792
6011
|
}
|
|
6793
6012
|
async function createTask(uid, body) {
|
|
6794
|
-
|
|
6795
|
-
const dueDate = body.dueDate ?? null;
|
|
6796
|
-
const taskData = {
|
|
6797
|
-
text: body.text,
|
|
6798
|
-
userId: uid,
|
|
6799
|
-
isPublic: body.isPublic ?? true,
|
|
6800
|
-
completed: false,
|
|
6801
|
-
completedAt: null,
|
|
6802
|
-
createdAt: now2,
|
|
6803
|
-
dueDate,
|
|
6804
|
-
remindDate: null,
|
|
6805
|
-
tags: Array.isArray(body.tags) ? body.tags : [],
|
|
6806
|
-
assets: [],
|
|
6807
|
-
note: body.note ?? "",
|
|
6808
|
-
priority: typeof body.priority === "number" ? body.priority : 0,
|
|
6809
|
-
difficulty: body.difficulty ?? null,
|
|
6810
|
-
duration: typeof body.duration === "number" ? body.duration : 10,
|
|
6811
|
-
backlog: dueDate == null,
|
|
6812
|
-
parentTaskId: null,
|
|
6813
|
-
completions: 0,
|
|
6814
|
-
repeat: body.repeat ?? { type: "none", every: null, end: null, endDate: null, endAfter: null, monthDays: null, weekDays: null },
|
|
6815
|
-
subtasks: Array.isArray(body.subtasks) ? body.subtasks.map((s) => ({ id: crypto3.randomUUID(), text: s.text, completed: false })) : [],
|
|
6816
|
-
withTime: dueDate != null && !/\b00:00$/.test(dueDate),
|
|
6817
|
-
listPosition: null,
|
|
6818
|
-
source: "cli"
|
|
6819
|
-
};
|
|
6820
|
-
taskData.remindDate = getTaskRemindDate(taskData);
|
|
6821
|
-
const taskId = generateTaskId(taskData);
|
|
6822
|
-
const doc = await setDoc(taskPath(uid, taskId), taskData);
|
|
6823
|
-
const createResults = await Promise.allSettled([
|
|
6824
|
-
giveKarma(uid, "addTask", taskId, KARMA_POINTS.addTask, String(taskData.text)),
|
|
6825
|
-
addToOrdering(uid, taskId, "end"),
|
|
6826
|
-
incrementField(activityTotalsPath(uid), "tasks.active", 1)
|
|
6827
|
-
]);
|
|
6828
|
-
logSideEffectResults("createTask", createResults);
|
|
6829
|
-
return { task: doc, karma: KARMA_POINTS.addTask };
|
|
6013
|
+
return api.post("/api/tasks", body);
|
|
6830
6014
|
}
|
|
6831
6015
|
async function updateTask(uid, id, body) {
|
|
6832
|
-
|
|
6833
|
-
const allowed = ["text", "isPublic", "dueDate", "tags", "note", "priority", "difficulty", "duration", "repeat", "subtasks"];
|
|
6834
|
-
const update = {};
|
|
6835
|
-
const fieldMask = [];
|
|
6836
|
-
for (const key of allowed) {
|
|
6837
|
-
if (key in body) {
|
|
6838
|
-
update[key] = body[key];
|
|
6839
|
-
fieldMask.push(key);
|
|
6840
|
-
}
|
|
6841
|
-
}
|
|
6842
|
-
if ("dueDate" in update) {
|
|
6843
|
-
const newDueDate = update.dueDate;
|
|
6844
|
-
update.backlog = newDueDate == null;
|
|
6845
|
-
if (!fieldMask.includes("backlog")) fieldMask.push("backlog");
|
|
6846
|
-
}
|
|
6847
|
-
await updateDoc(taskPath(uid, id), update, fieldMask);
|
|
6848
|
-
if ("dueDate" in body) {
|
|
6849
|
-
await addToOrdering(uid, id, "end");
|
|
6850
|
-
}
|
|
6851
|
-
const updated = await getDoc(taskPath(uid, id));
|
|
6852
|
-
const recalculatedRemindDate = getTaskRemindDate(updated);
|
|
6853
|
-
if (updated.remindDate !== recalculatedRemindDate) {
|
|
6854
|
-
await updateDoc(taskPath(uid, id), { remindDate: recalculatedRemindDate }, ["remindDate"]);
|
|
6855
|
-
const final = await getDoc(taskPath(uid, id));
|
|
6856
|
-
return { task: final };
|
|
6857
|
-
}
|
|
6858
|
-
return { task: updated };
|
|
6016
|
+
return api.patch(`/api/tasks/${encodeURIComponent(id)}`, body);
|
|
6859
6017
|
}
|
|
6860
6018
|
async function deleteTask(uid, id) {
|
|
6861
|
-
|
|
6862
|
-
let taskData = {};
|
|
6863
|
-
try {
|
|
6864
|
-
taskData = await getDoc(taskPath(uid, id));
|
|
6865
|
-
} catch {
|
|
6866
|
-
}
|
|
6867
|
-
const hasData = Object.keys(taskData).length > 0;
|
|
6868
|
-
const writes = [
|
|
6869
|
-
{ type: "delete", path: taskPath(uid, id) }
|
|
6870
|
-
];
|
|
6871
|
-
if (hasData) {
|
|
6872
|
-
writes.push({
|
|
6873
|
-
type: "update",
|
|
6874
|
-
path: archivePath(uid, id),
|
|
6875
|
-
data: { ...taskData, archived: true, archivedAt: Date.now() }
|
|
6876
|
-
});
|
|
6877
|
-
writes.push({
|
|
6878
|
-
type: "transform",
|
|
6879
|
-
path: archiveCounterPath(uid),
|
|
6880
|
-
transforms: [{ field: "archivedTasksCount", increment: 1 }]
|
|
6881
|
-
});
|
|
6882
|
-
}
|
|
6883
|
-
await commit(writes);
|
|
6884
|
-
try {
|
|
6885
|
-
const historyDocs = await runQuery(`users/${uid}`, "tasksHistory", {
|
|
6886
|
-
where: [{ field: "parentTaskId", op: "EQUAL", value: id }],
|
|
6887
|
-
limit: MAX_TASKS_PER_REQUEST
|
|
6888
|
-
});
|
|
6889
|
-
const historyResults = await Promise.allSettled(historyDocs.map((h2) => deleteDoc(taskHistoryPath(uid, h2.id))));
|
|
6890
|
-
logSideEffectResults("deleteTask.history", historyResults);
|
|
6891
|
-
} catch (err) {
|
|
6892
|
-
process.stderr.write(`[warn] deleteTask.history: ${err instanceof Error ? err.message : String(err)}
|
|
6893
|
-
`);
|
|
6894
|
-
}
|
|
6895
|
-
const deleteResults = await Promise.allSettled([
|
|
6896
|
-
removeFromOrdering(uid, id),
|
|
6897
|
-
deleteRoutineStreak(uid, id),
|
|
6898
|
-
incrementField(activityTotalsPath(uid), "tasks.active", -1)
|
|
6899
|
-
]);
|
|
6900
|
-
logSideEffectResults("deleteTask", deleteResults);
|
|
6901
|
-
removeDailyStreak(id);
|
|
6902
|
-
return { deleted: true, taskText: String(taskData.text ?? ""), archived: hasData };
|
|
6019
|
+
return api.del(`/api/tasks/${encodeURIComponent(id)}`);
|
|
6903
6020
|
}
|
|
6904
6021
|
async function completeTask(uid, id, date) {
|
|
6905
|
-
|
|
6906
|
-
const task = await getDoc(taskPath(uid, id));
|
|
6907
|
-
const completedAt = date ? new Date(date).getTime() : Date.now();
|
|
6908
|
-
const repeating = isRepeating(task);
|
|
6909
|
-
const historyId = repeating ? `${id}-${completedAt}` : id;
|
|
6910
|
-
const historyData = {
|
|
6911
|
-
...task,
|
|
6912
|
-
id: historyId,
|
|
6913
|
-
completedAt,
|
|
6914
|
-
completed: true,
|
|
6915
|
-
parentTaskId: id,
|
|
6916
|
-
isHistoryTask: true,
|
|
6917
|
-
completions: (task.completions ?? 0) + 1,
|
|
6918
|
-
remindDate: null
|
|
6919
|
-
};
|
|
6920
|
-
delete historyData.id;
|
|
6921
|
-
const writes = [];
|
|
6922
|
-
if (repeating) {
|
|
6923
|
-
const nextDueDate = getNextDueDate(task);
|
|
6924
|
-
const resetSubtasks = (task.subtasks ?? []).map((s) => ({ ...s, completed: false }));
|
|
6925
|
-
const updatedData = {
|
|
6926
|
-
dueDate: nextDueDate,
|
|
6927
|
-
completions: (task.completions ?? 0) + 1,
|
|
6928
|
-
completedAt,
|
|
6929
|
-
subtasks: resetSubtasks
|
|
6930
|
-
};
|
|
6931
|
-
writes.push({
|
|
6932
|
-
type: "update",
|
|
6933
|
-
path: taskPath(uid, id),
|
|
6934
|
-
data: updatedData,
|
|
6935
|
-
fieldMask: Object.keys(updatedData)
|
|
6936
|
-
});
|
|
6937
|
-
} else {
|
|
6938
|
-
writes.push({ type: "delete", path: taskPath(uid, id) });
|
|
6939
|
-
}
|
|
6940
|
-
writes.push({
|
|
6941
|
-
type: "update",
|
|
6942
|
-
path: taskHistoryPath(uid, historyId),
|
|
6943
|
-
data: historyData
|
|
6944
|
-
});
|
|
6945
|
-
await commit(writes);
|
|
6946
|
-
if (repeating) {
|
|
6947
|
-
try {
|
|
6948
|
-
const afterUpdate = await getDoc(taskPath(uid, id));
|
|
6949
|
-
await updateDoc(taskPath(uid, id), { remindDate: getTaskRemindDate(afterUpdate) }, ["remindDate"]);
|
|
6950
|
-
} catch {
|
|
6951
|
-
}
|
|
6952
|
-
}
|
|
6953
|
-
const completeDateStr = date ?? formatDate(/* @__PURE__ */ new Date());
|
|
6954
|
-
const entityId = repeating ? `${id}_${completeDateStr.slice(0, 10)}` : id;
|
|
6955
|
-
const checksInRow = recordDailyStreak(id, completeDateStr);
|
|
6956
|
-
const karmaPoints = computeCompleteKarma(task, checksInRow);
|
|
6957
|
-
const sideEffects = [
|
|
6958
|
-
giveKarma(uid, "completeTask", entityId, karmaPoints, task.text),
|
|
6959
|
-
addToProgress(uid, id, completeDateStr),
|
|
6960
|
-
incrementField(activityTotalsPath(uid), "tasks.completed", 1)
|
|
6961
|
-
];
|
|
6962
|
-
if (repeating) {
|
|
6963
|
-
sideEffects.push(recordRoutineStreak(uid, id));
|
|
6964
|
-
} else {
|
|
6965
|
-
sideEffects.push(removeFromOrdering(uid, id));
|
|
6966
|
-
sideEffects.push(incrementField(activityTotalsPath(uid), "tasks.active", -1));
|
|
6967
|
-
}
|
|
6968
|
-
const completeResults = await Promise.allSettled(sideEffects);
|
|
6969
|
-
logSideEffectResults("completeTask", completeResults);
|
|
6970
|
-
return { completed: true, taskHistory: historyData, karma: karmaPoints, checksInRow, taskText: task.text };
|
|
6022
|
+
return api.post(`/api/tasks/${encodeURIComponent(id)}/complete`, date ? { date } : void 0);
|
|
6971
6023
|
}
|
|
6972
6024
|
async function uncompleteTask(uid, taskHistoryId) {
|
|
6973
|
-
|
|
6974
|
-
const history = await getDoc(taskHistoryPath(uid, taskHistoryId));
|
|
6975
|
-
const parentTaskId = history.parentTaskId;
|
|
6976
|
-
if (!parentTaskId) throw new Error("Not a history record");
|
|
6977
|
-
const repeating = isRepeating(history);
|
|
6978
|
-
const restoredTask = {
|
|
6979
|
-
...history,
|
|
6980
|
-
completed: false,
|
|
6981
|
-
completedAt: null,
|
|
6982
|
-
completions: Math.max(0, (history.completions ?? 0) - 1)
|
|
6983
|
-
};
|
|
6984
|
-
delete restoredTask.isHistoryTask;
|
|
6985
|
-
delete restoredTask.id;
|
|
6986
|
-
restoredTask.parentTaskId = null;
|
|
6987
|
-
if (repeating) {
|
|
6988
|
-
restoredTask.dueDate = history.dueDate;
|
|
6989
|
-
}
|
|
6990
|
-
const writes = [
|
|
6991
|
-
{ type: "update", path: taskPath(uid, parentTaskId), data: restoredTask },
|
|
6992
|
-
{ type: "delete", path: taskHistoryPath(uid, taskHistoryId) }
|
|
6993
|
-
];
|
|
6994
|
-
await commit(writes);
|
|
6995
|
-
try {
|
|
6996
|
-
const updated = await getDoc(taskPath(uid, parentTaskId));
|
|
6997
|
-
await updateDoc(taskPath(uid, parentTaskId), { remindDate: getTaskRemindDate(updated) }, ["remindDate"]);
|
|
6998
|
-
} catch {
|
|
6999
|
-
}
|
|
7000
|
-
const completedAtDate = history.completedAt ? formatDate(new Date(history.completedAt)) : history.dueDate ?? formatDate(/* @__PURE__ */ new Date());
|
|
7001
|
-
const entityId = repeating ? `${parentTaskId}_${completedAtDate.slice(0, 10)}` : parentTaskId;
|
|
7002
|
-
const sideEffects = [
|
|
7003
|
-
removeKarma(uid, "completeTask", entityId),
|
|
7004
|
-
removeFromProgress(uid, parentTaskId, completedAtDate),
|
|
7005
|
-
incrementField(activityTotalsPath(uid), "tasks.completed", -1)
|
|
7006
|
-
];
|
|
7007
|
-
revertDailyStreak(parentTaskId);
|
|
7008
|
-
if (repeating) {
|
|
7009
|
-
sideEffects.push(revertRoutineStreak(uid, parentTaskId));
|
|
7010
|
-
} else {
|
|
7011
|
-
sideEffects.push(addToOrdering(uid, parentTaskId, "end"));
|
|
7012
|
-
sideEffects.push(incrementField(activityTotalsPath(uid), "tasks.active", 1));
|
|
7013
|
-
}
|
|
7014
|
-
const uncompleteResults = await Promise.allSettled(sideEffects);
|
|
7015
|
-
logSideEffectResults("uncompleteTask", uncompleteResults);
|
|
7016
|
-
const final = await getDoc(taskPath(uid, parentTaskId));
|
|
7017
|
-
return { uncompleted: true, task: final, karmaReverted: true };
|
|
6025
|
+
return api.post(`/api/tasks/${encodeURIComponent(taskHistoryId)}/uncomplete`);
|
|
7018
6026
|
}
|
|
7019
6027
|
|
|
7020
6028
|
// src/cli/lib/format.ts
|
|
7021
|
-
init_define_ADMIN_UIDS();
|
|
7022
6029
|
var import_picocolors7 = __toESM(require_picocolors(), 1);
|
|
7023
|
-
function
|
|
6030
|
+
function formatDate(ts) {
|
|
7024
6031
|
if (ts == null) return "";
|
|
7025
6032
|
const d = typeof ts === "number" ? new Date(ts) : new Date(ts);
|
|
7026
6033
|
if (isNaN(d.getTime())) return String(ts);
|
|
@@ -7043,7 +6050,7 @@ function formatRelativeDate(ts) {
|
|
|
7043
6050
|
if (hours < 24) return `${hours}h ago`;
|
|
7044
6051
|
const days = Math.floor(hours / 24);
|
|
7045
6052
|
if (days < 30) return `${days}d ago`;
|
|
7046
|
-
return
|
|
6053
|
+
return formatDate(ts);
|
|
7047
6054
|
}
|
|
7048
6055
|
function formatTags(tags) {
|
|
7049
6056
|
if (!Array.isArray(tags) || tags.length === 0) return "";
|
|
@@ -7121,26 +6128,7 @@ init_prompts();
|
|
|
7121
6128
|
init_tty();
|
|
7122
6129
|
init_errors();
|
|
7123
6130
|
|
|
7124
|
-
// src/cli/lib/parse-date.ts
|
|
7125
|
-
init_define_ADMIN_UIDS();
|
|
7126
|
-
|
|
7127
|
-
// node_modules/chrono-node/dist/esm/index.js
|
|
7128
|
-
init_define_ADMIN_UIDS();
|
|
7129
|
-
|
|
7130
|
-
// node_modules/chrono-node/dist/esm/locales/en/index.js
|
|
7131
|
-
init_define_ADMIN_UIDS();
|
|
7132
|
-
|
|
7133
|
-
// node_modules/chrono-node/dist/esm/chrono.js
|
|
7134
|
-
init_define_ADMIN_UIDS();
|
|
7135
|
-
|
|
7136
|
-
// node_modules/chrono-node/dist/esm/results.js
|
|
7137
|
-
init_define_ADMIN_UIDS();
|
|
7138
|
-
|
|
7139
|
-
// node_modules/chrono-node/dist/esm/utils/dates.js
|
|
7140
|
-
init_define_ADMIN_UIDS();
|
|
7141
|
-
|
|
7142
6131
|
// node_modules/chrono-node/dist/esm/types.js
|
|
7143
|
-
init_define_ADMIN_UIDS();
|
|
7144
6132
|
var Meridiem;
|
|
7145
6133
|
(function(Meridiem2) {
|
|
7146
6134
|
Meridiem2[Meridiem2["AM"] = 0] = "AM";
|
|
@@ -7199,7 +6187,6 @@ function implySimilarTime(component, target) {
|
|
|
7199
6187
|
}
|
|
7200
6188
|
|
|
7201
6189
|
// node_modules/chrono-node/dist/esm/timezone.js
|
|
7202
|
-
init_define_ADMIN_UIDS();
|
|
7203
6190
|
var TIMEZONE_ABBR_MAP = {
|
|
7204
6191
|
ACDT: 630,
|
|
7205
6192
|
ACST: 570,
|
|
@@ -7469,7 +6456,6 @@ function toTimezoneOffset(timezoneInput, date, timezoneOverrides = {}) {
|
|
|
7469
6456
|
}
|
|
7470
6457
|
|
|
7471
6458
|
// node_modules/chrono-node/dist/esm/calculation/duration.js
|
|
7472
|
-
init_define_ADMIN_UIDS();
|
|
7473
6459
|
var EmptyDuration = {
|
|
7474
6460
|
day: 0,
|
|
7475
6461
|
second: 0,
|
|
@@ -7870,17 +6856,7 @@ var ParsingResult = class _ParsingResult {
|
|
|
7870
6856
|
}
|
|
7871
6857
|
};
|
|
7872
6858
|
|
|
7873
|
-
// node_modules/chrono-node/dist/esm/locales/en/configuration.js
|
|
7874
|
-
init_define_ADMIN_UIDS();
|
|
7875
|
-
|
|
7876
|
-
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.js
|
|
7877
|
-
init_define_ADMIN_UIDS();
|
|
7878
|
-
|
|
7879
|
-
// node_modules/chrono-node/dist/esm/locales/en/constants.js
|
|
7880
|
-
init_define_ADMIN_UIDS();
|
|
7881
|
-
|
|
7882
6859
|
// node_modules/chrono-node/dist/esm/utils/pattern.js
|
|
7883
|
-
init_define_ADMIN_UIDS();
|
|
7884
6860
|
function repeatedTimeunitPattern(prefix, singleTimeunitPattern, connectorPattern = "\\s{0,5},?\\s{0,5}") {
|
|
7885
6861
|
const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:");
|
|
7886
6862
|
return `${prefix}${singleTimeunitPatternNoCapture}(?:${connectorPattern}${singleTimeunitPatternNoCapture}){0,10}`;
|
|
@@ -7902,7 +6878,6 @@ function matchAnyPattern(dictionary) {
|
|
|
7902
6878
|
}
|
|
7903
6879
|
|
|
7904
6880
|
// node_modules/chrono-node/dist/esm/calculation/years.js
|
|
7905
|
-
init_define_ADMIN_UIDS();
|
|
7906
6881
|
function findMostLikelyADYear(yearNumber) {
|
|
7907
6882
|
if (yearNumber < 100) {
|
|
7908
6883
|
if (yearNumber > 50) {
|
|
@@ -8180,7 +7155,6 @@ function collectDateTimeFragment(fragments, match) {
|
|
|
8180
7155
|
}
|
|
8181
7156
|
|
|
8182
7157
|
// node_modules/chrono-node/dist/esm/common/parsers/AbstractParserWithWordBoundary.js
|
|
8183
|
-
init_define_ADMIN_UIDS();
|
|
8184
7158
|
var AbstractParserWithWordBoundaryChecking = class {
|
|
8185
7159
|
innerPatternHasChange(context, currentInnerPattern) {
|
|
8186
7160
|
return this.innerPattern(context) !== currentInnerPattern;
|
|
@@ -8240,7 +7214,6 @@ var ENTimeUnitWithinFormatParser = class extends AbstractParserWithWordBoundaryC
|
|
|
8240
7214
|
};
|
|
8241
7215
|
|
|
8242
7216
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameLittleEndianParser.js
|
|
8243
|
-
init_define_ADMIN_UIDS();
|
|
8244
7217
|
var PATTERN = new RegExp(`(?:on\\s{0,3})?(${ORDINAL_NUMBER_PATTERN})(?:\\s{0,3}(?:to|\\-|\\\u2013|until|through|till)?\\s{0,3}(${ORDINAL_NUMBER_PATTERN}))?(?:-|/|\\s{0,3}(?:of)?\\s{0,3})(${matchAnyPattern(MONTH_DICTIONARY)})(?:(?:-|/|,?\\s{0,3})(${YEAR_PATTERN}(?!\\w)))?(?=\\W|$)`, "i");
|
|
8245
7218
|
var DATE_GROUP = 1;
|
|
8246
7219
|
var DATE_TO_GROUP = 2;
|
|
@@ -8277,7 +7250,6 @@ var ENMonthNameLittleEndianParser = class extends AbstractParserWithWordBoundary
|
|
|
8277
7250
|
};
|
|
8278
7251
|
|
|
8279
7252
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameMiddleEndianParser.js
|
|
8280
|
-
init_define_ADMIN_UIDS();
|
|
8281
7253
|
var PATTERN2 = new RegExp(`(${matchAnyPattern(MONTH_DICTIONARY)})(?:-|/|\\s*,?\\s*)(${ORDINAL_NUMBER_PATTERN})(?!\\s*(?:am|pm))\\s*(?:(?:to|\\-)\\s*(${ORDINAL_NUMBER_PATTERN})\\s*)?(?:(?:-|/|\\s*,\\s*|\\s+)(${YEAR_PATTERN}))?(?=\\W|$)(?!\\:\\d)`, "i");
|
|
8282
7254
|
var MONTH_NAME_GROUP2 = 1;
|
|
8283
7255
|
var DATE_GROUP2 = 2;
|
|
@@ -8327,7 +7299,6 @@ var ENMonthNameMiddleEndianParser = class extends AbstractParserWithWordBoundary
|
|
|
8327
7299
|
};
|
|
8328
7300
|
|
|
8329
7301
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameParser.js
|
|
8330
|
-
init_define_ADMIN_UIDS();
|
|
8331
7302
|
var PATTERN3 = new RegExp(`((?:in)\\s*)?(${matchAnyPattern(MONTH_DICTIONARY)})\\s*(?:(?:,|-|of)?\\s*(${YEAR_PATTERN})?)?(?=[^\\s\\w]|\\s+[^0-9]|\\s+$|$)`, "i");
|
|
8332
7303
|
var PREFIX_GROUP = 1;
|
|
8333
7304
|
var MONTH_NAME_GROUP3 = 2;
|
|
@@ -8358,7 +7329,6 @@ var ENMonthNameParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
|
8358
7329
|
};
|
|
8359
7330
|
|
|
8360
7331
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENYearMonthDayParser.js
|
|
8361
|
-
init_define_ADMIN_UIDS();
|
|
8362
7332
|
var PATTERN4 = new RegExp(`([0-9]{4})[-\\.\\/\\s](?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[-\\.\\/\\s]([0-9]{1,2})(?=\\W|$)`, "i");
|
|
8363
7333
|
var YEAR_NUMBER_GROUP = 1;
|
|
8364
7334
|
var MONTH_NAME_GROUP4 = 2;
|
|
@@ -8397,7 +7367,6 @@ var ENYearMonthDayParser = class extends AbstractParserWithWordBoundaryChecking
|
|
|
8397
7367
|
};
|
|
8398
7368
|
|
|
8399
7369
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENSlashMonthFormatParser.js
|
|
8400
|
-
init_define_ADMIN_UIDS();
|
|
8401
7370
|
var PATTERN5 = new RegExp("([0-9]|0[1-9]|1[012])/([0-9]{4})", "i");
|
|
8402
7371
|
var MONTH_GROUP = 1;
|
|
8403
7372
|
var YEAR_GROUP4 = 2;
|
|
@@ -8412,11 +7381,7 @@ var ENSlashMonthFormatParser = class extends AbstractParserWithWordBoundaryCheck
|
|
|
8412
7381
|
}
|
|
8413
7382
|
};
|
|
8414
7383
|
|
|
8415
|
-
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeExpressionParser.js
|
|
8416
|
-
init_define_ADMIN_UIDS();
|
|
8417
|
-
|
|
8418
7384
|
// node_modules/chrono-node/dist/esm/common/parsers/AbstractTimeExpressionParser.js
|
|
8419
|
-
init_define_ADMIN_UIDS();
|
|
8420
7385
|
function primaryTimePattern(leftBoundary, primaryPrefix, primarySuffix, flags) {
|
|
8421
7386
|
return new RegExp(`${leftBoundary}${primaryPrefix}(\\d{1,4})(?:(?:\\.|:|\uFF1A)(\\d{1,2})(?:(?::|\uFF1A)(\\d{2})(?:\\.(\\d{1,6}))?)?)?(?:\\s*(a\\.m\\.|p\\.m\\.|am?|pm?))?${primarySuffix}`, flags);
|
|
8422
7387
|
}
|
|
@@ -8772,7 +7737,6 @@ var ENTimeExpressionParser = class extends AbstractTimeExpressionParser {
|
|
|
8772
7737
|
};
|
|
8773
7738
|
|
|
8774
7739
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js
|
|
8775
|
-
init_define_ADMIN_UIDS();
|
|
8776
7740
|
var PATTERN6 = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=\\W|$)`, "i");
|
|
8777
7741
|
var STRICT_PATTERN = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=\\W|$)`, "i");
|
|
8778
7742
|
var ENTimeUnitAgoFormatParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
@@ -8794,7 +7758,6 @@ var ENTimeUnitAgoFormatParser = class extends AbstractParserWithWordBoundaryChec
|
|
|
8794
7758
|
};
|
|
8795
7759
|
|
|
8796
7760
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js
|
|
8797
|
-
init_define_ADMIN_UIDS();
|
|
8798
7761
|
var PATTERN7 = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:later|after|from now|henceforth|forward|out)(?=(?:\\W|$))`, "i");
|
|
8799
7762
|
var STRICT_PATTERN2 = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(later|after|from now)(?=\\W|$)`, "i");
|
|
8800
7763
|
var GROUP_NUM_TIMEUNITS = 1;
|
|
@@ -8816,14 +7779,7 @@ var ENTimeUnitLaterFormatParser = class extends AbstractParserWithWordBoundaryCh
|
|
|
8816
7779
|
}
|
|
8817
7780
|
};
|
|
8818
7781
|
|
|
8819
|
-
// node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeDateRangeRefiner.js
|
|
8820
|
-
init_define_ADMIN_UIDS();
|
|
8821
|
-
|
|
8822
|
-
// node_modules/chrono-node/dist/esm/common/refiners/AbstractMergeDateRangeRefiner.js
|
|
8823
|
-
init_define_ADMIN_UIDS();
|
|
8824
|
-
|
|
8825
7782
|
// node_modules/chrono-node/dist/esm/common/abstractRefiners.js
|
|
8826
|
-
init_define_ADMIN_UIDS();
|
|
8827
7783
|
var Filter = class {
|
|
8828
7784
|
refine(context, results) {
|
|
8829
7785
|
return results.filter((r) => this.isValid(context, r));
|
|
@@ -8921,14 +7877,7 @@ var ENMergeDateRangeRefiner = class extends AbstractMergeDateRangeRefiner {
|
|
|
8921
7877
|
}
|
|
8922
7878
|
};
|
|
8923
7879
|
|
|
8924
|
-
// node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeDateTimeRefiner.js
|
|
8925
|
-
init_define_ADMIN_UIDS();
|
|
8926
|
-
|
|
8927
|
-
// node_modules/chrono-node/dist/esm/common/refiners/AbstractMergeDateTimeRefiner.js
|
|
8928
|
-
init_define_ADMIN_UIDS();
|
|
8929
|
-
|
|
8930
7880
|
// node_modules/chrono-node/dist/esm/calculation/mergingCalculation.js
|
|
8931
|
-
init_define_ADMIN_UIDS();
|
|
8932
7881
|
function mergeDateTimeResult(dateResult, timeResult) {
|
|
8933
7882
|
const result = dateResult.clone();
|
|
8934
7883
|
const beginDate = dateResult.start;
|
|
@@ -9013,11 +7962,7 @@ var ENMergeDateTimeRefiner = class extends AbstractMergeDateTimeRefiner {
|
|
|
9013
7962
|
}
|
|
9014
7963
|
};
|
|
9015
7964
|
|
|
9016
|
-
// node_modules/chrono-node/dist/esm/configurations.js
|
|
9017
|
-
init_define_ADMIN_UIDS();
|
|
9018
|
-
|
|
9019
7965
|
// node_modules/chrono-node/dist/esm/common/refiners/ExtractTimezoneAbbrRefiner.js
|
|
9020
|
-
init_define_ADMIN_UIDS();
|
|
9021
7966
|
var TIMEZONE_NAME_PATTERN = new RegExp("^\\s*,?\\s*\\(?([A-Z]{2,4})\\)?(?=\\W|$)", "i");
|
|
9022
7967
|
var ExtractTimezoneAbbrRefiner = class {
|
|
9023
7968
|
timezoneOverrides;
|
|
@@ -9069,7 +8014,6 @@ var ExtractTimezoneAbbrRefiner = class {
|
|
|
9069
8014
|
};
|
|
9070
8015
|
|
|
9071
8016
|
// node_modules/chrono-node/dist/esm/common/refiners/ExtractTimezoneOffsetRefiner.js
|
|
9072
|
-
init_define_ADMIN_UIDS();
|
|
9073
8017
|
var TIMEZONE_OFFSET_PATTERN = new RegExp("^\\s*(?:\\(?(?:GMT|UTC)\\s?)?([+-])(\\d{1,2})(?::?(\\d{2}))?\\)?", "i");
|
|
9074
8018
|
var TIMEZONE_OFFSET_SIGN_GROUP = 1;
|
|
9075
8019
|
var TIMEZONE_OFFSET_HOUR_OFFSET_GROUP = 2;
|
|
@@ -9108,7 +8052,6 @@ var ExtractTimezoneOffsetRefiner = class {
|
|
|
9108
8052
|
};
|
|
9109
8053
|
|
|
9110
8054
|
// node_modules/chrono-node/dist/esm/common/refiners/OverlapRemovalRefiner.js
|
|
9111
|
-
init_define_ADMIN_UIDS();
|
|
9112
8055
|
var OverlapRemovalRefiner = class {
|
|
9113
8056
|
refine(context, results) {
|
|
9114
8057
|
if (results.length < 2) {
|
|
@@ -9145,7 +8088,6 @@ var OverlapRemovalRefiner = class {
|
|
|
9145
8088
|
};
|
|
9146
8089
|
|
|
9147
8090
|
// node_modules/chrono-node/dist/esm/common/refiners/ForwardDateRefiner.js
|
|
9148
|
-
init_define_ADMIN_UIDS();
|
|
9149
8091
|
var ForwardDateRefiner = class {
|
|
9150
8092
|
refine(context, results) {
|
|
9151
8093
|
if (!context.option.forwardDate) {
|
|
@@ -9211,7 +8153,6 @@ var ForwardDateRefiner = class {
|
|
|
9211
8153
|
};
|
|
9212
8154
|
|
|
9213
8155
|
// node_modules/chrono-node/dist/esm/common/refiners/UnlikelyFormatFilter.js
|
|
9214
|
-
init_define_ADMIN_UIDS();
|
|
9215
8156
|
var UnlikelyFormatFilter = class extends Filter {
|
|
9216
8157
|
strictMode;
|
|
9217
8158
|
constructor(strictMode) {
|
|
@@ -9254,7 +8195,6 @@ var UnlikelyFormatFilter = class extends Filter {
|
|
|
9254
8195
|
};
|
|
9255
8196
|
|
|
9256
8197
|
// node_modules/chrono-node/dist/esm/common/parsers/ISOFormatParser.js
|
|
9257
|
-
init_define_ADMIN_UIDS();
|
|
9258
8198
|
var PATTERN8 = new RegExp("([0-9]{4})\\-([0-9]{1,2})\\-([0-9]{1,2})(?:T([0-9]{1,2}):([0-9]{1,2})(?::([0-9]{1,2})(?:\\.(\\d{1,4}))?)?(Z|([+-]\\d{2}):?(\\d{2})?)?)?(?=\\W|$)", "i");
|
|
9259
8199
|
var YEAR_NUMBER_GROUP2 = 1;
|
|
9260
8200
|
var MONTH_NUMBER_GROUP2 = 2;
|
|
@@ -9308,7 +8248,6 @@ var ISOFormatParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
|
9308
8248
|
};
|
|
9309
8249
|
|
|
9310
8250
|
// node_modules/chrono-node/dist/esm/common/refiners/MergeWeekdayComponentRefiner.js
|
|
9311
|
-
init_define_ADMIN_UIDS();
|
|
9312
8251
|
var MergeWeekdayComponentRefiner = class extends MergingRefiner {
|
|
9313
8252
|
mergeResults(textBetween, currentResult, nextResult) {
|
|
9314
8253
|
const newResult = nextResult.clone();
|
|
@@ -9339,11 +8278,7 @@ function includeCommonConfiguration(configuration2, strictMode = false) {
|
|
|
9339
8278
|
return configuration2;
|
|
9340
8279
|
}
|
|
9341
8280
|
|
|
9342
|
-
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENCasualDateParser.js
|
|
9343
|
-
init_define_ADMIN_UIDS();
|
|
9344
|
-
|
|
9345
8281
|
// node_modules/chrono-node/dist/esm/common/casualReferences.js
|
|
9346
|
-
init_define_ADMIN_UIDS();
|
|
9347
8282
|
function now(reference) {
|
|
9348
8283
|
const targetDate = reference.getDateWithAdjustedTimezone();
|
|
9349
8284
|
const component = new ParsingComponents(reference, {});
|
|
@@ -9489,7 +8424,6 @@ var ENCasualDateParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
|
9489
8424
|
};
|
|
9490
8425
|
|
|
9491
8426
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENCasualTimeParser.js
|
|
9492
|
-
init_define_ADMIN_UIDS();
|
|
9493
8427
|
var PATTERN10 = /(?:this)?\s{0,3}(morning|afternoon|evening|night|midnight|midday|noon)(?=\W|$)/i;
|
|
9494
8428
|
var ENCasualTimeParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
9495
8429
|
innerPattern() {
|
|
@@ -9523,11 +8457,7 @@ var ENCasualTimeParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
|
9523
8457
|
}
|
|
9524
8458
|
};
|
|
9525
8459
|
|
|
9526
|
-
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENWeekdayParser.js
|
|
9527
|
-
init_define_ADMIN_UIDS();
|
|
9528
|
-
|
|
9529
8460
|
// node_modules/chrono-node/dist/esm/calculation/weekdays.js
|
|
9530
|
-
init_define_ADMIN_UIDS();
|
|
9531
8461
|
function createParsingComponentsAtWeekday(reference, weekday, modifier) {
|
|
9532
8462
|
const refDate = reference.getDateWithAdjustedTimezone();
|
|
9533
8463
|
const daysToWeekday = getDaysToWeekday(refDate, weekday, modifier);
|
|
@@ -9630,7 +8560,6 @@ var ENWeekdayParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
|
9630
8560
|
};
|
|
9631
8561
|
|
|
9632
8562
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENRelativeDateFormatParser.js
|
|
9633
|
-
init_define_ADMIN_UIDS();
|
|
9634
8563
|
var PATTERN12 = new RegExp(`(this|last|past|next|after\\s*this)\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})(?=\\s*)(?=\\W|$)`, "i");
|
|
9635
8564
|
var MODIFIER_WORD_GROUP = 1;
|
|
9636
8565
|
var RELATIVE_WORD_GROUP = 2;
|
|
@@ -9676,7 +8605,6 @@ var ENRelativeDateFormatParser = class extends AbstractParserWithWordBoundaryChe
|
|
|
9676
8605
|
};
|
|
9677
8606
|
|
|
9678
8607
|
// node_modules/chrono-node/dist/esm/common/parsers/SlashDateFormatParser.js
|
|
9679
|
-
init_define_ADMIN_UIDS();
|
|
9680
8608
|
var PATTERN13 = new RegExp("([^\\d]|^)([0-3]{0,1}[0-9]{1})[\\/\\.\\-]([0-3]{0,1}[0-9]{1})(?:[\\/\\.\\-]([0-9]{4}|[0-9]{2}))?(\\W|$)", "i");
|
|
9681
8609
|
var OPENING_GROUP = 1;
|
|
9682
8610
|
var ENDING_GROUP = 5;
|
|
@@ -9745,7 +8673,6 @@ var SlashDateFormatParser = class {
|
|
|
9745
8673
|
};
|
|
9746
8674
|
|
|
9747
8675
|
// node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js
|
|
9748
|
-
init_define_ADMIN_UIDS();
|
|
9749
8676
|
var PATTERN14 = new RegExp(`(this|last|past|next|after|\\+|-)\\s*(${TIME_UNITS_PATTERN})(?=\\W|$)`, "i");
|
|
9750
8677
|
var PATTERN_NO_ABBR = new RegExp(`(this|last|past|next|after|\\+|-)\\s*(${TIME_UNITS_NO_ABBR_PATTERN})(?=\\W|$)`, "i");
|
|
9751
8678
|
var ENTimeUnitCasualRelativeFormatParser = class extends AbstractParserWithWordBoundaryChecking {
|
|
@@ -9775,7 +8702,6 @@ var ENTimeUnitCasualRelativeFormatParser = class extends AbstractParserWithWordB
|
|
|
9775
8702
|
};
|
|
9776
8703
|
|
|
9777
8704
|
// node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeRelativeAfterDateRefiner.js
|
|
9778
|
-
init_define_ADMIN_UIDS();
|
|
9779
8705
|
function IsPositiveFollowingReference(result) {
|
|
9780
8706
|
return result.text.match(/^[+-]/i) != null;
|
|
9781
8707
|
}
|
|
@@ -9800,7 +8726,6 @@ var ENMergeRelativeAfterDateRefiner = class extends MergingRefiner {
|
|
|
9800
8726
|
};
|
|
9801
8727
|
|
|
9802
8728
|
// node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeRelativeFollowByDateRefiner.js
|
|
9803
|
-
init_define_ADMIN_UIDS();
|
|
9804
8729
|
function hasImpliedEarlierReferenceDate(result) {
|
|
9805
8730
|
return result.text.match(/\s+(before|from)$/i) != null;
|
|
9806
8731
|
}
|
|
@@ -9831,7 +8756,6 @@ var ENMergeRelativeFollowByDateRefiner = class extends MergingRefiner {
|
|
|
9831
8756
|
};
|
|
9832
8757
|
|
|
9833
8758
|
// node_modules/chrono-node/dist/esm/locales/en/refiners/ENExtractYearSuffixRefiner.js
|
|
9834
|
-
init_define_ADMIN_UIDS();
|
|
9835
8759
|
var YEAR_SUFFIX_PATTERN = new RegExp(`^\\s*(${YEAR_PATTERN})`, "i");
|
|
9836
8760
|
var YEAR_GROUP6 = 1;
|
|
9837
8761
|
var ENExtractYearSuffixRefiner = class {
|
|
@@ -9863,7 +8787,6 @@ var ENExtractYearSuffixRefiner = class {
|
|
|
9863
8787
|
};
|
|
9864
8788
|
|
|
9865
8789
|
// node_modules/chrono-node/dist/esm/locales/en/refiners/ENUnlikelyFormatFilter.js
|
|
9866
|
-
init_define_ADMIN_UIDS();
|
|
9867
8790
|
var ENUnlikelyFormatFilter = class extends Filter {
|
|
9868
8791
|
constructor() {
|
|
9869
8792
|
super();
|
|
@@ -10045,7 +8968,7 @@ var GB = new Chrono(configuration.createCasualConfiguration(true));
|
|
|
10045
8968
|
|
|
10046
8969
|
// node_modules/chrono-node/dist/esm/index.js
|
|
10047
8970
|
var casual2 = casual;
|
|
10048
|
-
function
|
|
8971
|
+
function parseDate(text, ref, option) {
|
|
10049
8972
|
return casual2.parseDate(text, ref, option);
|
|
10050
8973
|
}
|
|
10051
8974
|
|
|
@@ -10054,7 +8977,7 @@ function parseHumanDate(input) {
|
|
|
10054
8977
|
const trimmed = input.trim();
|
|
10055
8978
|
if (!trimmed) return null;
|
|
10056
8979
|
if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) return trimmed;
|
|
10057
|
-
const parsed =
|
|
8980
|
+
const parsed = parseDate(trimmed, /* @__PURE__ */ new Date(), { forwardDate: true });
|
|
10058
8981
|
if (!parsed) return null;
|
|
10059
8982
|
const date = parsed.toISOString().slice(0, 10);
|
|
10060
8983
|
const hours = parsed.getHours();
|
|
@@ -10072,10 +8995,9 @@ function parseHumanDateOnly(input) {
|
|
|
10072
8995
|
}
|
|
10073
8996
|
|
|
10074
8997
|
// src/cli/lib/stdin.ts
|
|
10075
|
-
|
|
10076
|
-
var fs4 = __toESM(require("fs"), 1);
|
|
8998
|
+
var fs3 = __toESM(require("fs"), 1);
|
|
10077
8999
|
function readStdinLines() {
|
|
10078
|
-
const input =
|
|
9000
|
+
const input = fs3.readFileSync(0, "utf8");
|
|
10079
9001
|
return input.split("\n").map((l) => l.trim()).filter(Boolean);
|
|
10080
9002
|
}
|
|
10081
9003
|
|
|
@@ -10095,7 +9017,7 @@ async function pickTask(uid, id, actionName) {
|
|
|
10095
9017
|
message: `Select task to ${actionName}`,
|
|
10096
9018
|
options: pending.map((t2) => ({
|
|
10097
9019
|
value: t2.id,
|
|
10098
|
-
label: `${truncate(
|
|
9020
|
+
label: `${truncate(t2.text, 50)} ${import_picocolors8.default.dim(t2.id)}`
|
|
10099
9021
|
}))
|
|
10100
9022
|
});
|
|
10101
9023
|
return selected;
|
|
@@ -10120,13 +9042,12 @@ function extractTime(dueDate) {
|
|
|
10120
9042
|
const time = parts[1];
|
|
10121
9043
|
return time === "00:00" ? "" : time;
|
|
10122
9044
|
}
|
|
10123
|
-
function
|
|
10124
|
-
|
|
10125
|
-
return !!repeat?.type && repeat.type !== "none";
|
|
9045
|
+
function isRepeating(t2) {
|
|
9046
|
+
return !!t2.repeat?.type && t2.repeat.type !== "none";
|
|
10126
9047
|
}
|
|
10127
9048
|
function getCheckIndicator(t2) {
|
|
10128
9049
|
if (t2.completed) return import_picocolors8.default.green(SYM.check);
|
|
10129
|
-
if (
|
|
9050
|
+
if (isRepeating(t2)) return import_picocolors8.default.blue(SYM.repeat);
|
|
10130
9051
|
return import_picocolors8.default.dim(SYM.circle);
|
|
10131
9052
|
}
|
|
10132
9053
|
function sortTasksForDisplay(tasks) {
|
|
@@ -10134,16 +9055,16 @@ function sortTasksForDisplay(tasks) {
|
|
|
10134
9055
|
return [...tasks].sort((a, b) => {
|
|
10135
9056
|
const timeA = extractTime(a.dueDate);
|
|
10136
9057
|
const timeB = extractTime(b.dueDate);
|
|
10137
|
-
const repA =
|
|
10138
|
-
const repB =
|
|
9058
|
+
const repA = isRepeating(a);
|
|
9059
|
+
const repB = isRepeating(b);
|
|
10139
9060
|
if (timeA && !timeB) return -1;
|
|
10140
9061
|
if (!timeA && timeB) return 1;
|
|
10141
9062
|
if (timeA && timeB) return timeA.localeCompare(timeB);
|
|
10142
9063
|
if (repA && !repB) return -1;
|
|
10143
9064
|
if (!repA && repB) return 1;
|
|
10144
9065
|
if (repA && repB) {
|
|
10145
|
-
const ra = a.repeat
|
|
10146
|
-
const rb = b.repeat
|
|
9066
|
+
const ra = a.repeat.type ?? "";
|
|
9067
|
+
const rb = b.repeat.type ?? "";
|
|
10147
9068
|
return (repeatOrder[ra] ?? 99) - (repeatOrder[rb] ?? 99);
|
|
10148
9069
|
}
|
|
10149
9070
|
return 0;
|
|
@@ -10155,7 +9076,7 @@ function printTaskDetail(t2) {
|
|
|
10155
9076
|
printRecord([
|
|
10156
9077
|
["ID", dim(t2.id)],
|
|
10157
9078
|
["Text", t2.text],
|
|
10158
|
-
["Due",
|
|
9079
|
+
["Due", formatDate(t2.dueDate) || dim("none (backlog)")],
|
|
10159
9080
|
["Status", t2.completed ? import_picocolors8.default.green("completed") : import_picocolors8.default.yellow("pending")],
|
|
10160
9081
|
["Tags", formatTags(t2.tags) || dim("none")],
|
|
10161
9082
|
["Difficulty", formatDifficulty(t2.difficulty) || dim("not set")],
|
|
@@ -10164,18 +9085,18 @@ function printTaskDetail(t2) {
|
|
|
10164
9085
|
["Note", t2.note || dim("none")],
|
|
10165
9086
|
["Public", t2.isPublic ? import_picocolors8.default.green("yes") : import_picocolors8.default.yellow("no")],
|
|
10166
9087
|
["Completions", String(t2.completions ?? 0)],
|
|
10167
|
-
["Created",
|
|
9088
|
+
["Created", formatDate(t2.createdAt)]
|
|
10168
9089
|
]);
|
|
10169
9090
|
console.log("");
|
|
10170
9091
|
}
|
|
10171
9092
|
function printTaskLine(t2) {
|
|
10172
9093
|
const check = getCheckIndicator(t2);
|
|
10173
|
-
const rawText = truncate(
|
|
9094
|
+
const rawText = truncate(t2.text, 50);
|
|
10174
9095
|
const text = t2.completed ? import_picocolors8.default.strikethrough(import_picocolors8.default.dim(rawText)) : rawText;
|
|
10175
9096
|
const time = extractTime(t2.dueDate);
|
|
10176
9097
|
const tags = formatTags(t2.tags);
|
|
10177
9098
|
const difficulty = formatDifficulty(t2.difficulty);
|
|
10178
|
-
const id = import_picocolors8.default.dim(
|
|
9099
|
+
const id = import_picocolors8.default.dim(t2.id);
|
|
10179
9100
|
const parts = [check, text];
|
|
10180
9101
|
if (time) parts.push(import_picocolors8.default.cyan(time));
|
|
10181
9102
|
if (tags) parts.push(tags);
|
|
@@ -10204,8 +9125,7 @@ function registerTasksCommands(program3) {
|
|
|
10204
9125
|
console.log(` ${import_picocolors8.default.bold("Backlog")} ${import_picocolors8.default.dim(`(${items.length})`)}`);
|
|
10205
9126
|
} else {
|
|
10206
9127
|
const viewDate = date ? /* @__PURE__ */ new Date(date + "T00:00:00") : /* @__PURE__ */ new Date();
|
|
10207
|
-
|
|
10208
|
-
console.log(formatWeekdayHeader(viewDate, streakCount));
|
|
9128
|
+
console.log(formatWeekdayHeader(viewDate, completed.length));
|
|
10209
9129
|
}
|
|
10210
9130
|
const tagLine = formatTagsSummary(items);
|
|
10211
9131
|
if (tagLine) console.log(`
|
|
@@ -10412,11 +9332,10 @@ Examples:
|
|
|
10412
9332
|
dataKey: "task",
|
|
10413
9333
|
spinnerMessage: "Creating task...",
|
|
10414
9334
|
onInteractive: (_task, payload) => {
|
|
10415
|
-
const task = payload
|
|
9335
|
+
const { task, karma } = payload;
|
|
10416
9336
|
const check = import_picocolors8.default.green(SYM.check);
|
|
10417
9337
|
console.log(`
|
|
10418
9338
|
${check} Created ${task.text}`);
|
|
10419
|
-
const karma = payload.karma;
|
|
10420
9339
|
if (karma) {
|
|
10421
9340
|
console.log(` ${formatKarmaGain(karma)}${" ".repeat(20)}${import_picocolors8.default.dim(task.id)}`);
|
|
10422
9341
|
}
|
|
@@ -10471,9 +9390,9 @@ Examples:
|
|
|
10471
9390
|
fn: () => updateTask(uid, taskId, body),
|
|
10472
9391
|
dataKey: "task",
|
|
10473
9392
|
spinnerMessage: "Updating task...",
|
|
10474
|
-
onInteractive: (
|
|
9393
|
+
onInteractive: (payload) => {
|
|
10475
9394
|
console.log(`
|
|
10476
|
-
${import_picocolors8.default.green("Updated!")} ${task.text} ${import_picocolors8.default.dim(task.id)}
|
|
9395
|
+
${import_picocolors8.default.green("Updated!")} ${payload.task.text} ${import_picocolors8.default.dim(payload.task.id)}
|
|
10477
9396
|
`);
|
|
10478
9397
|
}
|
|
10479
9398
|
});
|
|
@@ -10503,7 +9422,7 @@ Examples:
|
|
|
10503
9422
|
let taskText = taskId;
|
|
10504
9423
|
try {
|
|
10505
9424
|
const task = await getTask(uid, taskId);
|
|
10506
|
-
taskText =
|
|
9425
|
+
taskText = task.text ?? taskId;
|
|
10507
9426
|
} catch {
|
|
10508
9427
|
}
|
|
10509
9428
|
const confirmed = await promptConfirm({
|
|
@@ -10521,9 +9440,8 @@ Examples:
|
|
|
10521
9440
|
spinnerMessage: "Deleting task...",
|
|
10522
9441
|
onInteractive: (data) => {
|
|
10523
9442
|
const cross = import_picocolors8.default.red(SYM.cross);
|
|
10524
|
-
const text = data.taskText || taskId;
|
|
10525
9443
|
console.log(`
|
|
10526
|
-
${cross} Deleted ${
|
|
9444
|
+
${cross} Deleted ${data.taskText || taskId}`);
|
|
10527
9445
|
if (data.archived) console.log(` ${import_picocolors8.default.dim("Archived")}`);
|
|
10528
9446
|
console.log("");
|
|
10529
9447
|
}
|
|
@@ -10555,13 +9473,10 @@ Examples:
|
|
|
10555
9473
|
spinnerMessage: "Completing task...",
|
|
10556
9474
|
onInteractive: (data) => {
|
|
10557
9475
|
const check = import_picocolors8.default.green(SYM.check);
|
|
10558
|
-
const text = data.taskText ?? taskId;
|
|
10559
9476
|
console.log(`
|
|
10560
|
-
${check} Done! ${
|
|
10561
|
-
|
|
10562
|
-
|
|
10563
|
-
if (karma) {
|
|
10564
|
-
console.log(` ${formatKarmaGain(karma, checksInRow)}`);
|
|
9477
|
+
${check} Done! ${data.taskText ?? taskId}`);
|
|
9478
|
+
if (data.karma) {
|
|
9479
|
+
console.log(` ${formatKarmaGain(data.karma, data.checksInRow)}`);
|
|
10565
9480
|
}
|
|
10566
9481
|
console.log("");
|
|
10567
9482
|
}
|
|
@@ -10593,9 +9508,8 @@ Examples:
|
|
|
10593
9508
|
spinnerMessage: "Uncompleting task...",
|
|
10594
9509
|
onInteractive: (data) => {
|
|
10595
9510
|
const arrow = SYM.undo;
|
|
10596
|
-
const text = data.text ?? taskId;
|
|
10597
9511
|
console.log(`
|
|
10598
|
-
${import_picocolors8.default.yellow(arrow)} Reverted ${text}`);
|
|
9512
|
+
${import_picocolors8.default.yellow(arrow)} Reverted ${data.task.text ?? taskId}`);
|
|
10599
9513
|
console.log(` ${import_picocolors8.default.dim("Karma adjustment applied")}`);
|
|
10600
9514
|
console.log("");
|
|
10601
9515
|
}
|
|
@@ -10606,13 +9520,10 @@ Examples:
|
|
|
10606
9520
|
}
|
|
10607
9521
|
|
|
10608
9522
|
// src/cli/commands/posts.ts
|
|
10609
|
-
init_define_ADMIN_UIDS();
|
|
10610
9523
|
var import_picocolors10 = __toESM(require_picocolors(), 1);
|
|
10611
9524
|
|
|
10612
9525
|
// src/cli/lib/pagination.ts
|
|
10613
|
-
init_define_ADMIN_UIDS();
|
|
10614
9526
|
var import_picocolors9 = __toESM(require_picocolors(), 1);
|
|
10615
|
-
init_errors();
|
|
10616
9527
|
function printPaginationHint(opts) {
|
|
10617
9528
|
if (!opts.nextCursor) return;
|
|
10618
9529
|
const parts = [opts.command, `--cursor ${opts.nextCursor}`];
|
|
@@ -10622,223 +9533,59 @@ ${import_picocolors9.default.dim("Next page:")} ${import_picocolors9.default.dim
|
|
|
10622
9533
|
}
|
|
10623
9534
|
|
|
10624
9535
|
// src/cli/services/posts.ts
|
|
10625
|
-
|
|
10626
|
-
function generateSlug(title) {
|
|
10627
|
-
return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 80) + "-" + Date.now().toString(36);
|
|
10628
|
-
}
|
|
9536
|
+
init_api_client();
|
|
10629
9537
|
async function listPosts(opts) {
|
|
10630
|
-
|
|
10631
|
-
|
|
10632
|
-
|
|
10633
|
-
|
|
10634
|
-
};
|
|
10635
|
-
if (opts.cursor) {
|
|
10636
|
-
const cursorDoc = await getDoc(`posts/${opts.cursor}`);
|
|
10637
|
-
if (cursorDoc.createdAt != null) {
|
|
10638
|
-
queryOpts.startAfter = [cursorDoc.createdAt];
|
|
10639
|
-
}
|
|
10640
|
-
}
|
|
10641
|
-
const docs = await runQuery("", "posts", queryOpts);
|
|
10642
|
-
const hasMore = docs.length > lim;
|
|
10643
|
-
const posts = docs.slice(0, lim);
|
|
10644
|
-
return {
|
|
10645
|
-
posts,
|
|
10646
|
-
nextCursor: hasMore ? posts[posts.length - 1].id : void 0
|
|
10647
|
-
};
|
|
9538
|
+
return api.get("/api/posts", {
|
|
9539
|
+
cursor: opts.cursor,
|
|
9540
|
+
limit: opts.limit?.toString()
|
|
9541
|
+
});
|
|
10648
9542
|
}
|
|
10649
9543
|
async function getPost(id) {
|
|
10650
|
-
|
|
10651
|
-
const post = await getDoc(`posts/${id}`);
|
|
10652
|
-
if (post.authorId) {
|
|
10653
|
-
try {
|
|
10654
|
-
const author = await getDoc(`users/${post.authorId}`);
|
|
10655
|
-
post.authorName = author.username ?? null;
|
|
10656
|
-
} catch {
|
|
10657
|
-
}
|
|
10658
|
-
}
|
|
10659
|
-
return post;
|
|
9544
|
+
return api.get(`/api/posts/${encodeURIComponent(id)}`);
|
|
10660
9545
|
}
|
|
10661
9546
|
async function createPost(uid, body) {
|
|
10662
|
-
|
|
10663
|
-
const postData = {
|
|
10664
|
-
title: body.title,
|
|
10665
|
-
body: body.body,
|
|
10666
|
-
tag: body.tag,
|
|
10667
|
-
authorId: uid,
|
|
10668
|
-
slug: generateSlug(body.title),
|
|
10669
|
-
isPublic: body.isPublic !== false,
|
|
10670
|
-
createdAt: now2,
|
|
10671
|
-
updatedAt: now2,
|
|
10672
|
-
commentsCount: 0,
|
|
10673
|
-
likesCount: 0
|
|
10674
|
-
};
|
|
10675
|
-
const doc = await createDoc("posts", postData);
|
|
10676
|
-
const postId = doc.id;
|
|
10677
|
-
try {
|
|
10678
|
-
await giveKarma(uid, "createPost", postId, KARMA_POINTS.createPost, String(body.title));
|
|
10679
|
-
} catch {
|
|
10680
|
-
}
|
|
10681
|
-
try {
|
|
10682
|
-
await incrementField(`users/${uid}/activity/totals`, "posts.written", 1);
|
|
10683
|
-
} catch {
|
|
10684
|
-
}
|
|
10685
|
-
return { post: doc, karma: KARMA_POINTS.createPost };
|
|
9547
|
+
return api.post("/api/posts", body);
|
|
10686
9548
|
}
|
|
10687
9549
|
async function updatePost(uid, id, body) {
|
|
10688
|
-
|
|
10689
|
-
const post = await getDoc(`posts/${id}`);
|
|
10690
|
-
checkOwnership(post, uid, "update");
|
|
10691
|
-
const allowed = ["title", "body", "tag", "isPublic"];
|
|
10692
|
-
const update = { updatedAt: Date.now() };
|
|
10693
|
-
const fieldMask = ["updatedAt"];
|
|
10694
|
-
for (const key of allowed) {
|
|
10695
|
-
if (key in body) {
|
|
10696
|
-
update[key] = body[key];
|
|
10697
|
-
fieldMask.push(key);
|
|
10698
|
-
}
|
|
10699
|
-
}
|
|
10700
|
-
await updateDoc(`posts/${id}`, update, fieldMask);
|
|
10701
|
-
const updated = await getDoc(`posts/${id}`);
|
|
10702
|
-
return { post: updated };
|
|
9550
|
+
return api.patch(`/api/posts/${encodeURIComponent(id)}`, body);
|
|
10703
9551
|
}
|
|
10704
9552
|
async function deletePost(uid, id) {
|
|
10705
|
-
|
|
10706
|
-
const post = await getDoc(`posts/${id}`);
|
|
10707
|
-
checkOwnership(post, uid, "delete");
|
|
10708
|
-
await deleteDoc(`posts/${id}`);
|
|
9553
|
+
await api.del(`/api/posts/${encodeURIComponent(id)}`);
|
|
10709
9554
|
}
|
|
10710
9555
|
|
|
10711
9556
|
// src/cli/services/comments.ts
|
|
10712
|
-
|
|
9557
|
+
init_api_client();
|
|
10713
9558
|
async function listComments(postId, opts) {
|
|
10714
|
-
|
|
10715
|
-
|
|
10716
|
-
|
|
10717
|
-
|
|
10718
|
-
limit: lim + 1
|
|
10719
|
-
};
|
|
10720
|
-
if (opts.cursor) {
|
|
10721
|
-
const cursorDoc = await getDoc(`posts/${postId}/comments/${opts.cursor}`);
|
|
10722
|
-
if (cursorDoc.createdAt != null) {
|
|
10723
|
-
queryOpts.startAfter = [cursorDoc.createdAt];
|
|
10724
|
-
}
|
|
10725
|
-
}
|
|
10726
|
-
const docs = await runQuery(`posts/${postId}`, "comments", queryOpts);
|
|
10727
|
-
const hasMore = docs.length > lim;
|
|
10728
|
-
const comments = docs.slice(0, lim);
|
|
10729
|
-
const uids = [...new Set(comments.map((c) => c.userId).filter(Boolean))];
|
|
10730
|
-
const userMap = {};
|
|
10731
|
-
await Promise.all(uids.map(async (uid) => {
|
|
10732
|
-
try {
|
|
10733
|
-
const user = await getDoc(`users/${uid}`);
|
|
10734
|
-
if (user.username) userMap[uid] = user.username;
|
|
10735
|
-
} catch {
|
|
10736
|
-
}
|
|
10737
|
-
}));
|
|
10738
|
-
for (const c of comments) {
|
|
10739
|
-
c.authorName = userMap[c.userId] ?? null;
|
|
10740
|
-
}
|
|
10741
|
-
return {
|
|
10742
|
-
comments,
|
|
10743
|
-
nextCursor: hasMore ? comments[comments.length - 1].id : void 0
|
|
10744
|
-
};
|
|
9559
|
+
return api.get(`/api/posts/${encodeURIComponent(postId)}/comments`, {
|
|
9560
|
+
cursor: opts.cursor,
|
|
9561
|
+
limit: opts.limit?.toString()
|
|
9562
|
+
});
|
|
10745
9563
|
}
|
|
10746
9564
|
async function createComment(uid, postId, text) {
|
|
10747
|
-
|
|
10748
|
-
const now2 = Date.now();
|
|
10749
|
-
const commentData = {
|
|
10750
|
-
postId,
|
|
10751
|
-
userId: uid,
|
|
10752
|
-
text,
|
|
10753
|
-
createdAt: now2,
|
|
10754
|
-
updatedAt: now2,
|
|
10755
|
-
textLength: text.length,
|
|
10756
|
-
likes: [],
|
|
10757
|
-
repliesCount: 0
|
|
10758
|
-
};
|
|
10759
|
-
const doc = await createDoc(`posts/${postId}/comments`, commentData);
|
|
10760
|
-
const commentId = doc.id;
|
|
10761
|
-
await incrementField(`posts/${postId}`, "commentsCount", 1);
|
|
10762
|
-
try {
|
|
10763
|
-
await giveKarma(uid, "addComment", `${postId}_${commentId}`, KARMA_POINTS.addComment, text);
|
|
10764
|
-
} catch {
|
|
10765
|
-
}
|
|
10766
|
-
try {
|
|
10767
|
-
await incrementField(`users/${uid}/activity/totals`, "comments.written", 1);
|
|
10768
|
-
} catch {
|
|
10769
|
-
}
|
|
10770
|
-
return { comment: doc, karma: KARMA_POINTS.addComment };
|
|
9565
|
+
return api.post(`/api/posts/${encodeURIComponent(postId)}/comments`, { text });
|
|
10771
9566
|
}
|
|
10772
9567
|
async function deleteComment(uid, postId, commentId) {
|
|
10773
|
-
|
|
10774
|
-
validateDocId(commentId, "Comment ID");
|
|
10775
|
-
const comment = await getDoc(`posts/${postId}/comments/${commentId}`);
|
|
10776
|
-
checkOwnership(comment, uid, "delete");
|
|
10777
|
-
await deleteDoc(`posts/${postId}/comments/${commentId}`);
|
|
10778
|
-
await incrementField(`posts/${postId}`, "commentsCount", -1);
|
|
9568
|
+
await api.del(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}`);
|
|
10779
9569
|
}
|
|
10780
9570
|
|
|
10781
9571
|
// src/cli/services/replies.ts
|
|
10782
|
-
|
|
9572
|
+
init_api_client();
|
|
10783
9573
|
async function listReplies(postId, commentId, opts) {
|
|
10784
|
-
|
|
10785
|
-
|
|
10786
|
-
|
|
10787
|
-
|
|
10788
|
-
orderBy: [{ field: "createdAt", direction: "ASCENDING" }],
|
|
10789
|
-
limit: lim + 1
|
|
10790
|
-
};
|
|
10791
|
-
if (opts.cursor) {
|
|
10792
|
-
const cursorDoc = await getDoc(`posts/${postId}/comments/${commentId}/replies/${opts.cursor}`);
|
|
10793
|
-
if (cursorDoc.createdAt != null) {
|
|
10794
|
-
queryOpts.startAfter = [cursorDoc.createdAt];
|
|
10795
|
-
}
|
|
10796
|
-
}
|
|
10797
|
-
const docs = await runQuery(`posts/${postId}/comments/${commentId}`, "replies", queryOpts);
|
|
10798
|
-
const hasMore = docs.length > lim;
|
|
10799
|
-
const replies = docs.slice(0, lim);
|
|
10800
|
-
return {
|
|
10801
|
-
replies,
|
|
10802
|
-
nextCursor: hasMore ? replies[replies.length - 1].id : void 0
|
|
10803
|
-
};
|
|
9574
|
+
return api.get(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, {
|
|
9575
|
+
cursor: opts.cursor,
|
|
9576
|
+
limit: opts.limit?.toString()
|
|
9577
|
+
});
|
|
10804
9578
|
}
|
|
10805
9579
|
async function createReply(uid, postId, commentId, text) {
|
|
10806
|
-
|
|
10807
|
-
validateDocId(commentId, "Comment ID");
|
|
10808
|
-
const now2 = Date.now();
|
|
10809
|
-
const replyData = {
|
|
10810
|
-
postId,
|
|
10811
|
-
userId: uid,
|
|
10812
|
-
text,
|
|
10813
|
-
createdAt: now2,
|
|
10814
|
-
updatedAt: now2,
|
|
10815
|
-
textLength: text.length,
|
|
10816
|
-
likes: [],
|
|
10817
|
-
parentCommentId: commentId
|
|
10818
|
-
};
|
|
10819
|
-
const doc = await createDoc(`posts/${postId}/comments/${commentId}/replies`, replyData);
|
|
10820
|
-
const replyId = doc.id;
|
|
10821
|
-
await incrementField(`posts/${postId}/comments/${commentId}`, "repliesCount", 1);
|
|
10822
|
-
try {
|
|
10823
|
-
await giveKarma(uid, "addComment", `${postId}_${replyId}`, KARMA_POINTS.addComment, text);
|
|
10824
|
-
} catch {
|
|
10825
|
-
}
|
|
10826
|
-
try {
|
|
10827
|
-
await incrementField(`users/${uid}/activity/totals`, "replies.written", 1);
|
|
10828
|
-
} catch {
|
|
10829
|
-
}
|
|
10830
|
-
return { reply: doc, karma: KARMA_POINTS.addComment };
|
|
9580
|
+
return api.post(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, { text });
|
|
10831
9581
|
}
|
|
10832
9582
|
async function deleteReply(uid, postId, commentId, replyId) {
|
|
10833
|
-
|
|
10834
|
-
validateDocId(commentId, "Comment ID");
|
|
10835
|
-
validateDocId(replyId, "Reply ID");
|
|
10836
|
-
const reply = await getDoc(`posts/${postId}/comments/${commentId}/replies/${replyId}`);
|
|
10837
|
-
checkOwnership(reply, uid, "delete");
|
|
10838
|
-
await deleteDoc(`posts/${postId}/comments/${commentId}/replies/${replyId}`);
|
|
10839
|
-
await incrementField(`posts/${postId}/comments/${commentId}`, "repliesCount", -1);
|
|
9583
|
+
await api.del(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies/${encodeURIComponent(replyId)}`);
|
|
10840
9584
|
}
|
|
10841
9585
|
|
|
9586
|
+
// src/cli/types/api.ts
|
|
9587
|
+
var POST_TAGS = ["general", "hack", "story", "meme", "other", "question", "hack-tip", "activity"];
|
|
9588
|
+
|
|
10842
9589
|
// src/cli/commands/posts.ts
|
|
10843
9590
|
init_prompts();
|
|
10844
9591
|
init_tty();
|
|
@@ -10851,7 +9598,7 @@ async function pickComment(postId, commentId) {
|
|
|
10851
9598
|
message: "Select comment",
|
|
10852
9599
|
options: comments.map((c) => ({
|
|
10853
9600
|
value: c.id,
|
|
10854
|
-
label: `${truncate(
|
|
9601
|
+
label: `${truncate(c.text ?? "", 60)} ${import_picocolors10.default.dim(c.authorName ?? c.userId ?? "")}`
|
|
10855
9602
|
}))
|
|
10856
9603
|
});
|
|
10857
9604
|
}
|
|
@@ -10864,16 +9611,16 @@ async function pickReply(postId, commentId, replyId) {
|
|
|
10864
9611
|
message: "Select reply",
|
|
10865
9612
|
options: replies.map((r) => ({
|
|
10866
9613
|
value: r.id,
|
|
10867
|
-
label: `${truncate(
|
|
9614
|
+
label: `${truncate(r.text ?? "", 60)} ${import_picocolors10.default.dim(r.userId ?? "")}`
|
|
10868
9615
|
}))
|
|
10869
9616
|
});
|
|
10870
9617
|
}
|
|
10871
9618
|
function printPostLine(p) {
|
|
10872
|
-
const tag = import_picocolors10.default.cyan(
|
|
10873
|
-
const title = truncate(
|
|
9619
|
+
const tag = import_picocolors10.default.cyan(p.tag ?? "");
|
|
9620
|
+
const title = truncate(p.title, 55);
|
|
10874
9621
|
const comments = p.commentsCount ? import_picocolors10.default.dim(`${p.commentsCount} comments`) : "";
|
|
10875
9622
|
const time = formatRelativeDate(p.createdAt);
|
|
10876
|
-
const id = import_picocolors10.default.dim(
|
|
9623
|
+
const id = import_picocolors10.default.dim(p.id);
|
|
10877
9624
|
const parts = [tag, import_picocolors10.default.bold(title)];
|
|
10878
9625
|
if (comments) parts.push(comments);
|
|
10879
9626
|
parts.push(import_picocolors10.default.dim(time));
|
|
@@ -10890,7 +9637,7 @@ function printPostDetail(p) {
|
|
|
10890
9637
|
console.log(` ${import_picocolors10.default.dim(SYM.dash.repeat(40))}`);
|
|
10891
9638
|
printRecord([
|
|
10892
9639
|
["ID", import_picocolors10.default.dim(p.id)],
|
|
10893
|
-
["Tag", import_picocolors10.default.cyan(
|
|
9640
|
+
["Tag", import_picocolors10.default.cyan(p.tag ?? "")],
|
|
10894
9641
|
["Author", p.authorName ?? p.authorId],
|
|
10895
9642
|
["Comments", p.commentsCount != null ? String(p.commentsCount) : null],
|
|
10896
9643
|
["Likes", p.likesCount != null ? String(p.likesCount) : null],
|
|
@@ -10899,18 +9646,18 @@ function printPostDetail(p) {
|
|
|
10899
9646
|
console.log("");
|
|
10900
9647
|
}
|
|
10901
9648
|
function printCommentLine(c) {
|
|
10902
|
-
const author = import_picocolors10.default.bold(
|
|
9649
|
+
const author = import_picocolors10.default.bold(c.authorName ?? c.userId ?? "");
|
|
10903
9650
|
const time = import_picocolors10.default.dim(formatRelativeDate(c.createdAt));
|
|
10904
9651
|
const replies = c.repliesCount ? import_picocolors10.default.dim(`\xB7 ${c.repliesCount} replies`) : "";
|
|
10905
|
-
const text =
|
|
9652
|
+
const text = c.text ?? "";
|
|
10906
9653
|
console.log(` ${author} ${time}${replies}`);
|
|
10907
9654
|
console.log(` ${text}`);
|
|
10908
9655
|
console.log("");
|
|
10909
9656
|
}
|
|
10910
9657
|
function printReplyLine(r) {
|
|
10911
|
-
const text = truncate(
|
|
9658
|
+
const text = truncate(r.text ?? "", 60);
|
|
10912
9659
|
const time = formatRelativeDate(r.createdAt);
|
|
10913
|
-
const id = import_picocolors10.default.dim(
|
|
9660
|
+
const id = import_picocolors10.default.dim(r.id);
|
|
10914
9661
|
console.log(` ${text} ${import_picocolors10.default.dim(time)} ${id}`);
|
|
10915
9662
|
}
|
|
10916
9663
|
function registerPostsCommands(program3) {
|
|
@@ -11012,148 +9759,135 @@ Examples:
|
|
|
11012
9759
|
}
|
|
11013
9760
|
});
|
|
11014
9761
|
});
|
|
11015
|
-
|
|
11016
|
-
|
|
11017
|
-
|
|
11018
|
-
|
|
11019
|
-
|
|
11020
|
-
|
|
11021
|
-
|
|
11022
|
-
|
|
11023
|
-
|
|
11024
|
-
|
|
11025
|
-
options: POST_TAGS.map((t2) => ({ value: t2, label: t2 }))
|
|
11026
|
-
});
|
|
11027
|
-
}
|
|
11028
|
-
const body = { title, body: postBody, tag };
|
|
11029
|
-
await runCreate({
|
|
11030
|
-
global: opts,
|
|
11031
|
-
fn: () => createPost(uid, body),
|
|
11032
|
-
dataKey: "post",
|
|
11033
|
-
spinnerMessage: "Creating post...",
|
|
11034
|
-
onInteractive: (post) => {
|
|
11035
|
-
console.log(`
|
|
11036
|
-
${import_picocolors10.default.green("Posted!")} ${post.title} ${import_picocolors10.default.dim(post.id)}
|
|
11037
|
-
`);
|
|
11038
|
-
}
|
|
9762
|
+
posts.command("create").description("Create a new post").option("--title <title>").option("--body <body>").option("--tag <tag>", "general|hack|story|meme|other|question|hack-tip|activity").action(async function() {
|
|
9763
|
+
const opts = this.optsWithGlobals();
|
|
9764
|
+
const uid = requireUid();
|
|
9765
|
+
const title = await promptForMissing({ value: opts.title, message: "Title", placeholder: "Post title" });
|
|
9766
|
+
const postBody = await promptForMissing({ value: opts.body, message: "Body", placeholder: "Post body" });
|
|
9767
|
+
let tag = opts.tag;
|
|
9768
|
+
if (!tag) {
|
|
9769
|
+
tag = await promptSelect({
|
|
9770
|
+
message: "Tag",
|
|
9771
|
+
options: POST_TAGS.map((t2) => ({ value: t2, label: t2 }))
|
|
11039
9772
|
});
|
|
9773
|
+
}
|
|
9774
|
+
const body = { title, body: postBody, tag };
|
|
9775
|
+
await runCreate({
|
|
9776
|
+
global: opts,
|
|
9777
|
+
fn: () => createPost(uid, body),
|
|
9778
|
+
dataKey: "post",
|
|
9779
|
+
spinnerMessage: "Creating post...",
|
|
9780
|
+
onInteractive: (post, payload) => {
|
|
9781
|
+
console.log(`
|
|
9782
|
+
${import_picocolors10.default.green("Posted!")} ${payload.post.title} ${import_picocolors10.default.dim(payload.post.id)}
|
|
9783
|
+
`);
|
|
9784
|
+
}
|
|
11040
9785
|
});
|
|
11041
|
-
|
|
11042
|
-
|
|
11043
|
-
|
|
11044
|
-
|
|
11045
|
-
|
|
11046
|
-
|
|
11047
|
-
|
|
11048
|
-
|
|
11049
|
-
|
|
11050
|
-
|
|
11051
|
-
|
|
11052
|
-
|
|
11053
|
-
|
|
11054
|
-
|
|
11055
|
-
|
|
11056
|
-
|
|
11057
|
-
|
|
11058
|
-
|
|
11059
|
-
|
|
11060
|
-
|
|
11061
|
-
|
|
11062
|
-
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
|
|
9786
|
+
});
|
|
9787
|
+
posts.command("update [id]").description("Update a post").option("--title <title>").option("--body <body>").option("--tag <tag>").action(async function(id) {
|
|
9788
|
+
const opts = this.optsWithGlobals();
|
|
9789
|
+
const uid = requireUid();
|
|
9790
|
+
const postId = await promptForMissing({ value: id, message: "Post ID" });
|
|
9791
|
+
const body = {};
|
|
9792
|
+
const hasAnyFlag = opts.title || opts.body || opts.tag;
|
|
9793
|
+
if (!hasAnyFlag && isInteractive() && !opts.json) {
|
|
9794
|
+
const title = await promptText({ message: "Title (enter to skip)", required: false });
|
|
9795
|
+
if (title) body.title = title;
|
|
9796
|
+
const postBody = await promptText({ message: "Body (enter to skip)", required: false });
|
|
9797
|
+
if (postBody) body.body = postBody;
|
|
9798
|
+
const changeTag = await promptText({ message: "Tag (enter to skip)", placeholder: POST_TAGS.join("|"), required: false });
|
|
9799
|
+
if (changeTag) body.tag = changeTag;
|
|
9800
|
+
} else {
|
|
9801
|
+
if (opts.title) body.title = opts.title;
|
|
9802
|
+
if (opts.body) body.body = opts.body;
|
|
9803
|
+
if (opts.tag) body.tag = opts.tag;
|
|
9804
|
+
}
|
|
9805
|
+
await runWrite({
|
|
9806
|
+
global: opts,
|
|
9807
|
+
fn: () => updatePost(uid, postId, body),
|
|
9808
|
+
dataKey: "post",
|
|
9809
|
+
spinnerMessage: "Updating post...",
|
|
9810
|
+
onInteractive: (payload) => {
|
|
9811
|
+
console.log(`
|
|
9812
|
+
${import_picocolors10.default.green("Updated!")} ${payload.post.title} ${import_picocolors10.default.dim(payload.post.id)}
|
|
11067
9813
|
`);
|
|
11068
|
-
|
|
11069
|
-
});
|
|
9814
|
+
}
|
|
11070
9815
|
});
|
|
11071
|
-
|
|
11072
|
-
|
|
11073
|
-
|
|
11074
|
-
|
|
11075
|
-
|
|
11076
|
-
|
|
11077
|
-
|
|
11078
|
-
|
|
11079
|
-
|
|
9816
|
+
});
|
|
9817
|
+
posts.command("delete [id]").description("Delete a post").action(async function(id) {
|
|
9818
|
+
const uid = requireUid();
|
|
9819
|
+
const postId = await promptForMissing({ value: id, message: "Post ID" });
|
|
9820
|
+
await runDelete({
|
|
9821
|
+
global: this.optsWithGlobals(),
|
|
9822
|
+
fn: () => deletePost(uid, postId),
|
|
9823
|
+
successMessage: ` ${import_picocolors10.default.green("Deleted!")} Post ${import_picocolors10.default.dim(postId)}`,
|
|
9824
|
+
spinnerMessage: "Deleting post..."
|
|
11080
9825
|
});
|
|
11081
|
-
|
|
11082
|
-
|
|
11083
|
-
|
|
11084
|
-
|
|
11085
|
-
|
|
11086
|
-
|
|
11087
|
-
|
|
11088
|
-
|
|
11089
|
-
|
|
11090
|
-
|
|
11091
|
-
|
|
11092
|
-
|
|
11093
|
-
|
|
9826
|
+
});
|
|
9827
|
+
posts.command("comment [postId]").description("Add a comment to a post").option("--text <text>").action(async function(postId) {
|
|
9828
|
+
const opts = this.optsWithGlobals();
|
|
9829
|
+
const uid = requireUid();
|
|
9830
|
+
const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
|
|
9831
|
+
const text = await promptForMissing({ value: opts.text, message: "Comment text", placeholder: "Your comment" });
|
|
9832
|
+
await runCreate({
|
|
9833
|
+
global: opts,
|
|
9834
|
+
fn: () => createComment(uid, resolvedPostId, text),
|
|
9835
|
+
dataKey: "comment",
|
|
9836
|
+
spinnerMessage: "Adding comment...",
|
|
9837
|
+
onInteractive: (comment, payload) => {
|
|
9838
|
+
console.log(`
|
|
9839
|
+
${import_picocolors10.default.green("Commented!")} ${truncate(payload.comment.text ?? "", 50)} ${import_picocolors10.default.dim(payload.comment.id)}
|
|
11094
9840
|
`);
|
|
11095
|
-
|
|
11096
|
-
});
|
|
9841
|
+
}
|
|
11097
9842
|
});
|
|
11098
|
-
|
|
11099
|
-
|
|
11100
|
-
|
|
11101
|
-
|
|
11102
|
-
|
|
11103
|
-
|
|
11104
|
-
|
|
11105
|
-
|
|
11106
|
-
|
|
11107
|
-
|
|
9843
|
+
});
|
|
9844
|
+
posts.command("comment-delete [postId] [commentId]").description("Delete a comment").action(async function(postId, commentId) {
|
|
9845
|
+
const uid = requireUid();
|
|
9846
|
+
const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
|
|
9847
|
+
const resolvedCommentId = await pickComment(resolvedPostId, commentId);
|
|
9848
|
+
await runDelete({
|
|
9849
|
+
global: this.optsWithGlobals(),
|
|
9850
|
+
fn: () => deleteComment(uid, resolvedPostId, resolvedCommentId),
|
|
9851
|
+
successMessage: ` ${import_picocolors10.default.green("Deleted!")} Comment ${import_picocolors10.default.dim(resolvedCommentId)}`,
|
|
9852
|
+
spinnerMessage: "Deleting comment..."
|
|
11108
9853
|
});
|
|
11109
|
-
|
|
11110
|
-
|
|
11111
|
-
|
|
11112
|
-
|
|
11113
|
-
|
|
11114
|
-
|
|
11115
|
-
|
|
11116
|
-
|
|
11117
|
-
|
|
11118
|
-
|
|
11119
|
-
|
|
11120
|
-
|
|
11121
|
-
|
|
11122
|
-
|
|
9854
|
+
});
|
|
9855
|
+
posts.command("reply [postId] [commentId]").description("Add a reply to a comment").option("--text <text>").action(async function(postId, commentId) {
|
|
9856
|
+
const opts = this.optsWithGlobals();
|
|
9857
|
+
const uid = requireUid();
|
|
9858
|
+
const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
|
|
9859
|
+
const resolvedCommentId = await pickComment(resolvedPostId, commentId);
|
|
9860
|
+
const text = await promptForMissing({ value: opts.text, message: "Reply text", placeholder: "Your reply" });
|
|
9861
|
+
await runCreate({
|
|
9862
|
+
global: opts,
|
|
9863
|
+
fn: () => createReply(uid, resolvedPostId, resolvedCommentId, text),
|
|
9864
|
+
dataKey: "reply",
|
|
9865
|
+
spinnerMessage: "Adding reply...",
|
|
9866
|
+
onInteractive: (reply, payload) => {
|
|
9867
|
+
console.log(`
|
|
9868
|
+
${import_picocolors10.default.green("Replied!")} ${truncate(payload.reply.text ?? "", 50)} ${import_picocolors10.default.dim(payload.reply.id)}
|
|
11123
9869
|
`);
|
|
11124
|
-
|
|
11125
|
-
});
|
|
9870
|
+
}
|
|
11126
9871
|
});
|
|
11127
|
-
|
|
11128
|
-
|
|
11129
|
-
|
|
11130
|
-
|
|
11131
|
-
|
|
11132
|
-
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11137
|
-
|
|
9872
|
+
});
|
|
9873
|
+
posts.command("reply-delete [postId] [commentId] [replyId]").description("Delete a reply").action(async function(postId, commentId, replyId) {
|
|
9874
|
+
const uid = requireUid();
|
|
9875
|
+
const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
|
|
9876
|
+
const resolvedCommentId = await pickComment(resolvedPostId, commentId);
|
|
9877
|
+
const resolvedReplyId = await pickReply(resolvedPostId, resolvedCommentId, replyId);
|
|
9878
|
+
await runDelete({
|
|
9879
|
+
global: this.optsWithGlobals(),
|
|
9880
|
+
fn: () => deleteReply(uid, resolvedPostId, resolvedCommentId, resolvedReplyId),
|
|
9881
|
+
successMessage: ` ${import_picocolors10.default.green("Deleted!")} Reply ${import_picocolors10.default.dim(resolvedReplyId)}`,
|
|
9882
|
+
spinnerMessage: "Deleting reply..."
|
|
11138
9883
|
});
|
|
11139
|
-
}
|
|
9884
|
+
});
|
|
11140
9885
|
}
|
|
11141
9886
|
|
|
11142
|
-
// src/cli/commands/profile.ts
|
|
11143
|
-
init_define_ADMIN_UIDS();
|
|
11144
|
-
|
|
11145
9887
|
// src/cli/services/profile.ts
|
|
11146
|
-
|
|
9888
|
+
init_api_client();
|
|
11147
9889
|
async function getProfile() {
|
|
11148
|
-
|
|
11149
|
-
if (!creds) throw new Error("Not logged in. Run: numo login");
|
|
11150
|
-
const doc = await getDoc(`users/${creds.uid}`);
|
|
11151
|
-
return {
|
|
11152
|
-
uid: creds.uid,
|
|
11153
|
-
email: creds.email ?? null,
|
|
11154
|
-
username: doc.username ?? null,
|
|
11155
|
-
photoURL: doc.photoURL ?? null
|
|
11156
|
-
};
|
|
9890
|
+
return api.get("/api/profile");
|
|
11157
9891
|
}
|
|
11158
9892
|
|
|
11159
9893
|
// src/cli/commands/profile.ts
|
|
@@ -11177,10 +9911,9 @@ function registerProfileCommands(program3) {
|
|
|
11177
9911
|
}
|
|
11178
9912
|
|
|
11179
9913
|
// src/cli/commands/doctor.ts
|
|
11180
|
-
init_define_ADMIN_UIDS();
|
|
11181
9914
|
var import_picocolors11 = __toESM(require_picocolors(), 1);
|
|
11182
|
-
|
|
11183
|
-
|
|
9915
|
+
init_credentials();
|
|
9916
|
+
init_api_client();
|
|
11184
9917
|
init_tty();
|
|
11185
9918
|
async function runChecks() {
|
|
11186
9919
|
const checks = [];
|
|
@@ -11191,6 +9924,11 @@ async function runChecks() {
|
|
|
11191
9924
|
status: major >= 18 ? "ok" : "fail",
|
|
11192
9925
|
message: major >= 18 ? `Node ${nodeVersion}` : `Node ${nodeVersion} \u2014 requires >= 18`
|
|
11193
9926
|
});
|
|
9927
|
+
checks.push({
|
|
9928
|
+
name: "api_url",
|
|
9929
|
+
status: process.env.NUMO_API_URL ? "ok" : "warn",
|
|
9930
|
+
message: process.env.NUMO_API_URL ? `API URL: ${API_BASE}` : `NUMO_API_URL not set (using default: ${API_BASE})`
|
|
9931
|
+
});
|
|
11194
9932
|
const creds = loadCredentials();
|
|
11195
9933
|
checks.push({
|
|
11196
9934
|
name: "credentials",
|
|
@@ -11207,18 +9945,11 @@ async function runChecks() {
|
|
|
11207
9945
|
} else {
|
|
11208
9946
|
checks.push({ name: "token", status: "fail", message: "Skipped (no credentials)" });
|
|
11209
9947
|
}
|
|
11210
|
-
const apiKey = getFirebaseApiKey();
|
|
11211
|
-
checks.push({
|
|
11212
|
-
name: "api_key",
|
|
11213
|
-
status: apiKey ? "ok" : "fail",
|
|
11214
|
-
message: apiKey ? "Firebase API key configured" : "NUMO_FIREBASE_API_KEY not set"
|
|
11215
|
-
});
|
|
11216
9948
|
try {
|
|
11217
|
-
const
|
|
11218
|
-
|
|
11219
|
-
checks.push({ name: "firebase_reachable", status: "ok", message: `Firebase reachable (HTTP ${resp.status})` });
|
|
9949
|
+
const resp = await fetch(`${API_BASE}/api/health`, { signal: AbortSignal.timeout(5e3) });
|
|
9950
|
+
checks.push({ name: "api_reachable", status: "ok", message: `API server reachable (HTTP ${resp.status})` });
|
|
11220
9951
|
} catch (err) {
|
|
11221
|
-
checks.push({ name: "
|
|
9952
|
+
checks.push({ name: "api_reachable", status: "fail", message: `API server unreachable: ${err.message}` });
|
|
11222
9953
|
}
|
|
11223
9954
|
return checks;
|
|
11224
9955
|
}
|
|
@@ -11248,20 +9979,23 @@ function registerDoctorCommand(program3) {
|
|
|
11248
9979
|
});
|
|
11249
9980
|
}
|
|
11250
9981
|
|
|
9982
|
+
// src/cli/cli.ts
|
|
9983
|
+
init_dirs();
|
|
9984
|
+
|
|
11251
9985
|
// src/cli/lib/update-check.ts
|
|
11252
|
-
|
|
11253
|
-
var
|
|
11254
|
-
var path3 = __toESM(require("path"), 1);
|
|
9986
|
+
var fs4 = __toESM(require("fs"), 1);
|
|
9987
|
+
var path2 = __toESM(require("path"), 1);
|
|
11255
9988
|
var import_picocolors12 = __toESM(require_picocolors(), 1);
|
|
9989
|
+
init_dirs();
|
|
11256
9990
|
init_tty();
|
|
11257
9991
|
var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
|
|
11258
9992
|
var PACKAGE_NAME = "numo-cli";
|
|
11259
9993
|
function getStatePath() {
|
|
11260
|
-
return
|
|
9994
|
+
return path2.join(getConfigDir(), "update-check.json");
|
|
11261
9995
|
}
|
|
11262
9996
|
function loadState() {
|
|
11263
9997
|
try {
|
|
11264
|
-
return JSON.parse(
|
|
9998
|
+
return JSON.parse(fs4.readFileSync(getStatePath(), "utf8"));
|
|
11265
9999
|
} catch {
|
|
11266
10000
|
return { lastCheck: 0 };
|
|
11267
10001
|
}
|
|
@@ -11269,7 +10003,7 @@ function loadState() {
|
|
|
11269
10003
|
function saveState(state) {
|
|
11270
10004
|
try {
|
|
11271
10005
|
ensureConfigDir();
|
|
11272
|
-
|
|
10006
|
+
fs4.writeFileSync(getStatePath(), JSON.stringify(state), { mode: 384 });
|
|
11273
10007
|
} catch {
|
|
11274
10008
|
}
|
|
11275
10009
|
}
|
|
@@ -11318,7 +10052,7 @@ function fetchLatestVersion(state) {
|
|
|
11318
10052
|
// src/cli/cli.ts
|
|
11319
10053
|
init_tty();
|
|
11320
10054
|
init_errors();
|
|
11321
|
-
var CLI_VERSION = true ? "1.
|
|
10055
|
+
var CLI_VERSION = true ? "1.5.0" : "0.0.0-dev";
|
|
11322
10056
|
var program2 = new Command();
|
|
11323
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) => {
|
|
11324
10058
|
const opts = thisCommand.optsWithGlobals();
|
|
@@ -11340,7 +10074,7 @@ program2.command("login").description("Login with your Numo account").option("--
|
|
|
11340
10074
|
Examples:
|
|
11341
10075
|
$ numo login # Interactive (email/password)
|
|
11342
10076
|
$ numo login --phone # SMS OTP flow`);
|
|
11343
|
-
program2.command("register").description("Create a new Numo account").option("--email <email>", "Email address").option("--password <password>", "Password (min 6
|
|
10077
|
+
program2.command("register").description("Create a new Numo account").option("--email <email>", "Email address").option("--password <password>", "Password (min 6 chars; visible in ps/history \u2014 prefer interactive mode)").action(async (opts) => {
|
|
11344
10078
|
await register(opts);
|
|
11345
10079
|
}).addHelpText("after", `
|
|
11346
10080
|
Examples:
|
|
@@ -11348,7 +10082,6 @@ Examples:
|
|
|
11348
10082
|
$ numo register --email user@example.com --password s3cret # Non-interactive`);
|
|
11349
10083
|
program2.command("logout").description("Clear stored credentials").action(() => {
|
|
11350
10084
|
clearCredentials();
|
|
11351
|
-
clearStreaks();
|
|
11352
10085
|
console.log(import_picocolors13.default.green("Logged out."));
|
|
11353
10086
|
});
|
|
11354
10087
|
program2.command("whoami").description("Show current auth status (no API call)").action(function() {
|
|
@@ -11401,17 +10134,16 @@ program2.command("add [text...]").description("Quick-add a task (today, public,
|
|
|
11401
10134
|
dataKey: "task",
|
|
11402
10135
|
spinnerMessage: "Creating task...",
|
|
11403
10136
|
onInteractive: (_task, payload) => {
|
|
11404
|
-
const task = payload
|
|
10137
|
+
const { task, karma } = payload;
|
|
11405
10138
|
const check = import_picocolors13.default.green(SYM.check);
|
|
11406
10139
|
console.log(`
|
|
11407
10140
|
${check} Created ${task.text} ${import_picocolors13.default.dim(task.id)}`);
|
|
11408
|
-
const karma = payload.karma;
|
|
11409
10141
|
if (karma) console.log(` ${formatKarmaGain(karma)}`);
|
|
11410
10142
|
console.log("");
|
|
11411
10143
|
}
|
|
11412
10144
|
});
|
|
11413
10145
|
});
|
|
11414
|
-
|
|
10146
|
+
registerPostsCommands(program2);
|
|
11415
10147
|
registerProfileCommands(program2);
|
|
11416
10148
|
registerDoctorCommand(program2);
|
|
11417
10149
|
program2.command("commands").description("List all available commands").action(function() {
|