numo-cli 1.2.0 → 1.3.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.
Files changed (2) hide show
  1. package/dist/cli.cjs +377 -1682
  2. 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 path4 = require("node:path");
993
- var fs6 = require("node:fs");
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 = path4.resolve(baseDir, baseName);
1926
- if (fs6.existsSync(localBin)) return localBin;
1927
- if (sourceExt.includes(path4.extname(baseName))) return void 0;
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) => fs6.existsSync(`${localBin}${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 = fs6.realpathSync(this._scriptPath);
1918
+ resolvedScriptPath = fs5.realpathSync(this._scriptPath);
1942
1919
  } catch (err) {
1943
1920
  resolvedScriptPath = this._scriptPath;
1944
1921
  }
1945
- executableDir = path4.resolve(
1946
- path4.dirname(resolvedScriptPath),
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 = path4.basename(
1930
+ const legacyName = path3.basename(
1954
1931
  this._scriptPath,
1955
- path4.extname(this._scriptPath)
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(path4.extname(executableFile));
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 = path4.basename(filename, path4.extname(filename));
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(path5) {
2821
- if (path5 === void 0) return this._executableDir;
2822
- this._executableDir = path5;
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,34 +3217,6 @@ var init_http = __esm({
3243
3217
  }
3244
3218
  });
3245
3219
 
3246
- // src/cli/lib/config.ts
3247
- var config_exports = {};
3248
- __export(config_exports, {
3249
- getFirebaseApiKey: () => getFirebaseApiKey,
3250
- getFirebaseAppId: () => getFirebaseAppId,
3251
- getFirebaseProjectId: () => getFirebaseProjectId,
3252
- getFirestoreBaseUrl: () => getFirestoreBaseUrl
3253
- });
3254
- function getFirestoreBaseUrl() {
3255
- const projectId = getFirebaseProjectId();
3256
- return `https://firestore.googleapis.com/v1/projects/${projectId}/databases/(default)/documents`;
3257
- }
3258
- function getFirebaseApiKey() {
3259
- return process.env.NUMO_FIREBASE_API_KEY ?? (true ? "AIzaSyAwJdEvE-ZPyGzAlJdqt_bMSsoSrHonniA" : "");
3260
- }
3261
- function getFirebaseProjectId() {
3262
- return process.env.NUMO_FIREBASE_PROJECT_ID ?? (true ? "mindist-well" : "");
3263
- }
3264
- function getFirebaseAppId() {
3265
- return process.env.NUMO_FIREBASE_APP_ID ?? (true ? "1:767603956046:web:d2c5911157e1cb017a261f" : "");
3266
- }
3267
- var init_config = __esm({
3268
- "src/cli/lib/config.ts"() {
3269
- "use strict";
3270
- init_define_ADMIN_UIDS();
3271
- }
3272
- });
3273
-
3274
3220
  // src/cli/lib/tty.ts
3275
3221
  function isInteractive() {
3276
3222
  if (!process.stdin.isTTY || !process.stdout.isTTY) return false;
@@ -3282,7 +3228,6 @@ var isUnicodeSupported;
3282
3228
  var init_tty = __esm({
3283
3229
  "src/cli/lib/tty.ts"() {
3284
3230
  "use strict";
3285
- init_define_ADMIN_UIDS();
3286
3231
  isUnicodeSupported = process.platform !== "win32" || !!process.env.WT_SESSION || process.env.TERM_PROGRAM === "vscode";
3287
3232
  }
3288
3233
  });
@@ -3291,7 +3236,6 @@ var init_tty = __esm({
3291
3236
  var require_src = __commonJS({
3292
3237
  "node_modules/sisteransi/src/index.js"(exports2, module2) {
3293
3238
  "use strict";
3294
- init_define_ADMIN_UIDS();
3295
3239
  var ESC = "\x1B";
3296
3240
  var CSI = `${ESC}[`;
3297
3241
  var beep = "\x07";
@@ -3433,7 +3377,6 @@ function St(t2, e) {
3433
3377
  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
3378
  var init_dist = __esm({
3435
3379
  "node_modules/@clack/core/dist/index.mjs"() {
3436
- init_define_ADMIN_UIDS();
3437
3380
  import_node_util = require("node:util");
3438
3381
  import_node_process = require("node:process");
3439
3382
  k = __toESM(require("node:readline"), 1);
@@ -4100,7 +4043,6 @@ function qt({ style: e = "heavy", max: r = 100, size: s = 40, ...i } = {}) {
4100
4043
  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
4044
  var init_dist2 = __esm({
4102
4045
  "node_modules/@clack/prompts/dist/index.mjs"() {
4103
- init_define_ADMIN_UIDS();
4104
4046
  init_dist();
4105
4047
  init_dist();
4106
4048
  import_node_util2 = require("node:util");
@@ -5072,7 +5014,6 @@ async function promptForMissing(opts) {
5072
5014
  var init_prompts = __esm({
5073
5015
  "src/cli/lib/prompts.ts"() {
5074
5016
  "use strict";
5075
- init_define_ADMIN_UIDS();
5076
5017
  init_tty();
5077
5018
  }
5078
5019
  });
@@ -5116,7 +5057,6 @@ var ExitCode, CliError, Errors;
5116
5057
  var init_errors = __esm({
5117
5058
  "src/cli/lib/errors.ts"() {
5118
5059
  "use strict";
5119
- init_define_ADMIN_UIDS();
5120
5060
  ExitCode = {
5121
5061
  OK: 0,
5122
5062
  GENERAL: 1,
@@ -5182,142 +5122,12 @@ var init_errors = __esm({
5182
5122
  }
5183
5123
  });
5184
5124
 
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;
5219
- }
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
5125
  // src/cli/auth/phone-login.ts
5253
5126
  var phone_login_exports = {};
5254
5127
  __export(phone_login_exports, {
5255
5128
  authenticateWithPhone: () => authenticateWithPhone
5256
5129
  });
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
- }
5304
- </script>
5305
- </body>
5306
- </html>`;
5307
- }
5308
5130
  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
5131
  const p = await Promise.resolve().then(() => (init_dist2(), dist_exports));
5322
5132
  const phone = await promptText({
5323
5133
  message: "Phone number (with country code)",
@@ -5327,63 +5137,55 @@ async function authenticateWithPhone(spinner) {
5327
5137
  if (!/^\+[0-9]{7,15}$/.test(phone)) {
5328
5138
  throw Errors.invalidInput("Invalid phone number. Use E.164 format: +<country code><number>", "Example: +380501234567");
5329
5139
  }
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}`));
5140
+ spinner.start("Starting phone verification...");
5141
+ const startResp = await http.post(`${API_BASE}/api/auth/phone/start`, {
5142
+ phoneNumber: phone
5143
+ });
5144
+ const { sessionId, verifyUrl } = startResp.data;
5145
+ spinner.stop("");
5146
+ p.log.info("Opening browser for phone verification...");
5147
+ p.log.info(import_picocolors.default.dim(`If the browser does not open, visit: ${verifyUrl}`));
5336
5148
  const { default: open } = await import("open");
5337
- const cp = await open(`http://localhost:${port}`);
5149
+ const cp = await open(verifyUrl);
5338
5150
  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");
5151
+ spinner.start("Waiting for verification in browser...");
5152
+ const deadline = Date.now() + POLL_TIMEOUT;
5153
+ while (Date.now() < deadline) {
5154
+ await new Promise((r) => setTimeout(r, POLL_INTERVAL));
5155
+ try {
5156
+ const pollResp = await http.get(`${API_BASE}/api/auth/phone/poll?session=${sessionId}`);
5157
+ if (pollResp.status === 200 && pollResp.data.idToken) {
5158
+ return {
5159
+ refreshToken: pollResp.data.refreshToken,
5160
+ uid: pollResp.data.uid,
5161
+ displayName: phone,
5162
+ idToken: pollResp.data.idToken,
5163
+ idTokenExpiry: Date.now() + (pollResp.data.expiresIn || 3600) * 1e3
5164
+ };
5165
+ }
5166
+ } catch (err) {
5167
+ if (err.response?.status === 404) {
5168
+ throw Errors.networkError("Verification session expired. Try again.");
5169
+ }
5170
+ }
5358
5171
  }
5359
- return {
5360
- refreshToken,
5361
- uid,
5362
- displayName: phoneNumber || phone,
5363
- idToken,
5364
- idTokenExpiry: idToken ? Date.now() + 3600 * 1e3 : void 0
5365
- };
5172
+ throw Errors.networkError("Phone verification timed out. Try again.");
5366
5173
  }
5367
- var crypto2, import_picocolors;
5174
+ var import_picocolors, API_BASE, POLL_INTERVAL, POLL_TIMEOUT;
5368
5175
  var init_phone_login = __esm({
5369
5176
  "src/cli/auth/phone-login.ts"() {
5370
5177
  "use strict";
5371
- init_define_ADMIN_UIDS();
5372
- crypto2 = __toESM(require("crypto"), 1);
5373
5178
  init_http();
5374
5179
  import_picocolors = __toESM(require_picocolors(), 1);
5375
5180
  init_errors();
5376
- init_local_server();
5377
- init_config();
5378
5181
  init_prompts();
5182
+ API_BASE = process.env.NUMO_API_URL ?? "http://localhost:3000";
5183
+ POLL_INTERVAL = 2e3;
5184
+ POLL_TIMEOUT = 5 * 60 * 1e3;
5379
5185
  }
5380
5186
  });
5381
5187
 
5382
- // src/cli/cli.ts
5383
- init_define_ADMIN_UIDS();
5384
-
5385
5188
  // node_modules/commander/esm.mjs
5386
- init_define_ADMIN_UIDS();
5387
5189
  var import_index = __toESM(require_commander(), 1);
5388
5190
  var {
5389
5191
  program,
@@ -5404,17 +5206,14 @@ var {
5404
5206
  var import_picocolors13 = __toESM(require_picocolors(), 1);
5405
5207
 
5406
5208
  // src/cli/auth/login.ts
5407
- init_define_ADMIN_UIDS();
5408
5209
  init_http();
5409
5210
  var import_picocolors2 = __toESM(require_picocolors(), 1);
5410
5211
 
5411
5212
  // src/cli/auth/credentials.ts
5412
- init_define_ADMIN_UIDS();
5413
5213
  var fs2 = __toESM(require("fs"), 1);
5414
5214
  var crypto = __toESM(require("crypto"), 1);
5415
5215
 
5416
5216
  // src/cli/lib/dirs.ts
5417
- init_define_ADMIN_UIDS();
5418
5217
  var fs = __toESM(require("fs"), 1);
5419
5218
  var path = __toESM(require("path"), 1);
5420
5219
  var os = __toESM(require("os"), 1);
@@ -5501,81 +5300,45 @@ async function getIdToken() {
5501
5300
  return refreshInFlight;
5502
5301
  }
5503
5302
  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" } }
5303
+ const apiBase = process.env.NUMO_API_URL ?? "http://localhost:3000";
5304
+ const { http: http2 } = await Promise.resolve().then(() => (init_http(), http_exports));
5305
+ const resp = await http2.post(
5306
+ `${apiBase}/api/auth/refresh`,
5307
+ { refreshToken: creds.refreshToken }
5512
5308
  );
5513
- creds.idToken = resp.data.id_token;
5514
- creds.idTokenExpiry = Date.now() + (parseInt(resp.data.expires_in) || 3600) * 1e3;
5309
+ creds.idToken = resp.data.idToken;
5310
+ creds.refreshToken = resp.data.refreshToken ?? creds.refreshToken;
5311
+ creds.idTokenExpiry = Date.now() + (resp.data.expiresIn || 3600) * 1e3;
5515
5312
  saveCredentials(creds);
5516
5313
  return creds.idToken;
5517
5314
  }
5518
5315
 
5519
5316
  // src/cli/auth/login.ts
5520
- init_config();
5521
5317
  init_prompts();
5522
5318
  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
5319
+ var API_BASE2 = process.env.NUMO_API_URL ?? "http://localhost:3000";
5549
5320
  async function authenticateWithEmail(spinner) {
5550
- const fbApiKey = getFirebaseApiKey();
5551
- if (!fbApiKey) {
5552
- throw Errors.configMissing("NUMO_FIREBASE_API_KEY");
5553
- }
5554
5321
  const email = await promptText({ message: "Email", required: true });
5555
5322
  const password = await promptPassword({ message: "Password" });
5556
5323
  spinner.start("Signing in...");
5557
5324
  const resp = await http.post(
5558
- `https://identitytoolkit.googleapis.com/v1/accounts:signInWithPassword?key=${fbApiKey}`,
5559
- { email, password, returnSecureToken: true },
5560
- { headers: { "Content-Type": "application/json" } }
5325
+ `${API_BASE2}/api/auth/login`,
5326
+ { email, password }
5561
5327
  );
5562
5328
  return {
5563
5329
  refreshToken: resp.data.refreshToken,
5564
- uid: resp.data.localId,
5565
- displayName: resp.data.email,
5330
+ uid: resp.data.uid,
5331
+ displayName: resp.data.email ?? email,
5566
5332
  idToken: resp.data.idToken,
5567
- idTokenExpiry: Date.now() + (parseInt(resp.data.expiresIn) || 3600) * 1e3
5333
+ idTokenExpiry: Date.now() + (resp.data.expiresIn || 3600) * 1e3
5568
5334
  };
5569
5335
  }
5570
5336
  function printSuccess(displayName) {
5571
5337
  const lines = [
5572
5338
  ` ${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`
5339
+ ` ${import_picocolors2.default.dim("$")} numo tasks create --text "..." Create a task`,
5340
+ ` ${import_picocolors2.default.dim("$")} numo profile View your profile`
5574
5341
  ];
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
5342
  console.log(`
5580
5343
  ${import_picocolors2.default.bold("Available commands:")}
5581
5344
  ${lines.join("\n")}
@@ -5623,176 +5386,12 @@ async function login(options = {}) {
5623
5386
  }
5624
5387
 
5625
5388
  // src/cli/auth/register.ts
5626
- init_define_ADMIN_UIDS();
5627
5389
  init_http();
5628
5390
  var import_picocolors3 = __toESM(require_picocolors(), 1);
5629
- init_config();
5630
5391
  init_prompts();
5631
5392
  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
5393
  init_tty();
5394
+ var API_BASE3 = process.env.NUMO_API_URL ?? "http://localhost:3000";
5796
5395
  function validateEmail(email) {
5797
5396
  const trimmed = email.trim();
5798
5397
  if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(trimmed)) {
@@ -5806,92 +5405,44 @@ function validatePassword(password) {
5806
5405
  }
5807
5406
  return password;
5808
5407
  }
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
5408
  function classifySignUpError(err) {
5838
5409
  if (err instanceof CliError) return err;
5839
5410
  const resp = err?.response?.data?.error;
5411
+ const kind = resp?.kind ?? "";
5840
5412
  const msg = resp?.message ?? "";
5841
- if (msg.includes("EMAIL_EXISTS")) {
5413
+ if (kind === "CONFLICT" || msg.includes("already in use")) {
5842
5414
  return Errors.invalidInput(
5843
5415
  "Email already in use",
5844
5416
  "Already have an account? Run: numo login"
5845
5417
  );
5846
5418
  }
5847
- if (msg.includes("INVALID_EMAIL")) {
5419
+ if (msg.includes("Invalid email")) {
5848
5420
  return Errors.invalidInput("Invalid email address");
5849
5421
  }
5850
- if (msg.includes("WEAK_PASSWORD")) {
5422
+ if (msg.includes("Password too weak") || msg.includes("min 6")) {
5851
5423
  return Errors.invalidInput("Password is too weak (min 6 characters)");
5852
5424
  }
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
5425
  return classifyError(err);
5861
5426
  }
5862
5427
  async function signUp(email, password) {
5863
- const fbApiKey = getFirebaseApiKey();
5864
- if (!fbApiKey) throw Errors.configMissing("NUMO_FIREBASE_API_KEY");
5865
5428
  try {
5866
- const resp = await http.post(
5867
- `https://identitytoolkit.googleapis.com/v1/accounts:signUp?key=${fbApiKey}`,
5868
- { email, password, returnSecureToken: true },
5869
- { headers: { "Content-Type": "application/json" } }
5870
- );
5429
+ const resp = await http.post(`${API_BASE3}/api/auth/register`, {
5430
+ email,
5431
+ password,
5432
+ tz: Intl.DateTimeFormat().resolvedOptions().timeZone,
5433
+ tzOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset()
5434
+ });
5871
5435
  return {
5872
5436
  refreshToken: resp.data.refreshToken,
5873
- uid: resp.data.localId,
5874
- displayName: resp.data.email,
5437
+ uid: resp.data.uid,
5438
+ displayName: resp.data.email ?? email,
5875
5439
  idToken: resp.data.idToken,
5876
- idTokenExpiry: Date.now() + (parseInt(resp.data.expiresIn) || 3600) * 1e3
5440
+ idTokenExpiry: Date.now() + (resp.data.expiresIn || 3600) * 1e3
5877
5441
  };
5878
5442
  } catch (err) {
5879
5443
  throw classifySignUpError(err);
5880
5444
  }
5881
5445
  }
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
5446
  async function register(options = {}) {
5896
5447
  const p = await Promise.resolve().then(() => (init_dist2(), dist_exports));
5897
5448
  p.intro(import_picocolors3.default.bold("Numo \u2014 Register"));
@@ -5919,8 +5470,6 @@ async function register(options = {}) {
5919
5470
  idToken: result.idToken,
5920
5471
  idTokenExpiry: result.idTokenExpiry
5921
5472
  });
5922
- s.message("Setting up profile...");
5923
- await setupUserProfile(result.uid, email);
5924
5473
  s.stop(`Registered as ${import_picocolors3.default.green(result.displayName)}`);
5925
5474
  p.outro("Welcome to Numo!");
5926
5475
  printSuccess(result.displayName);
@@ -5933,86 +5482,17 @@ async function register(options = {}) {
5933
5482
  }
5934
5483
  }
5935
5484
 
5936
- // src/cli/lib/streaks.ts
5937
- init_define_ADMIN_UIDS();
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
- }
6000
-
6001
5485
  // src/cli/commands/tasks.ts
6002
- init_define_ADMIN_UIDS();
6003
5486
  var import_picocolors8 = __toESM(require_picocolors(), 1);
6004
5487
 
6005
5488
  // src/cli/lib/actions.ts
6006
- init_define_ADMIN_UIDS();
6007
5489
  init_tty();
6008
5490
 
6009
5491
  // src/cli/lib/output.ts
6010
- init_define_ADMIN_UIDS();
6011
5492
  var import_picocolors5 = __toESM(require_picocolors(), 1);
6012
5493
  init_tty();
6013
5494
 
6014
5495
  // src/cli/lib/table.ts
6015
- init_define_ADMIN_UIDS();
6016
5496
  var import_picocolors4 = __toESM(require_picocolors(), 1);
6017
5497
  init_tty();
6018
5498
  var BOX = isUnicodeSupported ? {
@@ -6155,12 +5635,10 @@ ${import_picocolors5.default.red("Error")}: ${structured.message}`);
6155
5635
  }
6156
5636
 
6157
5637
  // src/cli/lib/spinner.ts
6158
- init_define_ADMIN_UIDS();
6159
5638
  var import_picocolors6 = __toESM(require_picocolors(), 1);
6160
5639
  init_tty();
6161
5640
 
6162
5641
  // src/cli/lib/symbols.ts
6163
- init_define_ADMIN_UIDS();
6164
5642
  init_tty();
6165
5643
  var u = isUnicodeSupported;
6166
5644
  var SYM = {
@@ -6359,7 +5837,7 @@ async function runWrite(opts) {
6359
5837
  if (useJson(opts.global)) {
6360
5838
  printJson(selectFields(payload, opts.global.json));
6361
5839
  } else if (opts.onInteractive) {
6362
- opts.onInteractive(item);
5840
+ opts.onInteractive(payload);
6363
5841
  } else {
6364
5842
  if (opts.successMessage) console.log(opts.successMessage);
6365
5843
  if (item && Object.keys(item).length > 0) {
@@ -6385,642 +5863,143 @@ async function runDelete(opts) {
6385
5863
  }
6386
5864
  }
6387
5865
 
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;
5866
+ // src/cli/lib/uid.ts
5867
+ init_errors();
5868
+ function requireUid() {
5869
+ const creds = loadCredentials();
5870
+ if (!creds) throw Errors.authRequired();
5871
+ return creds.uid;
5872
+ }
6433
5873
 
6434
- // src/cli/lib/validation.ts
6435
- init_define_ADMIN_UIDS();
5874
+ // src/cli/lib/api-client.ts
5875
+ init_http();
6436
5876
  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)`);
5877
+ var API_BASE4 = process.env.NUMO_API_URL ?? "http://localhost:3000";
5878
+ if (API_BASE4 !== "http://localhost:3000" && API_BASE4.startsWith("http://")) {
5879
+ process.stderr.write("[warn] NUMO_API_URL uses HTTP \u2014 tokens sent unencrypted. Use HTTPS in production.\n");
5880
+ }
5881
+ var KIND_EXIT = {
5882
+ AUTH_REQUIRED: ExitCode.NO_PERM,
5883
+ AUTH_EXPIRED: ExitCode.NO_PERM,
5884
+ AUTH_FORBIDDEN: ExitCode.NO_PERM,
5885
+ INVALID_INPUT: ExitCode.USAGE,
5886
+ MISSING_ARGUMENT: ExitCode.USAGE,
5887
+ NOT_FOUND: ExitCode.NOT_FOUND,
5888
+ CONFLICT: ExitCode.CONFLICT,
5889
+ RATE_LIMITED: ExitCode.TEMP_FAIL,
5890
+ NETWORK_ERROR: ExitCode.UNAVAILABLE,
5891
+ TIMEOUT: ExitCode.TEMP_FAIL,
5892
+ SERVICE_UNAVAILABLE: ExitCode.UNAVAILABLE
5893
+ };
5894
+ function toCliError(err) {
5895
+ if (err instanceof CliError) return err;
5896
+ const httpErr = err;
5897
+ if (httpErr.code === "ECONNABORTED" || httpErr.code === "ETIMEDOUT") {
5898
+ return new CliError("TIMEOUT" /* TIMEOUT */, "Request timed out", ExitCode.TEMP_FAIL, {
5899
+ hint: "The API server took too long to respond.",
5900
+ retryable: true
5901
+ });
6456
5902
  }
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);
5903
+ if (httpErr.code === "ECONNREFUSED" || httpErr.code === "ECONNRESET" || httpErr.code === "ENOTFOUND") {
5904
+ return new CliError("NETWORK_ERROR" /* NETWORK_ERROR */, "Can't reach Numo API", ExitCode.UNAVAILABLE, {
5905
+ hint: "Is the API server running? Check NUMO_API_URL.",
5906
+ retryable: true
5907
+ });
6463
5908
  }
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 {
5909
+ const body = httpErr.response?.data;
5910
+ if (body?.error) {
5911
+ const e = body.error;
5912
+ const kind = e.kind ?? "UNKNOWN" /* UNKNOWN */;
5913
+ const exitCode = KIND_EXIT[kind] ?? ExitCode.GENERAL;
5914
+ return new CliError(kind, e.message ?? "Unknown error", exitCode, {
5915
+ retryable: e.retryable,
5916
+ retryAfter: e.retryAfter
5917
+ });
6503
5918
  }
5919
+ return new CliError("UNKNOWN" /* UNKNOWN */, httpErr.message ?? "Unknown error", ExitCode.GENERAL);
6504
5920
  }
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;
5921
+ async function apiHeaders() {
5922
+ const token = await getIdToken();
5923
+ return {
5924
+ Authorization: `Bearer ${token}`,
5925
+ "Content-Type": "application/json"
6591
5926
  };
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
5927
  }
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
- }
5928
+ function url(path3, params) {
5929
+ const u2 = `${API_BASE4}${path3}`;
5930
+ if (!params) return u2;
5931
+ const sp = new URLSearchParams();
5932
+ for (const [k2, v] of Object.entries(params)) {
5933
+ if (v !== void 0) sp.set(k2, v);
6652
5934
  }
6653
- return result;
5935
+ const qs = sp.toString();
5936
+ return qs ? `${u2}?${qs}` : u2;
6654
5937
  }
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
- `);
5938
+ var api = {
5939
+ async get(path3, params) {
5940
+ try {
5941
+ const resp = await http.get(url(path3, params), { headers: await apiHeaders() });
5942
+ return resp.data;
5943
+ } catch (err) {
5944
+ throw toCliError(err);
6669
5945
  }
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"]);
5946
+ },
5947
+ async post(path3, body) {
5948
+ try {
5949
+ const resp = await http.post(url(path3), body, { headers: await apiHeaders() });
5950
+ return resp.data;
5951
+ } catch (err) {
5952
+ throw toCliError(err);
6691
5953
  }
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 });
5954
+ },
5955
+ async patch(path3, body) {
5956
+ try {
5957
+ const resp = await http.patch(url(path3), body, { headers: await apiHeaders() });
5958
+ return resp.data;
5959
+ } catch (err) {
5960
+ throw toCliError(err);
6713
5961
  }
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) {
6734
- try {
6735
- const doc = await getDoc(routineStreakPath(uid, taskId));
6736
- const newStreak = Math.max(0, (doc.streak ?? 0) - 1);
6737
- await setDoc(routineStreakPath(uid, taskId), {
6738
- ...doc,
6739
- streak: newStreak,
6740
- lastCompletedAt: newStreak > 0 ? doc.lastCompletedAt : null
6741
- });
6742
- } catch {
6743
- }
6744
- }
6745
- async function deleteRoutineStreak(uid, taskId) {
6746
- try {
6747
- await deleteDoc(routineStreakPath(uid, taskId));
6748
- } catch {
6749
- }
6750
- }
6751
- function computeCompleteKarma(task, checksInRow) {
6752
- const difficultyIdx = task.difficulty ?? 0;
6753
- const bonus = DIFFICULTY_BONUS[difficultyIdx] ?? 0;
6754
- const raw = (KARMA_POINTS.completeTask + bonus) * checksInRow;
6755
- return Math.min(raw, MAX_KARMA_PER_COMPLETE);
6756
- }
6757
- async function listTasks(uid, opts) {
6758
- const [activeTasks, historyTasks] = await Promise.all([
6759
- runQuery(`users/${uid}`, "tasks", { where: [], limit: MAX_TASKS_PER_REQUEST }),
6760
- opts.backlog ? Promise.resolve([]) : runQuery(`users/${uid}`, "tasksHistory", { where: [], limit: MAX_TASKS_PER_REQUEST })
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);
5962
+ },
5963
+ async del(path3) {
5964
+ try {
5965
+ const resp = await http.delete(url(path3), { headers: await apiHeaders() });
5966
+ return resp.data;
5967
+ } catch (err) {
5968
+ throw toCliError(err);
6774
5969
  }
6775
- completed = completed.filter((t2) => {
6776
- if (typeof t2.dueDate !== "string") return false;
6777
- return t2.dueDate.slice(0, 10) === opts.date;
6778
- });
6779
5970
  }
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;
5971
+ };
5972
+
5973
+ // src/cli/services/tasks.ts
5974
+ async function listTasks(uid, opts) {
5975
+ return api.get("/api/tasks", {
5976
+ date: opts.date,
5977
+ backlog: opts.backlog ? "true" : void 0,
5978
+ tag: opts.tag
6783
5979
  });
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
5980
  }
6789
5981
  async function getTask(uid, id) {
6790
- validateDocId(id, "Task ID");
6791
- return getDoc(taskPath(uid, id));
5982
+ return api.get(`/api/tasks/${encodeURIComponent(id)}`);
6792
5983
  }
6793
5984
  async function createTask(uid, body) {
6794
- const now2 = Date.now();
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 };
5985
+ return api.post("/api/tasks", body);
6830
5986
  }
6831
5987
  async function updateTask(uid, id, body) {
6832
- validateDocId(id, "Task ID");
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 };
5988
+ return api.patch(`/api/tasks/${encodeURIComponent(id)}`, body);
6859
5989
  }
6860
5990
  async function deleteTask(uid, id) {
6861
- validateDocId(id, "Task ID");
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 };
5991
+ return api.del(`/api/tasks/${encodeURIComponent(id)}`);
6903
5992
  }
6904
5993
  async function completeTask(uid, id, date) {
6905
- validateDocId(id, "Task ID");
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 };
5994
+ return api.post(`/api/tasks/${encodeURIComponent(id)}/complete`, date ? { date } : void 0);
6971
5995
  }
6972
5996
  async function uncompleteTask(uid, taskHistoryId) {
6973
- validateDocId(taskHistoryId, "Task history ID");
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 };
5997
+ return api.post(`/api/tasks/${encodeURIComponent(taskHistoryId)}/uncomplete`);
7018
5998
  }
7019
5999
 
7020
6000
  // src/cli/lib/format.ts
7021
- init_define_ADMIN_UIDS();
7022
6001
  var import_picocolors7 = __toESM(require_picocolors(), 1);
7023
- function formatDate2(ts) {
6002
+ function formatDate(ts) {
7024
6003
  if (ts == null) return "";
7025
6004
  const d = typeof ts === "number" ? new Date(ts) : new Date(ts);
7026
6005
  if (isNaN(d.getTime())) return String(ts);
@@ -7043,7 +6022,7 @@ function formatRelativeDate(ts) {
7043
6022
  if (hours < 24) return `${hours}h ago`;
7044
6023
  const days = Math.floor(hours / 24);
7045
6024
  if (days < 30) return `${days}d ago`;
7046
- return formatDate2(ts);
6025
+ return formatDate(ts);
7047
6026
  }
7048
6027
  function formatTags(tags) {
7049
6028
  if (!Array.isArray(tags) || tags.length === 0) return "";
@@ -7121,26 +6100,7 @@ init_prompts();
7121
6100
  init_tty();
7122
6101
  init_errors();
7123
6102
 
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
6103
  // node_modules/chrono-node/dist/esm/types.js
7143
- init_define_ADMIN_UIDS();
7144
6104
  var Meridiem;
7145
6105
  (function(Meridiem2) {
7146
6106
  Meridiem2[Meridiem2["AM"] = 0] = "AM";
@@ -7199,7 +6159,6 @@ function implySimilarTime(component, target) {
7199
6159
  }
7200
6160
 
7201
6161
  // node_modules/chrono-node/dist/esm/timezone.js
7202
- init_define_ADMIN_UIDS();
7203
6162
  var TIMEZONE_ABBR_MAP = {
7204
6163
  ACDT: 630,
7205
6164
  ACST: 570,
@@ -7469,7 +6428,6 @@ function toTimezoneOffset(timezoneInput, date, timezoneOverrides = {}) {
7469
6428
  }
7470
6429
 
7471
6430
  // node_modules/chrono-node/dist/esm/calculation/duration.js
7472
- init_define_ADMIN_UIDS();
7473
6431
  var EmptyDuration = {
7474
6432
  day: 0,
7475
6433
  second: 0,
@@ -7870,17 +6828,7 @@ var ParsingResult = class _ParsingResult {
7870
6828
  }
7871
6829
  };
7872
6830
 
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
6831
  // node_modules/chrono-node/dist/esm/utils/pattern.js
7883
- init_define_ADMIN_UIDS();
7884
6832
  function repeatedTimeunitPattern(prefix, singleTimeunitPattern, connectorPattern = "\\s{0,5},?\\s{0,5}") {
7885
6833
  const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:");
7886
6834
  return `${prefix}${singleTimeunitPatternNoCapture}(?:${connectorPattern}${singleTimeunitPatternNoCapture}){0,10}`;
@@ -7902,7 +6850,6 @@ function matchAnyPattern(dictionary) {
7902
6850
  }
7903
6851
 
7904
6852
  // node_modules/chrono-node/dist/esm/calculation/years.js
7905
- init_define_ADMIN_UIDS();
7906
6853
  function findMostLikelyADYear(yearNumber) {
7907
6854
  if (yearNumber < 100) {
7908
6855
  if (yearNumber > 50) {
@@ -8180,7 +7127,6 @@ function collectDateTimeFragment(fragments, match) {
8180
7127
  }
8181
7128
 
8182
7129
  // node_modules/chrono-node/dist/esm/common/parsers/AbstractParserWithWordBoundary.js
8183
- init_define_ADMIN_UIDS();
8184
7130
  var AbstractParserWithWordBoundaryChecking = class {
8185
7131
  innerPatternHasChange(context, currentInnerPattern) {
8186
7132
  return this.innerPattern(context) !== currentInnerPattern;
@@ -8240,7 +7186,6 @@ var ENTimeUnitWithinFormatParser = class extends AbstractParserWithWordBoundaryC
8240
7186
  };
8241
7187
 
8242
7188
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameLittleEndianParser.js
8243
- init_define_ADMIN_UIDS();
8244
7189
  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
7190
  var DATE_GROUP = 1;
8246
7191
  var DATE_TO_GROUP = 2;
@@ -8277,7 +7222,6 @@ var ENMonthNameLittleEndianParser = class extends AbstractParserWithWordBoundary
8277
7222
  };
8278
7223
 
8279
7224
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameMiddleEndianParser.js
8280
- init_define_ADMIN_UIDS();
8281
7225
  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
7226
  var MONTH_NAME_GROUP2 = 1;
8283
7227
  var DATE_GROUP2 = 2;
@@ -8327,7 +7271,6 @@ var ENMonthNameMiddleEndianParser = class extends AbstractParserWithWordBoundary
8327
7271
  };
8328
7272
 
8329
7273
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameParser.js
8330
- init_define_ADMIN_UIDS();
8331
7274
  var PATTERN3 = new RegExp(`((?:in)\\s*)?(${matchAnyPattern(MONTH_DICTIONARY)})\\s*(?:(?:,|-|of)?\\s*(${YEAR_PATTERN})?)?(?=[^\\s\\w]|\\s+[^0-9]|\\s+$|$)`, "i");
8332
7275
  var PREFIX_GROUP = 1;
8333
7276
  var MONTH_NAME_GROUP3 = 2;
@@ -8358,7 +7301,6 @@ var ENMonthNameParser = class extends AbstractParserWithWordBoundaryChecking {
8358
7301
  };
8359
7302
 
8360
7303
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENYearMonthDayParser.js
8361
- init_define_ADMIN_UIDS();
8362
7304
  var PATTERN4 = new RegExp(`([0-9]{4})[-\\.\\/\\s](?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[-\\.\\/\\s]([0-9]{1,2})(?=\\W|$)`, "i");
8363
7305
  var YEAR_NUMBER_GROUP = 1;
8364
7306
  var MONTH_NAME_GROUP4 = 2;
@@ -8397,7 +7339,6 @@ var ENYearMonthDayParser = class extends AbstractParserWithWordBoundaryChecking
8397
7339
  };
8398
7340
 
8399
7341
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENSlashMonthFormatParser.js
8400
- init_define_ADMIN_UIDS();
8401
7342
  var PATTERN5 = new RegExp("([0-9]|0[1-9]|1[012])/([0-9]{4})", "i");
8402
7343
  var MONTH_GROUP = 1;
8403
7344
  var YEAR_GROUP4 = 2;
@@ -8412,11 +7353,7 @@ var ENSlashMonthFormatParser = class extends AbstractParserWithWordBoundaryCheck
8412
7353
  }
8413
7354
  };
8414
7355
 
8415
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeExpressionParser.js
8416
- init_define_ADMIN_UIDS();
8417
-
8418
7356
  // node_modules/chrono-node/dist/esm/common/parsers/AbstractTimeExpressionParser.js
8419
- init_define_ADMIN_UIDS();
8420
7357
  function primaryTimePattern(leftBoundary, primaryPrefix, primarySuffix, flags) {
8421
7358
  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
7359
  }
@@ -8772,7 +7709,6 @@ var ENTimeExpressionParser = class extends AbstractTimeExpressionParser {
8772
7709
  };
8773
7710
 
8774
7711
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js
8775
- init_define_ADMIN_UIDS();
8776
7712
  var PATTERN6 = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=\\W|$)`, "i");
8777
7713
  var STRICT_PATTERN = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=\\W|$)`, "i");
8778
7714
  var ENTimeUnitAgoFormatParser = class extends AbstractParserWithWordBoundaryChecking {
@@ -8794,7 +7730,6 @@ var ENTimeUnitAgoFormatParser = class extends AbstractParserWithWordBoundaryChec
8794
7730
  };
8795
7731
 
8796
7732
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js
8797
- init_define_ADMIN_UIDS();
8798
7733
  var PATTERN7 = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:later|after|from now|henceforth|forward|out)(?=(?:\\W|$))`, "i");
8799
7734
  var STRICT_PATTERN2 = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(later|after|from now)(?=\\W|$)`, "i");
8800
7735
  var GROUP_NUM_TIMEUNITS = 1;
@@ -8816,14 +7751,7 @@ var ENTimeUnitLaterFormatParser = class extends AbstractParserWithWordBoundaryCh
8816
7751
  }
8817
7752
  };
8818
7753
 
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
7754
  // node_modules/chrono-node/dist/esm/common/abstractRefiners.js
8826
- init_define_ADMIN_UIDS();
8827
7755
  var Filter = class {
8828
7756
  refine(context, results) {
8829
7757
  return results.filter((r) => this.isValid(context, r));
@@ -8921,14 +7849,7 @@ var ENMergeDateRangeRefiner = class extends AbstractMergeDateRangeRefiner {
8921
7849
  }
8922
7850
  };
8923
7851
 
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
7852
  // node_modules/chrono-node/dist/esm/calculation/mergingCalculation.js
8931
- init_define_ADMIN_UIDS();
8932
7853
  function mergeDateTimeResult(dateResult, timeResult) {
8933
7854
  const result = dateResult.clone();
8934
7855
  const beginDate = dateResult.start;
@@ -9013,11 +7934,7 @@ var ENMergeDateTimeRefiner = class extends AbstractMergeDateTimeRefiner {
9013
7934
  }
9014
7935
  };
9015
7936
 
9016
- // node_modules/chrono-node/dist/esm/configurations.js
9017
- init_define_ADMIN_UIDS();
9018
-
9019
7937
  // node_modules/chrono-node/dist/esm/common/refiners/ExtractTimezoneAbbrRefiner.js
9020
- init_define_ADMIN_UIDS();
9021
7938
  var TIMEZONE_NAME_PATTERN = new RegExp("^\\s*,?\\s*\\(?([A-Z]{2,4})\\)?(?=\\W|$)", "i");
9022
7939
  var ExtractTimezoneAbbrRefiner = class {
9023
7940
  timezoneOverrides;
@@ -9069,7 +7986,6 @@ var ExtractTimezoneAbbrRefiner = class {
9069
7986
  };
9070
7987
 
9071
7988
  // node_modules/chrono-node/dist/esm/common/refiners/ExtractTimezoneOffsetRefiner.js
9072
- init_define_ADMIN_UIDS();
9073
7989
  var TIMEZONE_OFFSET_PATTERN = new RegExp("^\\s*(?:\\(?(?:GMT|UTC)\\s?)?([+-])(\\d{1,2})(?::?(\\d{2}))?\\)?", "i");
9074
7990
  var TIMEZONE_OFFSET_SIGN_GROUP = 1;
9075
7991
  var TIMEZONE_OFFSET_HOUR_OFFSET_GROUP = 2;
@@ -9108,7 +8024,6 @@ var ExtractTimezoneOffsetRefiner = class {
9108
8024
  };
9109
8025
 
9110
8026
  // node_modules/chrono-node/dist/esm/common/refiners/OverlapRemovalRefiner.js
9111
- init_define_ADMIN_UIDS();
9112
8027
  var OverlapRemovalRefiner = class {
9113
8028
  refine(context, results) {
9114
8029
  if (results.length < 2) {
@@ -9145,7 +8060,6 @@ var OverlapRemovalRefiner = class {
9145
8060
  };
9146
8061
 
9147
8062
  // node_modules/chrono-node/dist/esm/common/refiners/ForwardDateRefiner.js
9148
- init_define_ADMIN_UIDS();
9149
8063
  var ForwardDateRefiner = class {
9150
8064
  refine(context, results) {
9151
8065
  if (!context.option.forwardDate) {
@@ -9211,7 +8125,6 @@ var ForwardDateRefiner = class {
9211
8125
  };
9212
8126
 
9213
8127
  // node_modules/chrono-node/dist/esm/common/refiners/UnlikelyFormatFilter.js
9214
- init_define_ADMIN_UIDS();
9215
8128
  var UnlikelyFormatFilter = class extends Filter {
9216
8129
  strictMode;
9217
8130
  constructor(strictMode) {
@@ -9254,7 +8167,6 @@ var UnlikelyFormatFilter = class extends Filter {
9254
8167
  };
9255
8168
 
9256
8169
  // node_modules/chrono-node/dist/esm/common/parsers/ISOFormatParser.js
9257
- init_define_ADMIN_UIDS();
9258
8170
  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
8171
  var YEAR_NUMBER_GROUP2 = 1;
9260
8172
  var MONTH_NUMBER_GROUP2 = 2;
@@ -9308,7 +8220,6 @@ var ISOFormatParser = class extends AbstractParserWithWordBoundaryChecking {
9308
8220
  };
9309
8221
 
9310
8222
  // node_modules/chrono-node/dist/esm/common/refiners/MergeWeekdayComponentRefiner.js
9311
- init_define_ADMIN_UIDS();
9312
8223
  var MergeWeekdayComponentRefiner = class extends MergingRefiner {
9313
8224
  mergeResults(textBetween, currentResult, nextResult) {
9314
8225
  const newResult = nextResult.clone();
@@ -9339,11 +8250,7 @@ function includeCommonConfiguration(configuration2, strictMode = false) {
9339
8250
  return configuration2;
9340
8251
  }
9341
8252
 
9342
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENCasualDateParser.js
9343
- init_define_ADMIN_UIDS();
9344
-
9345
8253
  // node_modules/chrono-node/dist/esm/common/casualReferences.js
9346
- init_define_ADMIN_UIDS();
9347
8254
  function now(reference) {
9348
8255
  const targetDate = reference.getDateWithAdjustedTimezone();
9349
8256
  const component = new ParsingComponents(reference, {});
@@ -9489,7 +8396,6 @@ var ENCasualDateParser = class extends AbstractParserWithWordBoundaryChecking {
9489
8396
  };
9490
8397
 
9491
8398
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENCasualTimeParser.js
9492
- init_define_ADMIN_UIDS();
9493
8399
  var PATTERN10 = /(?:this)?\s{0,3}(morning|afternoon|evening|night|midnight|midday|noon)(?=\W|$)/i;
9494
8400
  var ENCasualTimeParser = class extends AbstractParserWithWordBoundaryChecking {
9495
8401
  innerPattern() {
@@ -9523,11 +8429,7 @@ var ENCasualTimeParser = class extends AbstractParserWithWordBoundaryChecking {
9523
8429
  }
9524
8430
  };
9525
8431
 
9526
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENWeekdayParser.js
9527
- init_define_ADMIN_UIDS();
9528
-
9529
8432
  // node_modules/chrono-node/dist/esm/calculation/weekdays.js
9530
- init_define_ADMIN_UIDS();
9531
8433
  function createParsingComponentsAtWeekday(reference, weekday, modifier) {
9532
8434
  const refDate = reference.getDateWithAdjustedTimezone();
9533
8435
  const daysToWeekday = getDaysToWeekday(refDate, weekday, modifier);
@@ -9630,7 +8532,6 @@ var ENWeekdayParser = class extends AbstractParserWithWordBoundaryChecking {
9630
8532
  };
9631
8533
 
9632
8534
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENRelativeDateFormatParser.js
9633
- init_define_ADMIN_UIDS();
9634
8535
  var PATTERN12 = new RegExp(`(this|last|past|next|after\\s*this)\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})(?=\\s*)(?=\\W|$)`, "i");
9635
8536
  var MODIFIER_WORD_GROUP = 1;
9636
8537
  var RELATIVE_WORD_GROUP = 2;
@@ -9676,7 +8577,6 @@ var ENRelativeDateFormatParser = class extends AbstractParserWithWordBoundaryChe
9676
8577
  };
9677
8578
 
9678
8579
  // node_modules/chrono-node/dist/esm/common/parsers/SlashDateFormatParser.js
9679
- init_define_ADMIN_UIDS();
9680
8580
  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
8581
  var OPENING_GROUP = 1;
9682
8582
  var ENDING_GROUP = 5;
@@ -9745,7 +8645,6 @@ var SlashDateFormatParser = class {
9745
8645
  };
9746
8646
 
9747
8647
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js
9748
- init_define_ADMIN_UIDS();
9749
8648
  var PATTERN14 = new RegExp(`(this|last|past|next|after|\\+|-)\\s*(${TIME_UNITS_PATTERN})(?=\\W|$)`, "i");
9750
8649
  var PATTERN_NO_ABBR = new RegExp(`(this|last|past|next|after|\\+|-)\\s*(${TIME_UNITS_NO_ABBR_PATTERN})(?=\\W|$)`, "i");
9751
8650
  var ENTimeUnitCasualRelativeFormatParser = class extends AbstractParserWithWordBoundaryChecking {
@@ -9775,7 +8674,6 @@ var ENTimeUnitCasualRelativeFormatParser = class extends AbstractParserWithWordB
9775
8674
  };
9776
8675
 
9777
8676
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeRelativeAfterDateRefiner.js
9778
- init_define_ADMIN_UIDS();
9779
8677
  function IsPositiveFollowingReference(result) {
9780
8678
  return result.text.match(/^[+-]/i) != null;
9781
8679
  }
@@ -9800,7 +8698,6 @@ var ENMergeRelativeAfterDateRefiner = class extends MergingRefiner {
9800
8698
  };
9801
8699
 
9802
8700
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeRelativeFollowByDateRefiner.js
9803
- init_define_ADMIN_UIDS();
9804
8701
  function hasImpliedEarlierReferenceDate(result) {
9805
8702
  return result.text.match(/\s+(before|from)$/i) != null;
9806
8703
  }
@@ -9831,7 +8728,6 @@ var ENMergeRelativeFollowByDateRefiner = class extends MergingRefiner {
9831
8728
  };
9832
8729
 
9833
8730
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENExtractYearSuffixRefiner.js
9834
- init_define_ADMIN_UIDS();
9835
8731
  var YEAR_SUFFIX_PATTERN = new RegExp(`^\\s*(${YEAR_PATTERN})`, "i");
9836
8732
  var YEAR_GROUP6 = 1;
9837
8733
  var ENExtractYearSuffixRefiner = class {
@@ -9863,7 +8759,6 @@ var ENExtractYearSuffixRefiner = class {
9863
8759
  };
9864
8760
 
9865
8761
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENUnlikelyFormatFilter.js
9866
- init_define_ADMIN_UIDS();
9867
8762
  var ENUnlikelyFormatFilter = class extends Filter {
9868
8763
  constructor() {
9869
8764
  super();
@@ -10045,7 +8940,7 @@ var GB = new Chrono(configuration.createCasualConfiguration(true));
10045
8940
 
10046
8941
  // node_modules/chrono-node/dist/esm/index.js
10047
8942
  var casual2 = casual;
10048
- function parseDate2(text, ref, option) {
8943
+ function parseDate(text, ref, option) {
10049
8944
  return casual2.parseDate(text, ref, option);
10050
8945
  }
10051
8946
 
@@ -10054,7 +8949,7 @@ function parseHumanDate(input) {
10054
8949
  const trimmed = input.trim();
10055
8950
  if (!trimmed) return null;
10056
8951
  if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) return trimmed;
10057
- const parsed = parseDate2(trimmed, /* @__PURE__ */ new Date(), { forwardDate: true });
8952
+ const parsed = parseDate(trimmed, /* @__PURE__ */ new Date(), { forwardDate: true });
10058
8953
  if (!parsed) return null;
10059
8954
  const date = parsed.toISOString().slice(0, 10);
10060
8955
  const hours = parsed.getHours();
@@ -10072,10 +8967,9 @@ function parseHumanDateOnly(input) {
10072
8967
  }
10073
8968
 
10074
8969
  // src/cli/lib/stdin.ts
10075
- init_define_ADMIN_UIDS();
10076
- var fs4 = __toESM(require("fs"), 1);
8970
+ var fs3 = __toESM(require("fs"), 1);
10077
8971
  function readStdinLines() {
10078
- const input = fs4.readFileSync(0, "utf8");
8972
+ const input = fs3.readFileSync(0, "utf8");
10079
8973
  return input.split("\n").map((l) => l.trim()).filter(Boolean);
10080
8974
  }
10081
8975
 
@@ -10095,7 +8989,7 @@ async function pickTask(uid, id, actionName) {
10095
8989
  message: `Select task to ${actionName}`,
10096
8990
  options: pending.map((t2) => ({
10097
8991
  value: t2.id,
10098
- label: `${truncate(String(t2.text), 50)} ${import_picocolors8.default.dim(String(t2.id))}`
8992
+ label: `${truncate(t2.text, 50)} ${import_picocolors8.default.dim(t2.id)}`
10099
8993
  }))
10100
8994
  });
10101
8995
  return selected;
@@ -10120,13 +9014,12 @@ function extractTime(dueDate) {
10120
9014
  const time = parts[1];
10121
9015
  return time === "00:00" ? "" : time;
10122
9016
  }
10123
- function isRepeating2(t2) {
10124
- const repeat = t2.repeat;
10125
- return !!repeat?.type && repeat.type !== "none";
9017
+ function isRepeating(t2) {
9018
+ return !!t2.repeat?.type && t2.repeat.type !== "none";
10126
9019
  }
10127
9020
  function getCheckIndicator(t2) {
10128
9021
  if (t2.completed) return import_picocolors8.default.green(SYM.check);
10129
- if (isRepeating2(t2)) return import_picocolors8.default.blue(SYM.repeat);
9022
+ if (isRepeating(t2)) return import_picocolors8.default.blue(SYM.repeat);
10130
9023
  return import_picocolors8.default.dim(SYM.circle);
10131
9024
  }
10132
9025
  function sortTasksForDisplay(tasks) {
@@ -10134,16 +9027,16 @@ function sortTasksForDisplay(tasks) {
10134
9027
  return [...tasks].sort((a, b) => {
10135
9028
  const timeA = extractTime(a.dueDate);
10136
9029
  const timeB = extractTime(b.dueDate);
10137
- const repA = isRepeating2(a);
10138
- const repB = isRepeating2(b);
9030
+ const repA = isRepeating(a);
9031
+ const repB = isRepeating(b);
10139
9032
  if (timeA && !timeB) return -1;
10140
9033
  if (!timeA && timeB) return 1;
10141
9034
  if (timeA && timeB) return timeA.localeCompare(timeB);
10142
9035
  if (repA && !repB) return -1;
10143
9036
  if (!repA && repB) return 1;
10144
9037
  if (repA && repB) {
10145
- const ra = a.repeat?.type ?? "";
10146
- const rb = b.repeat?.type ?? "";
9038
+ const ra = a.repeat.type ?? "";
9039
+ const rb = b.repeat.type ?? "";
10147
9040
  return (repeatOrder[ra] ?? 99) - (repeatOrder[rb] ?? 99);
10148
9041
  }
10149
9042
  return 0;
@@ -10155,7 +9048,7 @@ function printTaskDetail(t2) {
10155
9048
  printRecord([
10156
9049
  ["ID", dim(t2.id)],
10157
9050
  ["Text", t2.text],
10158
- ["Due", formatDate2(t2.dueDate) || dim("none (backlog)")],
9051
+ ["Due", formatDate(t2.dueDate) || dim("none (backlog)")],
10159
9052
  ["Status", t2.completed ? import_picocolors8.default.green("completed") : import_picocolors8.default.yellow("pending")],
10160
9053
  ["Tags", formatTags(t2.tags) || dim("none")],
10161
9054
  ["Difficulty", formatDifficulty(t2.difficulty) || dim("not set")],
@@ -10164,18 +9057,18 @@ function printTaskDetail(t2) {
10164
9057
  ["Note", t2.note || dim("none")],
10165
9058
  ["Public", t2.isPublic ? import_picocolors8.default.green("yes") : import_picocolors8.default.yellow("no")],
10166
9059
  ["Completions", String(t2.completions ?? 0)],
10167
- ["Created", formatDate2(t2.createdAt)]
9060
+ ["Created", formatDate(t2.createdAt)]
10168
9061
  ]);
10169
9062
  console.log("");
10170
9063
  }
10171
9064
  function printTaskLine(t2) {
10172
9065
  const check = getCheckIndicator(t2);
10173
- const rawText = truncate(String(t2.text ?? ""), 50);
9066
+ const rawText = truncate(t2.text, 50);
10174
9067
  const text = t2.completed ? import_picocolors8.default.strikethrough(import_picocolors8.default.dim(rawText)) : rawText;
10175
9068
  const time = extractTime(t2.dueDate);
10176
9069
  const tags = formatTags(t2.tags);
10177
9070
  const difficulty = formatDifficulty(t2.difficulty);
10178
- const id = import_picocolors8.default.dim(String(t2.id ?? ""));
9071
+ const id = import_picocolors8.default.dim(t2.id);
10179
9072
  const parts = [check, text];
10180
9073
  if (time) parts.push(import_picocolors8.default.cyan(time));
10181
9074
  if (tags) parts.push(tags);
@@ -10204,8 +9097,7 @@ function registerTasksCommands(program3) {
10204
9097
  console.log(` ${import_picocolors8.default.bold("Backlog")} ${import_picocolors8.default.dim(`(${items.length})`)}`);
10205
9098
  } else {
10206
9099
  const viewDate = date ? /* @__PURE__ */ new Date(date + "T00:00:00") : /* @__PURE__ */ new Date();
10207
- const streakCount = getCompletedTodayCount();
10208
- console.log(formatWeekdayHeader(viewDate, streakCount));
9100
+ console.log(formatWeekdayHeader(viewDate, completed.length));
10209
9101
  }
10210
9102
  const tagLine = formatTagsSummary(items);
10211
9103
  if (tagLine) console.log(`
@@ -10412,11 +9304,10 @@ Examples:
10412
9304
  dataKey: "task",
10413
9305
  spinnerMessage: "Creating task...",
10414
9306
  onInteractive: (_task, payload) => {
10415
- const task = payload.task;
9307
+ const { task, karma } = payload;
10416
9308
  const check = import_picocolors8.default.green(SYM.check);
10417
9309
  console.log(`
10418
9310
  ${check} Created ${task.text}`);
10419
- const karma = payload.karma;
10420
9311
  if (karma) {
10421
9312
  console.log(` ${formatKarmaGain(karma)}${" ".repeat(20)}${import_picocolors8.default.dim(task.id)}`);
10422
9313
  }
@@ -10471,9 +9362,9 @@ Examples:
10471
9362
  fn: () => updateTask(uid, taskId, body),
10472
9363
  dataKey: "task",
10473
9364
  spinnerMessage: "Updating task...",
10474
- onInteractive: (task) => {
9365
+ onInteractive: (payload) => {
10475
9366
  console.log(`
10476
- ${import_picocolors8.default.green("Updated!")} ${task.text} ${import_picocolors8.default.dim(task.id)}
9367
+ ${import_picocolors8.default.green("Updated!")} ${payload.task.text} ${import_picocolors8.default.dim(payload.task.id)}
10477
9368
  `);
10478
9369
  }
10479
9370
  });
@@ -10503,7 +9394,7 @@ Examples:
10503
9394
  let taskText = taskId;
10504
9395
  try {
10505
9396
  const task = await getTask(uid, taskId);
10506
- taskText = String(task.text ?? taskId);
9397
+ taskText = task.text ?? taskId;
10507
9398
  } catch {
10508
9399
  }
10509
9400
  const confirmed = await promptConfirm({
@@ -10521,9 +9412,8 @@ Examples:
10521
9412
  spinnerMessage: "Deleting task...",
10522
9413
  onInteractive: (data) => {
10523
9414
  const cross = import_picocolors8.default.red(SYM.cross);
10524
- const text = data.taskText || taskId;
10525
9415
  console.log(`
10526
- ${cross} Deleted ${text}`);
9416
+ ${cross} Deleted ${data.taskText || taskId}`);
10527
9417
  if (data.archived) console.log(` ${import_picocolors8.default.dim("Archived")}`);
10528
9418
  console.log("");
10529
9419
  }
@@ -10555,13 +9445,10 @@ Examples:
10555
9445
  spinnerMessage: "Completing task...",
10556
9446
  onInteractive: (data) => {
10557
9447
  const check = import_picocolors8.default.green(SYM.check);
10558
- const text = data.taskText ?? taskId;
10559
9448
  console.log(`
10560
- ${check} Done! ${text}`);
10561
- const karma = data.karma;
10562
- const checksInRow = data.checksInRow;
10563
- if (karma) {
10564
- console.log(` ${formatKarmaGain(karma, checksInRow)}`);
9449
+ ${check} Done! ${data.taskText ?? taskId}`);
9450
+ if (data.karma) {
9451
+ console.log(` ${formatKarmaGain(data.karma, data.checksInRow)}`);
10565
9452
  }
10566
9453
  console.log("");
10567
9454
  }
@@ -10593,9 +9480,8 @@ Examples:
10593
9480
  spinnerMessage: "Uncompleting task...",
10594
9481
  onInteractive: (data) => {
10595
9482
  const arrow = SYM.undo;
10596
- const text = data.text ?? taskId;
10597
9483
  console.log(`
10598
- ${import_picocolors8.default.yellow(arrow)} Reverted ${text}`);
9484
+ ${import_picocolors8.default.yellow(arrow)} Reverted ${data.task.text ?? taskId}`);
10599
9485
  console.log(` ${import_picocolors8.default.dim("Karma adjustment applied")}`);
10600
9486
  console.log("");
10601
9487
  }
@@ -10606,13 +9492,10 @@ Examples:
10606
9492
  }
10607
9493
 
10608
9494
  // src/cli/commands/posts.ts
10609
- init_define_ADMIN_UIDS();
10610
9495
  var import_picocolors10 = __toESM(require_picocolors(), 1);
10611
9496
 
10612
9497
  // src/cli/lib/pagination.ts
10613
- init_define_ADMIN_UIDS();
10614
9498
  var import_picocolors9 = __toESM(require_picocolors(), 1);
10615
- init_errors();
10616
9499
  function printPaginationHint(opts) {
10617
9500
  if (!opts.nextCursor) return;
10618
9501
  const parts = [opts.command, `--cursor ${opts.nextCursor}`];
@@ -10622,223 +9505,56 @@ ${import_picocolors9.default.dim("Next page:")} ${import_picocolors9.default.dim
10622
9505
  }
10623
9506
 
10624
9507
  // src/cli/services/posts.ts
10625
- init_define_ADMIN_UIDS();
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
- }
10629
9508
  async function listPosts(opts) {
10630
- const lim = Math.min(opts.limit ?? 20, MAX_POSTS_PER_REQUEST);
10631
- const queryOpts = {
10632
- orderBy: [{ field: "createdAt", direction: "DESCENDING" }],
10633
- limit: lim + 1
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
- };
9509
+ return api.get("/api/posts", {
9510
+ cursor: opts.cursor,
9511
+ limit: opts.limit?.toString()
9512
+ });
10648
9513
  }
10649
9514
  async function getPost(id) {
10650
- validateDocId(id, "Post ID");
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;
9515
+ return api.get(`/api/posts/${encodeURIComponent(id)}`);
10660
9516
  }
10661
9517
  async function createPost(uid, body) {
10662
- const now2 = Date.now();
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 };
9518
+ return api.post("/api/posts", body);
10686
9519
  }
10687
9520
  async function updatePost(uid, id, body) {
10688
- validateDocId(id, "Post ID");
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 };
9521
+ return api.patch(`/api/posts/${encodeURIComponent(id)}`, body);
10703
9522
  }
10704
9523
  async function deletePost(uid, id) {
10705
- validateDocId(id, "Post ID");
10706
- const post = await getDoc(`posts/${id}`);
10707
- checkOwnership(post, uid, "delete");
10708
- await deleteDoc(`posts/${id}`);
9524
+ await api.del(`/api/posts/${encodeURIComponent(id)}`);
10709
9525
  }
10710
9526
 
10711
9527
  // src/cli/services/comments.ts
10712
- init_define_ADMIN_UIDS();
10713
9528
  async function listComments(postId, opts) {
10714
- validateDocId(postId, "Post ID");
10715
- const lim = Math.min(opts.limit ?? 20, MAX_COMMENTS_PER_REQUEST);
10716
- const queryOpts = {
10717
- orderBy: [{ field: "createdAt", direction: "ASCENDING" }],
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
- };
9529
+ return api.get(`/api/posts/${encodeURIComponent(postId)}/comments`, {
9530
+ cursor: opts.cursor,
9531
+ limit: opts.limit?.toString()
9532
+ });
10745
9533
  }
10746
9534
  async function createComment(uid, postId, text) {
10747
- validateDocId(postId, "Post ID");
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 };
9535
+ return api.post(`/api/posts/${encodeURIComponent(postId)}/comments`, { text });
10771
9536
  }
10772
9537
  async function deleteComment(uid, postId, commentId) {
10773
- validateDocId(postId, "Post ID");
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);
9538
+ await api.del(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}`);
10779
9539
  }
10780
9540
 
10781
9541
  // src/cli/services/replies.ts
10782
- init_define_ADMIN_UIDS();
10783
9542
  async function listReplies(postId, commentId, opts) {
10784
- validateDocId(postId, "Post ID");
10785
- validateDocId(commentId, "Comment ID");
10786
- const lim = Math.min(opts.limit ?? 20, MAX_REPLIES_PER_REQUEST);
10787
- const queryOpts = {
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
- };
9543
+ return api.get(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, {
9544
+ cursor: opts.cursor,
9545
+ limit: opts.limit?.toString()
9546
+ });
10804
9547
  }
10805
9548
  async function createReply(uid, postId, commentId, text) {
10806
- validateDocId(postId, "Post ID");
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 };
9549
+ return api.post(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, { text });
10831
9550
  }
10832
9551
  async function deleteReply(uid, postId, commentId, replyId) {
10833
- validateDocId(postId, "Post ID");
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);
9552
+ await api.del(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies/${encodeURIComponent(replyId)}`);
10840
9553
  }
10841
9554
 
9555
+ // src/cli/types/api.ts
9556
+ var POST_TAGS = ["general", "hack", "story", "meme", "other", "question", "hack-tip", "activity"];
9557
+
10842
9558
  // src/cli/commands/posts.ts
10843
9559
  init_prompts();
10844
9560
  init_tty();
@@ -10851,7 +9567,7 @@ async function pickComment(postId, commentId) {
10851
9567
  message: "Select comment",
10852
9568
  options: comments.map((c) => ({
10853
9569
  value: c.id,
10854
- label: `${truncate(String(c.text ?? ""), 60)} ${import_picocolors10.default.dim(String(c.authorName ?? c.userId ?? ""))}`
9570
+ label: `${truncate(c.text ?? "", 60)} ${import_picocolors10.default.dim(c.authorName ?? c.userId ?? "")}`
10855
9571
  }))
10856
9572
  });
10857
9573
  }
@@ -10864,16 +9580,16 @@ async function pickReply(postId, commentId, replyId) {
10864
9580
  message: "Select reply",
10865
9581
  options: replies.map((r) => ({
10866
9582
  value: r.id,
10867
- label: `${truncate(String(r.text ?? ""), 60)} ${import_picocolors10.default.dim(String(r.userId ?? ""))}`
9583
+ label: `${truncate(r.text ?? "", 60)} ${import_picocolors10.default.dim(r.userId ?? "")}`
10868
9584
  }))
10869
9585
  });
10870
9586
  }
10871
9587
  function printPostLine(p) {
10872
- const tag = import_picocolors10.default.cyan(String(p.tag ?? ""));
10873
- const title = truncate(String(p.title ?? ""), 55);
9588
+ const tag = import_picocolors10.default.cyan(p.tag ?? "");
9589
+ const title = truncate(p.title, 55);
10874
9590
  const comments = p.commentsCount ? import_picocolors10.default.dim(`${p.commentsCount} comments`) : "";
10875
9591
  const time = formatRelativeDate(p.createdAt);
10876
- const id = import_picocolors10.default.dim(String(p.id ?? ""));
9592
+ const id = import_picocolors10.default.dim(p.id);
10877
9593
  const parts = [tag, import_picocolors10.default.bold(title)];
10878
9594
  if (comments) parts.push(comments);
10879
9595
  parts.push(import_picocolors10.default.dim(time));
@@ -10890,7 +9606,7 @@ function printPostDetail(p) {
10890
9606
  console.log(` ${import_picocolors10.default.dim(SYM.dash.repeat(40))}`);
10891
9607
  printRecord([
10892
9608
  ["ID", import_picocolors10.default.dim(p.id)],
10893
- ["Tag", import_picocolors10.default.cyan(String(p.tag ?? ""))],
9609
+ ["Tag", import_picocolors10.default.cyan(p.tag ?? "")],
10894
9610
  ["Author", p.authorName ?? p.authorId],
10895
9611
  ["Comments", p.commentsCount != null ? String(p.commentsCount) : null],
10896
9612
  ["Likes", p.likesCount != null ? String(p.likesCount) : null],
@@ -10899,18 +9615,18 @@ function printPostDetail(p) {
10899
9615
  console.log("");
10900
9616
  }
10901
9617
  function printCommentLine(c) {
10902
- const author = import_picocolors10.default.bold(String(c.authorName ?? c.userId ?? ""));
9618
+ const author = import_picocolors10.default.bold(c.authorName ?? c.userId ?? "");
10903
9619
  const time = import_picocolors10.default.dim(formatRelativeDate(c.createdAt));
10904
9620
  const replies = c.repliesCount ? import_picocolors10.default.dim(`\xB7 ${c.repliesCount} replies`) : "";
10905
- const text = String(c.text ?? "");
9621
+ const text = c.text ?? "";
10906
9622
  console.log(` ${author} ${time}${replies}`);
10907
9623
  console.log(` ${text}`);
10908
9624
  console.log("");
10909
9625
  }
10910
9626
  function printReplyLine(r) {
10911
- const text = truncate(String(r.text ?? ""), 60);
9627
+ const text = truncate(r.text ?? "", 60);
10912
9628
  const time = formatRelativeDate(r.createdAt);
10913
- const id = import_picocolors10.default.dim(String(r.id ?? ""));
9629
+ const id = import_picocolors10.default.dim(r.id);
10914
9630
  console.log(` ${text} ${import_picocolors10.default.dim(time)} ${id}`);
10915
9631
  }
10916
9632
  function registerPostsCommands(program3) {
@@ -11012,148 +9728,134 @@ Examples:
11012
9728
  }
11013
9729
  });
11014
9730
  });
11015
- if (isAdmin()) {
11016
- 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() {
11017
- const opts = this.optsWithGlobals();
11018
- const uid = requireAdmin();
11019
- const title = await promptForMissing({ value: opts.title, message: "Title", placeholder: "Post title" });
11020
- const postBody = await promptForMissing({ value: opts.body, message: "Body", placeholder: "Post body" });
11021
- let tag = opts.tag;
11022
- if (!tag) {
11023
- tag = await promptSelect({
11024
- message: "Tag",
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
- }
9731
+ 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() {
9732
+ const opts = this.optsWithGlobals();
9733
+ const uid = requireUid();
9734
+ const title = await promptForMissing({ value: opts.title, message: "Title", placeholder: "Post title" });
9735
+ const postBody = await promptForMissing({ value: opts.body, message: "Body", placeholder: "Post body" });
9736
+ let tag = opts.tag;
9737
+ if (!tag) {
9738
+ tag = await promptSelect({
9739
+ message: "Tag",
9740
+ options: POST_TAGS.map((t2) => ({ value: t2, label: t2 }))
11039
9741
  });
9742
+ }
9743
+ const body = { title, body: postBody, tag };
9744
+ await runCreate({
9745
+ global: opts,
9746
+ fn: () => createPost(uid, body),
9747
+ dataKey: "post",
9748
+ spinnerMessage: "Creating post...",
9749
+ onInteractive: (post, payload) => {
9750
+ console.log(`
9751
+ ${import_picocolors10.default.green("Posted!")} ${payload.post.title} ${import_picocolors10.default.dim(payload.post.id)}
9752
+ `);
9753
+ }
11040
9754
  });
11041
- posts.command("update [id]").description("Update a post").option("--title <title>").option("--body <body>").option("--tag <tag>").action(async function(id) {
11042
- const opts = this.optsWithGlobals();
11043
- const uid = requireAdmin();
11044
- const postId = await promptForMissing({ value: id, message: "Post ID" });
11045
- const body = {};
11046
- const hasAnyFlag = opts.title || opts.body || opts.tag;
11047
- if (!hasAnyFlag && isInteractive() && !opts.json) {
11048
- const title = await promptText({ message: "Title (enter to skip)", required: false });
11049
- if (title) body.title = title;
11050
- const postBody = await promptText({ message: "Body (enter to skip)", required: false });
11051
- if (postBody) body.body = postBody;
11052
- const changeTag = await promptText({ message: "Tag (enter to skip)", placeholder: POST_TAGS.join("|"), required: false });
11053
- if (changeTag) body.tag = changeTag;
11054
- } else {
11055
- if (opts.title) body.title = opts.title;
11056
- if (opts.body) body.body = opts.body;
11057
- if (opts.tag) body.tag = opts.tag;
11058
- }
11059
- await runWrite({
11060
- global: opts,
11061
- fn: () => updatePost(uid, postId, body),
11062
- dataKey: "post",
11063
- spinnerMessage: "Updating post...",
11064
- onInteractive: (post) => {
11065
- console.log(`
11066
- ${import_picocolors10.default.green("Updated!")} ${post.title} ${import_picocolors10.default.dim(post.id)}
9755
+ });
9756
+ posts.command("update [id]").description("Update a post").option("--title <title>").option("--body <body>").option("--tag <tag>").action(async function(id) {
9757
+ const opts = this.optsWithGlobals();
9758
+ const uid = requireUid();
9759
+ const postId = await promptForMissing({ value: id, message: "Post ID" });
9760
+ const body = {};
9761
+ const hasAnyFlag = opts.title || opts.body || opts.tag;
9762
+ if (!hasAnyFlag && isInteractive() && !opts.json) {
9763
+ const title = await promptText({ message: "Title (enter to skip)", required: false });
9764
+ if (title) body.title = title;
9765
+ const postBody = await promptText({ message: "Body (enter to skip)", required: false });
9766
+ if (postBody) body.body = postBody;
9767
+ const changeTag = await promptText({ message: "Tag (enter to skip)", placeholder: POST_TAGS.join("|"), required: false });
9768
+ if (changeTag) body.tag = changeTag;
9769
+ } else {
9770
+ if (opts.title) body.title = opts.title;
9771
+ if (opts.body) body.body = opts.body;
9772
+ if (opts.tag) body.tag = opts.tag;
9773
+ }
9774
+ await runWrite({
9775
+ global: opts,
9776
+ fn: () => updatePost(uid, postId, body),
9777
+ dataKey: "post",
9778
+ spinnerMessage: "Updating post...",
9779
+ onInteractive: (payload) => {
9780
+ console.log(`
9781
+ ${import_picocolors10.default.green("Updated!")} ${payload.post.title} ${import_picocolors10.default.dim(payload.post.id)}
11067
9782
  `);
11068
- }
11069
- });
9783
+ }
11070
9784
  });
11071
- posts.command("delete [id]").description("Delete a post").action(async function(id) {
11072
- const uid = requireAdmin();
11073
- const postId = await promptForMissing({ value: id, message: "Post ID" });
11074
- await runDelete({
11075
- global: this.optsWithGlobals(),
11076
- fn: () => deletePost(uid, postId),
11077
- successMessage: ` ${import_picocolors10.default.green("Deleted!")} Post ${import_picocolors10.default.dim(postId)}`,
11078
- spinnerMessage: "Deleting post..."
11079
- });
9785
+ });
9786
+ posts.command("delete [id]").description("Delete a post").action(async function(id) {
9787
+ const uid = requireUid();
9788
+ const postId = await promptForMissing({ value: id, message: "Post ID" });
9789
+ await runDelete({
9790
+ global: this.optsWithGlobals(),
9791
+ fn: () => deletePost(uid, postId),
9792
+ successMessage: ` ${import_picocolors10.default.green("Deleted!")} Post ${import_picocolors10.default.dim(postId)}`,
9793
+ spinnerMessage: "Deleting post..."
11080
9794
  });
11081
- posts.command("comment [postId]").description("Add a comment to a post").option("--text <text>").action(async function(postId) {
11082
- const opts = this.optsWithGlobals();
11083
- const uid = requireAdmin();
11084
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11085
- const text = await promptForMissing({ value: opts.text, message: "Comment text", placeholder: "Your comment" });
11086
- await runCreate({
11087
- global: opts,
11088
- fn: () => createComment(uid, resolvedPostId, text),
11089
- dataKey: "comment",
11090
- spinnerMessage: "Adding comment...",
11091
- onInteractive: (comment) => {
11092
- console.log(`
11093
- ${import_picocolors10.default.green("Commented!")} ${truncate(String(comment.text ?? ""), 50)} ${import_picocolors10.default.dim(comment.id)}
9795
+ });
9796
+ posts.command("comment [postId]").description("Add a comment to a post").option("--text <text>").action(async function(postId) {
9797
+ const opts = this.optsWithGlobals();
9798
+ const uid = requireUid();
9799
+ const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
9800
+ const text = await promptForMissing({ value: opts.text, message: "Comment text", placeholder: "Your comment" });
9801
+ await runCreate({
9802
+ global: opts,
9803
+ fn: () => createComment(uid, resolvedPostId, text),
9804
+ dataKey: "comment",
9805
+ spinnerMessage: "Adding comment...",
9806
+ onInteractive: (comment, payload) => {
9807
+ console.log(`
9808
+ ${import_picocolors10.default.green("Commented!")} ${truncate(payload.comment.text ?? "", 50)} ${import_picocolors10.default.dim(payload.comment.id)}
11094
9809
  `);
11095
- }
11096
- });
9810
+ }
11097
9811
  });
11098
- posts.command("comment-delete [postId] [commentId]").description("Delete a comment").action(async function(postId, commentId) {
11099
- const uid = requireAdmin();
11100
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11101
- const resolvedCommentId = await pickComment(resolvedPostId, commentId);
11102
- await runDelete({
11103
- global: this.optsWithGlobals(),
11104
- fn: () => deleteComment(uid, resolvedPostId, resolvedCommentId),
11105
- successMessage: ` ${import_picocolors10.default.green("Deleted!")} Comment ${import_picocolors10.default.dim(resolvedCommentId)}`,
11106
- spinnerMessage: "Deleting comment..."
11107
- });
9812
+ });
9813
+ posts.command("comment-delete [postId] [commentId]").description("Delete a comment").action(async function(postId, commentId) {
9814
+ const uid = requireUid();
9815
+ const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
9816
+ const resolvedCommentId = await pickComment(resolvedPostId, commentId);
9817
+ await runDelete({
9818
+ global: this.optsWithGlobals(),
9819
+ fn: () => deleteComment(uid, resolvedPostId, resolvedCommentId),
9820
+ successMessage: ` ${import_picocolors10.default.green("Deleted!")} Comment ${import_picocolors10.default.dim(resolvedCommentId)}`,
9821
+ spinnerMessage: "Deleting comment..."
11108
9822
  });
11109
- posts.command("reply [postId] [commentId]").description("Add a reply to a comment").option("--text <text>").action(async function(postId, commentId) {
11110
- const opts = this.optsWithGlobals();
11111
- const uid = requireAdmin();
11112
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11113
- const resolvedCommentId = await pickComment(resolvedPostId, commentId);
11114
- const text = await promptForMissing({ value: opts.text, message: "Reply text", placeholder: "Your reply" });
11115
- await runCreate({
11116
- global: opts,
11117
- fn: () => createReply(uid, resolvedPostId, resolvedCommentId, text),
11118
- dataKey: "reply",
11119
- spinnerMessage: "Adding reply...",
11120
- onInteractive: (reply) => {
11121
- console.log(`
11122
- ${import_picocolors10.default.green("Replied!")} ${truncate(String(reply.text ?? ""), 50)} ${import_picocolors10.default.dim(reply.id)}
9823
+ });
9824
+ posts.command("reply [postId] [commentId]").description("Add a reply to a comment").option("--text <text>").action(async function(postId, commentId) {
9825
+ const opts = this.optsWithGlobals();
9826
+ const uid = requireUid();
9827
+ const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
9828
+ const resolvedCommentId = await pickComment(resolvedPostId, commentId);
9829
+ const text = await promptForMissing({ value: opts.text, message: "Reply text", placeholder: "Your reply" });
9830
+ await runCreate({
9831
+ global: opts,
9832
+ fn: () => createReply(uid, resolvedPostId, resolvedCommentId, text),
9833
+ dataKey: "reply",
9834
+ spinnerMessage: "Adding reply...",
9835
+ onInteractive: (reply, payload) => {
9836
+ console.log(`
9837
+ ${import_picocolors10.default.green("Replied!")} ${truncate(payload.reply.text ?? "", 50)} ${import_picocolors10.default.dim(payload.reply.id)}
11123
9838
  `);
11124
- }
11125
- });
9839
+ }
11126
9840
  });
11127
- posts.command("reply-delete [postId] [commentId] [replyId]").description("Delete a reply").action(async function(postId, commentId, replyId) {
11128
- const uid = requireAdmin();
11129
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11130
- const resolvedCommentId = await pickComment(resolvedPostId, commentId);
11131
- const resolvedReplyId = await pickReply(resolvedPostId, resolvedCommentId, replyId);
11132
- await runDelete({
11133
- global: this.optsWithGlobals(),
11134
- fn: () => deleteReply(uid, resolvedPostId, resolvedCommentId, resolvedReplyId),
11135
- successMessage: ` ${import_picocolors10.default.green("Deleted!")} Reply ${import_picocolors10.default.dim(resolvedReplyId)}`,
11136
- spinnerMessage: "Deleting reply..."
11137
- });
9841
+ });
9842
+ posts.command("reply-delete [postId] [commentId] [replyId]").description("Delete a reply").action(async function(postId, commentId, replyId) {
9843
+ const uid = requireUid();
9844
+ const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
9845
+ const resolvedCommentId = await pickComment(resolvedPostId, commentId);
9846
+ const resolvedReplyId = await pickReply(resolvedPostId, resolvedCommentId, replyId);
9847
+ await runDelete({
9848
+ global: this.optsWithGlobals(),
9849
+ fn: () => deleteReply(uid, resolvedPostId, resolvedCommentId, resolvedReplyId),
9850
+ successMessage: ` ${import_picocolors10.default.green("Deleted!")} Reply ${import_picocolors10.default.dim(resolvedReplyId)}`,
9851
+ spinnerMessage: "Deleting reply..."
11138
9852
  });
11139
- }
9853
+ });
11140
9854
  }
11141
9855
 
11142
- // src/cli/commands/profile.ts
11143
- init_define_ADMIN_UIDS();
11144
-
11145
9856
  // src/cli/services/profile.ts
11146
- init_define_ADMIN_UIDS();
11147
9857
  async function getProfile() {
11148
- const creds = loadCredentials();
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
- };
9858
+ return api.get("/api/profile");
11157
9859
  }
11158
9860
 
11159
9861
  // src/cli/commands/profile.ts
@@ -11177,11 +9879,9 @@ function registerProfileCommands(program3) {
11177
9879
  }
11178
9880
 
11179
9881
  // src/cli/commands/doctor.ts
11180
- init_define_ADMIN_UIDS();
11181
9882
  var import_picocolors11 = __toESM(require_picocolors(), 1);
11182
- init_config();
11183
- init_config();
11184
9883
  init_tty();
9884
+ var API_BASE5 = process.env.NUMO_API_URL ?? "http://localhost:3000";
11185
9885
  async function runChecks() {
11186
9886
  const checks = [];
11187
9887
  const nodeVersion = process.version;
@@ -11191,6 +9891,11 @@ async function runChecks() {
11191
9891
  status: major >= 18 ? "ok" : "fail",
11192
9892
  message: major >= 18 ? `Node ${nodeVersion}` : `Node ${nodeVersion} \u2014 requires >= 18`
11193
9893
  });
9894
+ checks.push({
9895
+ name: "api_url",
9896
+ status: process.env.NUMO_API_URL ? "ok" : "warn",
9897
+ message: process.env.NUMO_API_URL ? `API URL: ${API_BASE5}` : `NUMO_API_URL not set (using default: ${API_BASE5})`
9898
+ });
11194
9899
  const creds = loadCredentials();
11195
9900
  checks.push({
11196
9901
  name: "credentials",
@@ -11207,18 +9912,11 @@ async function runChecks() {
11207
9912
  } else {
11208
9913
  checks.push({ name: "token", status: "fail", message: "Skipped (no credentials)" });
11209
9914
  }
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
9915
  try {
11217
- const baseUrl = getFirestoreBaseUrl();
11218
- const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(5e3) });
11219
- checks.push({ name: "firebase_reachable", status: "ok", message: `Firebase reachable (HTTP ${resp.status})` });
9916
+ const resp = await fetch(`${API_BASE5}/api/health`, { signal: AbortSignal.timeout(5e3) });
9917
+ checks.push({ name: "api_reachable", status: "ok", message: `API server reachable (HTTP ${resp.status})` });
11220
9918
  } catch (err) {
11221
- checks.push({ name: "firebase_reachable", status: "fail", message: `Firebase unreachable: ${err.message}` });
9919
+ checks.push({ name: "api_reachable", status: "fail", message: `API server unreachable: ${err.message}` });
11222
9920
  }
11223
9921
  return checks;
11224
9922
  }
@@ -11249,19 +9947,18 @@ function registerDoctorCommand(program3) {
11249
9947
  }
11250
9948
 
11251
9949
  // src/cli/lib/update-check.ts
11252
- init_define_ADMIN_UIDS();
11253
- var fs5 = __toESM(require("fs"), 1);
11254
- var path3 = __toESM(require("path"), 1);
9950
+ var fs4 = __toESM(require("fs"), 1);
9951
+ var path2 = __toESM(require("path"), 1);
11255
9952
  var import_picocolors12 = __toESM(require_picocolors(), 1);
11256
9953
  init_tty();
11257
9954
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
11258
9955
  var PACKAGE_NAME = "numo-cli";
11259
9956
  function getStatePath() {
11260
- return path3.join(getConfigDir(), "update-check.json");
9957
+ return path2.join(getConfigDir(), "update-check.json");
11261
9958
  }
11262
9959
  function loadState() {
11263
9960
  try {
11264
- return JSON.parse(fs5.readFileSync(getStatePath(), "utf8"));
9961
+ return JSON.parse(fs4.readFileSync(getStatePath(), "utf8"));
11265
9962
  } catch {
11266
9963
  return { lastCheck: 0 };
11267
9964
  }
@@ -11269,7 +9966,7 @@ function loadState() {
11269
9966
  function saveState(state) {
11270
9967
  try {
11271
9968
  ensureConfigDir();
11272
- fs5.writeFileSync(getStatePath(), JSON.stringify(state), { mode: 384 });
9969
+ fs4.writeFileSync(getStatePath(), JSON.stringify(state), { mode: 384 });
11273
9970
  } catch {
11274
9971
  }
11275
9972
  }
@@ -11318,7 +10015,7 @@ function fetchLatestVersion(state) {
11318
10015
  // src/cli/cli.ts
11319
10016
  init_tty();
11320
10017
  init_errors();
11321
- var CLI_VERSION = true ? "1.2.0" : "0.0.0-dev";
10018
+ var CLI_VERSION = true ? "1.3.0" : "0.0.0-dev";
11322
10019
  var program2 = new Command();
11323
10020
  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
10021
  const opts = thisCommand.optsWithGlobals();
@@ -11340,7 +10037,7 @@ program2.command("login").description("Login with your Numo account").option("--
11340
10037
  Examples:
11341
10038
  $ numo login # Interactive (email/password)
11342
10039
  $ 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 characters)").action(async (opts) => {
10040
+ 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
10041
  await register(opts);
11345
10042
  }).addHelpText("after", `
11346
10043
  Examples:
@@ -11348,7 +10045,6 @@ Examples:
11348
10045
  $ numo register --email user@example.com --password s3cret # Non-interactive`);
11349
10046
  program2.command("logout").description("Clear stored credentials").action(() => {
11350
10047
  clearCredentials();
11351
- clearStreaks();
11352
10048
  console.log(import_picocolors13.default.green("Logged out."));
11353
10049
  });
11354
10050
  program2.command("whoami").description("Show current auth status (no API call)").action(function() {
@@ -11401,17 +10097,16 @@ program2.command("add [text...]").description("Quick-add a task (today, public,
11401
10097
  dataKey: "task",
11402
10098
  spinnerMessage: "Creating task...",
11403
10099
  onInteractive: (_task, payload) => {
11404
- const task = payload.task;
10100
+ const { task, karma } = payload;
11405
10101
  const check = import_picocolors13.default.green(SYM.check);
11406
10102
  console.log(`
11407
10103
  ${check} Created ${task.text} ${import_picocolors13.default.dim(task.id)}`);
11408
- const karma = payload.karma;
11409
10104
  if (karma) console.log(` ${formatKarmaGain(karma)}`);
11410
10105
  console.log("");
11411
10106
  }
11412
10107
  });
11413
10108
  });
11414
- if (isAdmin()) registerPostsCommands(program2);
10109
+ registerPostsCommands(program2);
11415
10110
  registerProfileCommands(program2);
11416
10111
  registerDoctorCommand(program2);
11417
10112
  program2.command("commands").description("List all available commands").action(function() {