numo-cli 1.1.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 +392 -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,640 +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
- }
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
5927
  }
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
- note: body.note ?? "",
6807
- priority: typeof body.priority === "number" ? body.priority : 0,
6808
- difficulty: body.difficulty ?? null,
6809
- duration: typeof body.duration === "number" ? body.duration : 10,
6810
- backlog: dueDate == null,
6811
- parentTaskId: null,
6812
- completions: 0,
6813
- repeat: body.repeat ?? { type: "none", every: null, end: null, endDate: null, endAfter: null, monthDays: null, weekDays: null },
6814
- subtasks: Array.isArray(body.subtasks) ? body.subtasks.map((s) => ({ id: crypto3.randomUUID(), text: s.text, completed: false })) : [],
6815
- withTime: dueDate != null && !/\b00:00$/.test(dueDate),
6816
- listPosition: null
6817
- };
6818
- taskData.remindDate = getTaskRemindDate(taskData);
6819
- const taskId = generateTaskId(taskData);
6820
- const doc = await setDoc(taskPath(uid, taskId), taskData);
6821
- const createResults = await Promise.allSettled([
6822
- giveKarma(uid, "addTask", taskId, KARMA_POINTS.addTask, String(taskData.text)),
6823
- addToOrdering(uid, taskId, "end"),
6824
- incrementField(activityTotalsPath(uid), "tasks.active", 1)
6825
- ]);
6826
- logSideEffectResults("createTask", createResults);
6827
- return { task: doc, karma: KARMA_POINTS.addTask };
5985
+ return api.post("/api/tasks", body);
6828
5986
  }
6829
5987
  async function updateTask(uid, id, body) {
6830
- validateDocId(id, "Task ID");
6831
- const allowed = ["text", "isPublic", "dueDate", "tags", "note", "priority", "difficulty", "duration", "repeat", "subtasks"];
6832
- const update = {};
6833
- const fieldMask = [];
6834
- for (const key of allowed) {
6835
- if (key in body) {
6836
- update[key] = body[key];
6837
- fieldMask.push(key);
6838
- }
6839
- }
6840
- if ("dueDate" in update) {
6841
- const newDueDate = update.dueDate;
6842
- update.backlog = newDueDate == null;
6843
- if (!fieldMask.includes("backlog")) fieldMask.push("backlog");
6844
- }
6845
- await updateDoc(taskPath(uid, id), update, fieldMask);
6846
- if ("dueDate" in body) {
6847
- await addToOrdering(uid, id, "end");
6848
- }
6849
- const updated = await getDoc(taskPath(uid, id));
6850
- const recalculatedRemindDate = getTaskRemindDate(updated);
6851
- if (updated.remindDate !== recalculatedRemindDate) {
6852
- await updateDoc(taskPath(uid, id), { remindDate: recalculatedRemindDate }, ["remindDate"]);
6853
- const final = await getDoc(taskPath(uid, id));
6854
- return { task: final };
6855
- }
6856
- return { task: updated };
5988
+ return api.patch(`/api/tasks/${encodeURIComponent(id)}`, body);
6857
5989
  }
6858
5990
  async function deleteTask(uid, id) {
6859
- validateDocId(id, "Task ID");
6860
- let taskData = {};
6861
- try {
6862
- taskData = await getDoc(taskPath(uid, id));
6863
- } catch {
6864
- }
6865
- const hasData = Object.keys(taskData).length > 0;
6866
- const writes = [
6867
- { type: "delete", path: taskPath(uid, id) }
6868
- ];
6869
- if (hasData) {
6870
- writes.push({
6871
- type: "update",
6872
- path: archivePath(uid, id),
6873
- data: { ...taskData, archived: true, archivedAt: Date.now() }
6874
- });
6875
- writes.push({
6876
- type: "transform",
6877
- path: archiveCounterPath(uid),
6878
- transforms: [{ field: "archivedTasksCount", increment: 1 }]
6879
- });
6880
- }
6881
- await commit(writes);
6882
- try {
6883
- const historyDocs = await runQuery(`users/${uid}`, "tasksHistory", {
6884
- where: [{ field: "parentTaskId", op: "EQUAL", value: id }],
6885
- limit: MAX_TASKS_PER_REQUEST
6886
- });
6887
- const historyResults = await Promise.allSettled(historyDocs.map((h2) => deleteDoc(taskHistoryPath(uid, h2.id))));
6888
- logSideEffectResults("deleteTask.history", historyResults);
6889
- } catch (err) {
6890
- process.stderr.write(`[warn] deleteTask.history: ${err instanceof Error ? err.message : String(err)}
6891
- `);
6892
- }
6893
- const deleteResults = await Promise.allSettled([
6894
- removeFromOrdering(uid, id),
6895
- deleteRoutineStreak(uid, id),
6896
- incrementField(activityTotalsPath(uid), "tasks.active", -1)
6897
- ]);
6898
- logSideEffectResults("deleteTask", deleteResults);
6899
- removeDailyStreak(id);
6900
- return { deleted: true, taskText: String(taskData.text ?? ""), archived: hasData };
5991
+ return api.del(`/api/tasks/${encodeURIComponent(id)}`);
6901
5992
  }
6902
5993
  async function completeTask(uid, id, date) {
6903
- validateDocId(id, "Task ID");
6904
- const task = await getDoc(taskPath(uid, id));
6905
- const completedAt = date ? new Date(date).getTime() : Date.now();
6906
- const repeating = isRepeating(task);
6907
- const historyId = repeating ? `${id}-${completedAt}` : id;
6908
- const historyData = {
6909
- ...task,
6910
- id: historyId,
6911
- completedAt,
6912
- completed: true,
6913
- parentTaskId: id,
6914
- isHistoryTask: true,
6915
- completions: (task.completions ?? 0) + 1,
6916
- remindDate: null
6917
- };
6918
- delete historyData.id;
6919
- const writes = [];
6920
- if (repeating) {
6921
- const nextDueDate = getNextDueDate(task);
6922
- const resetSubtasks = (task.subtasks ?? []).map((s) => ({ ...s, completed: false }));
6923
- const updatedData = {
6924
- dueDate: nextDueDate,
6925
- completions: (task.completions ?? 0) + 1,
6926
- completedAt,
6927
- subtasks: resetSubtasks
6928
- };
6929
- writes.push({
6930
- type: "update",
6931
- path: taskPath(uid, id),
6932
- data: updatedData,
6933
- fieldMask: Object.keys(updatedData)
6934
- });
6935
- } else {
6936
- writes.push({ type: "delete", path: taskPath(uid, id) });
6937
- }
6938
- writes.push({
6939
- type: "update",
6940
- path: taskHistoryPath(uid, historyId),
6941
- data: historyData
6942
- });
6943
- await commit(writes);
6944
- if (repeating) {
6945
- try {
6946
- const afterUpdate = await getDoc(taskPath(uid, id));
6947
- await updateDoc(taskPath(uid, id), { remindDate: getTaskRemindDate(afterUpdate) }, ["remindDate"]);
6948
- } catch {
6949
- }
6950
- }
6951
- const completeDateStr = date ?? formatDate(/* @__PURE__ */ new Date());
6952
- const entityId = repeating ? `${id}_${completeDateStr.slice(0, 10)}` : id;
6953
- const checksInRow = recordDailyStreak(id, completeDateStr);
6954
- const karmaPoints = computeCompleteKarma(task, checksInRow);
6955
- const sideEffects = [
6956
- giveKarma(uid, "completeTask", entityId, karmaPoints, task.text),
6957
- addToProgress(uid, id, completeDateStr),
6958
- incrementField(activityTotalsPath(uid), "tasks.completed", 1)
6959
- ];
6960
- if (repeating) {
6961
- sideEffects.push(recordRoutineStreak(uid, id));
6962
- } else {
6963
- sideEffects.push(removeFromOrdering(uid, id));
6964
- sideEffects.push(incrementField(activityTotalsPath(uid), "tasks.active", -1));
6965
- }
6966
- const completeResults = await Promise.allSettled(sideEffects);
6967
- logSideEffectResults("completeTask", completeResults);
6968
- return { completed: true, taskHistory: historyData, karma: karmaPoints, checksInRow, taskText: task.text };
5994
+ return api.post(`/api/tasks/${encodeURIComponent(id)}/complete`, date ? { date } : void 0);
6969
5995
  }
6970
5996
  async function uncompleteTask(uid, taskHistoryId) {
6971
- validateDocId(taskHistoryId, "Task history ID");
6972
- const history = await getDoc(taskHistoryPath(uid, taskHistoryId));
6973
- const parentTaskId = history.parentTaskId;
6974
- if (!parentTaskId) throw new Error("Not a history record");
6975
- const repeating = isRepeating(history);
6976
- const restoredTask = {
6977
- ...history,
6978
- completed: false,
6979
- completedAt: null,
6980
- completions: Math.max(0, (history.completions ?? 0) - 1)
6981
- };
6982
- delete restoredTask.isHistoryTask;
6983
- delete restoredTask.id;
6984
- restoredTask.parentTaskId = null;
6985
- if (repeating) {
6986
- restoredTask.dueDate = history.dueDate;
6987
- }
6988
- const writes = [
6989
- { type: "update", path: taskPath(uid, parentTaskId), data: restoredTask },
6990
- { type: "delete", path: taskHistoryPath(uid, taskHistoryId) }
6991
- ];
6992
- await commit(writes);
6993
- try {
6994
- const updated = await getDoc(taskPath(uid, parentTaskId));
6995
- await updateDoc(taskPath(uid, parentTaskId), { remindDate: getTaskRemindDate(updated) }, ["remindDate"]);
6996
- } catch {
6997
- }
6998
- const completedAtDate = history.completedAt ? formatDate(new Date(history.completedAt)) : history.dueDate ?? formatDate(/* @__PURE__ */ new Date());
6999
- const entityId = repeating ? `${parentTaskId}_${completedAtDate.slice(0, 10)}` : parentTaskId;
7000
- const sideEffects = [
7001
- removeKarma(uid, "completeTask", entityId),
7002
- removeFromProgress(uid, parentTaskId, completedAtDate),
7003
- incrementField(activityTotalsPath(uid), "tasks.completed", -1)
7004
- ];
7005
- revertDailyStreak(parentTaskId);
7006
- if (repeating) {
7007
- sideEffects.push(revertRoutineStreak(uid, parentTaskId));
7008
- } else {
7009
- sideEffects.push(addToOrdering(uid, parentTaskId, "end"));
7010
- sideEffects.push(incrementField(activityTotalsPath(uid), "tasks.active", 1));
7011
- }
7012
- const uncompleteResults = await Promise.allSettled(sideEffects);
7013
- logSideEffectResults("uncompleteTask", uncompleteResults);
7014
- const final = await getDoc(taskPath(uid, parentTaskId));
7015
- return { uncompleted: true, task: final, karmaReverted: true };
5997
+ return api.post(`/api/tasks/${encodeURIComponent(taskHistoryId)}/uncomplete`);
7016
5998
  }
7017
5999
 
7018
6000
  // src/cli/lib/format.ts
7019
- init_define_ADMIN_UIDS();
7020
6001
  var import_picocolors7 = __toESM(require_picocolors(), 1);
7021
- function formatDate2(ts) {
6002
+ function formatDate(ts) {
7022
6003
  if (ts == null) return "";
7023
6004
  const d = typeof ts === "number" ? new Date(ts) : new Date(ts);
7024
6005
  if (isNaN(d.getTime())) return String(ts);
@@ -7041,7 +6022,7 @@ function formatRelativeDate(ts) {
7041
6022
  if (hours < 24) return `${hours}h ago`;
7042
6023
  const days = Math.floor(hours / 24);
7043
6024
  if (days < 30) return `${days}d ago`;
7044
- return formatDate2(ts);
6025
+ return formatDate(ts);
7045
6026
  }
7046
6027
  function formatTags(tags) {
7047
6028
  if (!Array.isArray(tags) || tags.length === 0) return "";
@@ -7119,26 +6100,7 @@ init_prompts();
7119
6100
  init_tty();
7120
6101
  init_errors();
7121
6102
 
7122
- // src/cli/lib/parse-date.ts
7123
- init_define_ADMIN_UIDS();
7124
-
7125
- // node_modules/chrono-node/dist/esm/index.js
7126
- init_define_ADMIN_UIDS();
7127
-
7128
- // node_modules/chrono-node/dist/esm/locales/en/index.js
7129
- init_define_ADMIN_UIDS();
7130
-
7131
- // node_modules/chrono-node/dist/esm/chrono.js
7132
- init_define_ADMIN_UIDS();
7133
-
7134
- // node_modules/chrono-node/dist/esm/results.js
7135
- init_define_ADMIN_UIDS();
7136
-
7137
- // node_modules/chrono-node/dist/esm/utils/dates.js
7138
- init_define_ADMIN_UIDS();
7139
-
7140
6103
  // node_modules/chrono-node/dist/esm/types.js
7141
- init_define_ADMIN_UIDS();
7142
6104
  var Meridiem;
7143
6105
  (function(Meridiem2) {
7144
6106
  Meridiem2[Meridiem2["AM"] = 0] = "AM";
@@ -7197,7 +6159,6 @@ function implySimilarTime(component, target) {
7197
6159
  }
7198
6160
 
7199
6161
  // node_modules/chrono-node/dist/esm/timezone.js
7200
- init_define_ADMIN_UIDS();
7201
6162
  var TIMEZONE_ABBR_MAP = {
7202
6163
  ACDT: 630,
7203
6164
  ACST: 570,
@@ -7467,7 +6428,6 @@ function toTimezoneOffset(timezoneInput, date, timezoneOverrides = {}) {
7467
6428
  }
7468
6429
 
7469
6430
  // node_modules/chrono-node/dist/esm/calculation/duration.js
7470
- init_define_ADMIN_UIDS();
7471
6431
  var EmptyDuration = {
7472
6432
  day: 0,
7473
6433
  second: 0,
@@ -7868,17 +6828,7 @@ var ParsingResult = class _ParsingResult {
7868
6828
  }
7869
6829
  };
7870
6830
 
7871
- // node_modules/chrono-node/dist/esm/locales/en/configuration.js
7872
- init_define_ADMIN_UIDS();
7873
-
7874
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitWithinFormatParser.js
7875
- init_define_ADMIN_UIDS();
7876
-
7877
- // node_modules/chrono-node/dist/esm/locales/en/constants.js
7878
- init_define_ADMIN_UIDS();
7879
-
7880
6831
  // node_modules/chrono-node/dist/esm/utils/pattern.js
7881
- init_define_ADMIN_UIDS();
7882
6832
  function repeatedTimeunitPattern(prefix, singleTimeunitPattern, connectorPattern = "\\s{0,5},?\\s{0,5}") {
7883
6833
  const singleTimeunitPatternNoCapture = singleTimeunitPattern.replace(/\((?!\?)/g, "(?:");
7884
6834
  return `${prefix}${singleTimeunitPatternNoCapture}(?:${connectorPattern}${singleTimeunitPatternNoCapture}){0,10}`;
@@ -7900,7 +6850,6 @@ function matchAnyPattern(dictionary) {
7900
6850
  }
7901
6851
 
7902
6852
  // node_modules/chrono-node/dist/esm/calculation/years.js
7903
- init_define_ADMIN_UIDS();
7904
6853
  function findMostLikelyADYear(yearNumber) {
7905
6854
  if (yearNumber < 100) {
7906
6855
  if (yearNumber > 50) {
@@ -8178,7 +7127,6 @@ function collectDateTimeFragment(fragments, match) {
8178
7127
  }
8179
7128
 
8180
7129
  // node_modules/chrono-node/dist/esm/common/parsers/AbstractParserWithWordBoundary.js
8181
- init_define_ADMIN_UIDS();
8182
7130
  var AbstractParserWithWordBoundaryChecking = class {
8183
7131
  innerPatternHasChange(context, currentInnerPattern) {
8184
7132
  return this.innerPattern(context) !== currentInnerPattern;
@@ -8238,7 +7186,6 @@ var ENTimeUnitWithinFormatParser = class extends AbstractParserWithWordBoundaryC
8238
7186
  };
8239
7187
 
8240
7188
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameLittleEndianParser.js
8241
- init_define_ADMIN_UIDS();
8242
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");
8243
7190
  var DATE_GROUP = 1;
8244
7191
  var DATE_TO_GROUP = 2;
@@ -8275,7 +7222,6 @@ var ENMonthNameLittleEndianParser = class extends AbstractParserWithWordBoundary
8275
7222
  };
8276
7223
 
8277
7224
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameMiddleEndianParser.js
8278
- init_define_ADMIN_UIDS();
8279
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");
8280
7226
  var MONTH_NAME_GROUP2 = 1;
8281
7227
  var DATE_GROUP2 = 2;
@@ -8325,7 +7271,6 @@ var ENMonthNameMiddleEndianParser = class extends AbstractParserWithWordBoundary
8325
7271
  };
8326
7272
 
8327
7273
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENMonthNameParser.js
8328
- init_define_ADMIN_UIDS();
8329
7274
  var PATTERN3 = new RegExp(`((?:in)\\s*)?(${matchAnyPattern(MONTH_DICTIONARY)})\\s*(?:(?:,|-|of)?\\s*(${YEAR_PATTERN})?)?(?=[^\\s\\w]|\\s+[^0-9]|\\s+$|$)`, "i");
8330
7275
  var PREFIX_GROUP = 1;
8331
7276
  var MONTH_NAME_GROUP3 = 2;
@@ -8356,7 +7301,6 @@ var ENMonthNameParser = class extends AbstractParserWithWordBoundaryChecking {
8356
7301
  };
8357
7302
 
8358
7303
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENYearMonthDayParser.js
8359
- init_define_ADMIN_UIDS();
8360
7304
  var PATTERN4 = new RegExp(`([0-9]{4})[-\\.\\/\\s](?:(${matchAnyPattern(MONTH_DICTIONARY)})|([0-9]{1,2}))[-\\.\\/\\s]([0-9]{1,2})(?=\\W|$)`, "i");
8361
7305
  var YEAR_NUMBER_GROUP = 1;
8362
7306
  var MONTH_NAME_GROUP4 = 2;
@@ -8395,7 +7339,6 @@ var ENYearMonthDayParser = class extends AbstractParserWithWordBoundaryChecking
8395
7339
  };
8396
7340
 
8397
7341
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENSlashMonthFormatParser.js
8398
- init_define_ADMIN_UIDS();
8399
7342
  var PATTERN5 = new RegExp("([0-9]|0[1-9]|1[012])/([0-9]{4})", "i");
8400
7343
  var MONTH_GROUP = 1;
8401
7344
  var YEAR_GROUP4 = 2;
@@ -8410,11 +7353,7 @@ var ENSlashMonthFormatParser = class extends AbstractParserWithWordBoundaryCheck
8410
7353
  }
8411
7354
  };
8412
7355
 
8413
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeExpressionParser.js
8414
- init_define_ADMIN_UIDS();
8415
-
8416
7356
  // node_modules/chrono-node/dist/esm/common/parsers/AbstractTimeExpressionParser.js
8417
- init_define_ADMIN_UIDS();
8418
7357
  function primaryTimePattern(leftBoundary, primaryPrefix, primarySuffix, flags) {
8419
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);
8420
7359
  }
@@ -8770,7 +7709,6 @@ var ENTimeExpressionParser = class extends AbstractTimeExpressionParser {
8770
7709
  };
8771
7710
 
8772
7711
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitAgoFormatParser.js
8773
- init_define_ADMIN_UIDS();
8774
7712
  var PATTERN6 = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=\\W|$)`, "i");
8775
7713
  var STRICT_PATTERN = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(?:ago|before|earlier)(?=\\W|$)`, "i");
8776
7714
  var ENTimeUnitAgoFormatParser = class extends AbstractParserWithWordBoundaryChecking {
@@ -8792,7 +7730,6 @@ var ENTimeUnitAgoFormatParser = class extends AbstractParserWithWordBoundaryChec
8792
7730
  };
8793
7731
 
8794
7732
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitLaterFormatParser.js
8795
- init_define_ADMIN_UIDS();
8796
7733
  var PATTERN7 = new RegExp(`(${TIME_UNITS_PATTERN})\\s{0,5}(?:later|after|from now|henceforth|forward|out)(?=(?:\\W|$))`, "i");
8797
7734
  var STRICT_PATTERN2 = new RegExp(`(${TIME_UNITS_NO_ABBR_PATTERN})\\s{0,5}(later|after|from now)(?=\\W|$)`, "i");
8798
7735
  var GROUP_NUM_TIMEUNITS = 1;
@@ -8814,14 +7751,7 @@ var ENTimeUnitLaterFormatParser = class extends AbstractParserWithWordBoundaryCh
8814
7751
  }
8815
7752
  };
8816
7753
 
8817
- // node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeDateRangeRefiner.js
8818
- init_define_ADMIN_UIDS();
8819
-
8820
- // node_modules/chrono-node/dist/esm/common/refiners/AbstractMergeDateRangeRefiner.js
8821
- init_define_ADMIN_UIDS();
8822
-
8823
7754
  // node_modules/chrono-node/dist/esm/common/abstractRefiners.js
8824
- init_define_ADMIN_UIDS();
8825
7755
  var Filter = class {
8826
7756
  refine(context, results) {
8827
7757
  return results.filter((r) => this.isValid(context, r));
@@ -8919,14 +7849,7 @@ var ENMergeDateRangeRefiner = class extends AbstractMergeDateRangeRefiner {
8919
7849
  }
8920
7850
  };
8921
7851
 
8922
- // node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeDateTimeRefiner.js
8923
- init_define_ADMIN_UIDS();
8924
-
8925
- // node_modules/chrono-node/dist/esm/common/refiners/AbstractMergeDateTimeRefiner.js
8926
- init_define_ADMIN_UIDS();
8927
-
8928
7852
  // node_modules/chrono-node/dist/esm/calculation/mergingCalculation.js
8929
- init_define_ADMIN_UIDS();
8930
7853
  function mergeDateTimeResult(dateResult, timeResult) {
8931
7854
  const result = dateResult.clone();
8932
7855
  const beginDate = dateResult.start;
@@ -9011,11 +7934,7 @@ var ENMergeDateTimeRefiner = class extends AbstractMergeDateTimeRefiner {
9011
7934
  }
9012
7935
  };
9013
7936
 
9014
- // node_modules/chrono-node/dist/esm/configurations.js
9015
- init_define_ADMIN_UIDS();
9016
-
9017
7937
  // node_modules/chrono-node/dist/esm/common/refiners/ExtractTimezoneAbbrRefiner.js
9018
- init_define_ADMIN_UIDS();
9019
7938
  var TIMEZONE_NAME_PATTERN = new RegExp("^\\s*,?\\s*\\(?([A-Z]{2,4})\\)?(?=\\W|$)", "i");
9020
7939
  var ExtractTimezoneAbbrRefiner = class {
9021
7940
  timezoneOverrides;
@@ -9067,7 +7986,6 @@ var ExtractTimezoneAbbrRefiner = class {
9067
7986
  };
9068
7987
 
9069
7988
  // node_modules/chrono-node/dist/esm/common/refiners/ExtractTimezoneOffsetRefiner.js
9070
- init_define_ADMIN_UIDS();
9071
7989
  var TIMEZONE_OFFSET_PATTERN = new RegExp("^\\s*(?:\\(?(?:GMT|UTC)\\s?)?([+-])(\\d{1,2})(?::?(\\d{2}))?\\)?", "i");
9072
7990
  var TIMEZONE_OFFSET_SIGN_GROUP = 1;
9073
7991
  var TIMEZONE_OFFSET_HOUR_OFFSET_GROUP = 2;
@@ -9106,7 +8024,6 @@ var ExtractTimezoneOffsetRefiner = class {
9106
8024
  };
9107
8025
 
9108
8026
  // node_modules/chrono-node/dist/esm/common/refiners/OverlapRemovalRefiner.js
9109
- init_define_ADMIN_UIDS();
9110
8027
  var OverlapRemovalRefiner = class {
9111
8028
  refine(context, results) {
9112
8029
  if (results.length < 2) {
@@ -9143,7 +8060,6 @@ var OverlapRemovalRefiner = class {
9143
8060
  };
9144
8061
 
9145
8062
  // node_modules/chrono-node/dist/esm/common/refiners/ForwardDateRefiner.js
9146
- init_define_ADMIN_UIDS();
9147
8063
  var ForwardDateRefiner = class {
9148
8064
  refine(context, results) {
9149
8065
  if (!context.option.forwardDate) {
@@ -9209,7 +8125,6 @@ var ForwardDateRefiner = class {
9209
8125
  };
9210
8126
 
9211
8127
  // node_modules/chrono-node/dist/esm/common/refiners/UnlikelyFormatFilter.js
9212
- init_define_ADMIN_UIDS();
9213
8128
  var UnlikelyFormatFilter = class extends Filter {
9214
8129
  strictMode;
9215
8130
  constructor(strictMode) {
@@ -9252,7 +8167,6 @@ var UnlikelyFormatFilter = class extends Filter {
9252
8167
  };
9253
8168
 
9254
8169
  // node_modules/chrono-node/dist/esm/common/parsers/ISOFormatParser.js
9255
- init_define_ADMIN_UIDS();
9256
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");
9257
8171
  var YEAR_NUMBER_GROUP2 = 1;
9258
8172
  var MONTH_NUMBER_GROUP2 = 2;
@@ -9306,7 +8220,6 @@ var ISOFormatParser = class extends AbstractParserWithWordBoundaryChecking {
9306
8220
  };
9307
8221
 
9308
8222
  // node_modules/chrono-node/dist/esm/common/refiners/MergeWeekdayComponentRefiner.js
9309
- init_define_ADMIN_UIDS();
9310
8223
  var MergeWeekdayComponentRefiner = class extends MergingRefiner {
9311
8224
  mergeResults(textBetween, currentResult, nextResult) {
9312
8225
  const newResult = nextResult.clone();
@@ -9337,11 +8250,7 @@ function includeCommonConfiguration(configuration2, strictMode = false) {
9337
8250
  return configuration2;
9338
8251
  }
9339
8252
 
9340
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENCasualDateParser.js
9341
- init_define_ADMIN_UIDS();
9342
-
9343
8253
  // node_modules/chrono-node/dist/esm/common/casualReferences.js
9344
- init_define_ADMIN_UIDS();
9345
8254
  function now(reference) {
9346
8255
  const targetDate = reference.getDateWithAdjustedTimezone();
9347
8256
  const component = new ParsingComponents(reference, {});
@@ -9487,7 +8396,6 @@ var ENCasualDateParser = class extends AbstractParserWithWordBoundaryChecking {
9487
8396
  };
9488
8397
 
9489
8398
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENCasualTimeParser.js
9490
- init_define_ADMIN_UIDS();
9491
8399
  var PATTERN10 = /(?:this)?\s{0,3}(morning|afternoon|evening|night|midnight|midday|noon)(?=\W|$)/i;
9492
8400
  var ENCasualTimeParser = class extends AbstractParserWithWordBoundaryChecking {
9493
8401
  innerPattern() {
@@ -9521,11 +8429,7 @@ var ENCasualTimeParser = class extends AbstractParserWithWordBoundaryChecking {
9521
8429
  }
9522
8430
  };
9523
8431
 
9524
- // node_modules/chrono-node/dist/esm/locales/en/parsers/ENWeekdayParser.js
9525
- init_define_ADMIN_UIDS();
9526
-
9527
8432
  // node_modules/chrono-node/dist/esm/calculation/weekdays.js
9528
- init_define_ADMIN_UIDS();
9529
8433
  function createParsingComponentsAtWeekday(reference, weekday, modifier) {
9530
8434
  const refDate = reference.getDateWithAdjustedTimezone();
9531
8435
  const daysToWeekday = getDaysToWeekday(refDate, weekday, modifier);
@@ -9628,7 +8532,6 @@ var ENWeekdayParser = class extends AbstractParserWithWordBoundaryChecking {
9628
8532
  };
9629
8533
 
9630
8534
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENRelativeDateFormatParser.js
9631
- init_define_ADMIN_UIDS();
9632
8535
  var PATTERN12 = new RegExp(`(this|last|past|next|after\\s*this)\\s*(${matchAnyPattern(TIME_UNIT_DICTIONARY)})(?=\\s*)(?=\\W|$)`, "i");
9633
8536
  var MODIFIER_WORD_GROUP = 1;
9634
8537
  var RELATIVE_WORD_GROUP = 2;
@@ -9674,7 +8577,6 @@ var ENRelativeDateFormatParser = class extends AbstractParserWithWordBoundaryChe
9674
8577
  };
9675
8578
 
9676
8579
  // node_modules/chrono-node/dist/esm/common/parsers/SlashDateFormatParser.js
9677
- init_define_ADMIN_UIDS();
9678
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");
9679
8581
  var OPENING_GROUP = 1;
9680
8582
  var ENDING_GROUP = 5;
@@ -9743,7 +8645,6 @@ var SlashDateFormatParser = class {
9743
8645
  };
9744
8646
 
9745
8647
  // node_modules/chrono-node/dist/esm/locales/en/parsers/ENTimeUnitCasualRelativeFormatParser.js
9746
- init_define_ADMIN_UIDS();
9747
8648
  var PATTERN14 = new RegExp(`(this|last|past|next|after|\\+|-)\\s*(${TIME_UNITS_PATTERN})(?=\\W|$)`, "i");
9748
8649
  var PATTERN_NO_ABBR = new RegExp(`(this|last|past|next|after|\\+|-)\\s*(${TIME_UNITS_NO_ABBR_PATTERN})(?=\\W|$)`, "i");
9749
8650
  var ENTimeUnitCasualRelativeFormatParser = class extends AbstractParserWithWordBoundaryChecking {
@@ -9773,7 +8674,6 @@ var ENTimeUnitCasualRelativeFormatParser = class extends AbstractParserWithWordB
9773
8674
  };
9774
8675
 
9775
8676
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeRelativeAfterDateRefiner.js
9776
- init_define_ADMIN_UIDS();
9777
8677
  function IsPositiveFollowingReference(result) {
9778
8678
  return result.text.match(/^[+-]/i) != null;
9779
8679
  }
@@ -9798,7 +8698,6 @@ var ENMergeRelativeAfterDateRefiner = class extends MergingRefiner {
9798
8698
  };
9799
8699
 
9800
8700
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENMergeRelativeFollowByDateRefiner.js
9801
- init_define_ADMIN_UIDS();
9802
8701
  function hasImpliedEarlierReferenceDate(result) {
9803
8702
  return result.text.match(/\s+(before|from)$/i) != null;
9804
8703
  }
@@ -9829,7 +8728,6 @@ var ENMergeRelativeFollowByDateRefiner = class extends MergingRefiner {
9829
8728
  };
9830
8729
 
9831
8730
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENExtractYearSuffixRefiner.js
9832
- init_define_ADMIN_UIDS();
9833
8731
  var YEAR_SUFFIX_PATTERN = new RegExp(`^\\s*(${YEAR_PATTERN})`, "i");
9834
8732
  var YEAR_GROUP6 = 1;
9835
8733
  var ENExtractYearSuffixRefiner = class {
@@ -9861,7 +8759,6 @@ var ENExtractYearSuffixRefiner = class {
9861
8759
  };
9862
8760
 
9863
8761
  // node_modules/chrono-node/dist/esm/locales/en/refiners/ENUnlikelyFormatFilter.js
9864
- init_define_ADMIN_UIDS();
9865
8762
  var ENUnlikelyFormatFilter = class extends Filter {
9866
8763
  constructor() {
9867
8764
  super();
@@ -10043,7 +8940,7 @@ var GB = new Chrono(configuration.createCasualConfiguration(true));
10043
8940
 
10044
8941
  // node_modules/chrono-node/dist/esm/index.js
10045
8942
  var casual2 = casual;
10046
- function parseDate2(text, ref, option) {
8943
+ function parseDate(text, ref, option) {
10047
8944
  return casual2.parseDate(text, ref, option);
10048
8945
  }
10049
8946
 
@@ -10052,7 +8949,7 @@ function parseHumanDate(input) {
10052
8949
  const trimmed = input.trim();
10053
8950
  if (!trimmed) return null;
10054
8951
  if (/^\d{4}-\d{2}-\d{2}/.test(trimmed)) return trimmed;
10055
- const parsed = parseDate2(trimmed, /* @__PURE__ */ new Date(), { forwardDate: true });
8952
+ const parsed = parseDate(trimmed, /* @__PURE__ */ new Date(), { forwardDate: true });
10056
8953
  if (!parsed) return null;
10057
8954
  const date = parsed.toISOString().slice(0, 10);
10058
8955
  const hours = parsed.getHours();
@@ -10070,10 +8967,9 @@ function parseHumanDateOnly(input) {
10070
8967
  }
10071
8968
 
10072
8969
  // src/cli/lib/stdin.ts
10073
- init_define_ADMIN_UIDS();
10074
- var fs4 = __toESM(require("fs"), 1);
8970
+ var fs3 = __toESM(require("fs"), 1);
10075
8971
  function readStdinLines() {
10076
- const input = fs4.readFileSync(0, "utf8");
8972
+ const input = fs3.readFileSync(0, "utf8");
10077
8973
  return input.split("\n").map((l) => l.trim()).filter(Boolean);
10078
8974
  }
10079
8975
 
@@ -10093,7 +8989,7 @@ async function pickTask(uid, id, actionName) {
10093
8989
  message: `Select task to ${actionName}`,
10094
8990
  options: pending.map((t2) => ({
10095
8991
  value: t2.id,
10096
- 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)}`
10097
8993
  }))
10098
8994
  });
10099
8995
  return selected;
@@ -10118,13 +9014,12 @@ function extractTime(dueDate) {
10118
9014
  const time = parts[1];
10119
9015
  return time === "00:00" ? "" : time;
10120
9016
  }
10121
- function isRepeating2(t2) {
10122
- const repeat = t2.repeat;
10123
- return !!repeat?.type && repeat.type !== "none";
9017
+ function isRepeating(t2) {
9018
+ return !!t2.repeat?.type && t2.repeat.type !== "none";
10124
9019
  }
10125
9020
  function getCheckIndicator(t2) {
10126
9021
  if (t2.completed) return import_picocolors8.default.green(SYM.check);
10127
- if (isRepeating2(t2)) return import_picocolors8.default.blue(SYM.repeat);
9022
+ if (isRepeating(t2)) return import_picocolors8.default.blue(SYM.repeat);
10128
9023
  return import_picocolors8.default.dim(SYM.circle);
10129
9024
  }
10130
9025
  function sortTasksForDisplay(tasks) {
@@ -10132,16 +9027,16 @@ function sortTasksForDisplay(tasks) {
10132
9027
  return [...tasks].sort((a, b) => {
10133
9028
  const timeA = extractTime(a.dueDate);
10134
9029
  const timeB = extractTime(b.dueDate);
10135
- const repA = isRepeating2(a);
10136
- const repB = isRepeating2(b);
9030
+ const repA = isRepeating(a);
9031
+ const repB = isRepeating(b);
10137
9032
  if (timeA && !timeB) return -1;
10138
9033
  if (!timeA && timeB) return 1;
10139
9034
  if (timeA && timeB) return timeA.localeCompare(timeB);
10140
9035
  if (repA && !repB) return -1;
10141
9036
  if (!repA && repB) return 1;
10142
9037
  if (repA && repB) {
10143
- const ra = a.repeat?.type ?? "";
10144
- const rb = b.repeat?.type ?? "";
9038
+ const ra = a.repeat.type ?? "";
9039
+ const rb = b.repeat.type ?? "";
10145
9040
  return (repeatOrder[ra] ?? 99) - (repeatOrder[rb] ?? 99);
10146
9041
  }
10147
9042
  return 0;
@@ -10153,7 +9048,7 @@ function printTaskDetail(t2) {
10153
9048
  printRecord([
10154
9049
  ["ID", dim(t2.id)],
10155
9050
  ["Text", t2.text],
10156
- ["Due", formatDate2(t2.dueDate) || dim("none (backlog)")],
9051
+ ["Due", formatDate(t2.dueDate) || dim("none (backlog)")],
10157
9052
  ["Status", t2.completed ? import_picocolors8.default.green("completed") : import_picocolors8.default.yellow("pending")],
10158
9053
  ["Tags", formatTags(t2.tags) || dim("none")],
10159
9054
  ["Difficulty", formatDifficulty(t2.difficulty) || dim("not set")],
@@ -10162,18 +9057,18 @@ function printTaskDetail(t2) {
10162
9057
  ["Note", t2.note || dim("none")],
10163
9058
  ["Public", t2.isPublic ? import_picocolors8.default.green("yes") : import_picocolors8.default.yellow("no")],
10164
9059
  ["Completions", String(t2.completions ?? 0)],
10165
- ["Created", formatDate2(t2.createdAt)]
9060
+ ["Created", formatDate(t2.createdAt)]
10166
9061
  ]);
10167
9062
  console.log("");
10168
9063
  }
10169
9064
  function printTaskLine(t2) {
10170
9065
  const check = getCheckIndicator(t2);
10171
- const rawText = truncate(String(t2.text ?? ""), 50);
9066
+ const rawText = truncate(t2.text, 50);
10172
9067
  const text = t2.completed ? import_picocolors8.default.strikethrough(import_picocolors8.default.dim(rawText)) : rawText;
10173
9068
  const time = extractTime(t2.dueDate);
10174
9069
  const tags = formatTags(t2.tags);
10175
9070
  const difficulty = formatDifficulty(t2.difficulty);
10176
- const id = import_picocolors8.default.dim(String(t2.id ?? ""));
9071
+ const id = import_picocolors8.default.dim(t2.id);
10177
9072
  const parts = [check, text];
10178
9073
  if (time) parts.push(import_picocolors8.default.cyan(time));
10179
9074
  if (tags) parts.push(tags);
@@ -10202,8 +9097,7 @@ function registerTasksCommands(program3) {
10202
9097
  console.log(` ${import_picocolors8.default.bold("Backlog")} ${import_picocolors8.default.dim(`(${items.length})`)}`);
10203
9098
  } else {
10204
9099
  const viewDate = date ? /* @__PURE__ */ new Date(date + "T00:00:00") : /* @__PURE__ */ new Date();
10205
- const streakCount = getCompletedTodayCount();
10206
- console.log(formatWeekdayHeader(viewDate, streakCount));
9100
+ console.log(formatWeekdayHeader(viewDate, completed.length));
10207
9101
  }
10208
9102
  const tagLine = formatTagsSummary(items);
10209
9103
  if (tagLine) console.log(`
@@ -10268,7 +9162,7 @@ Examples:
10268
9162
  Examples:
10269
9163
  $ numo tasks get abc123
10270
9164
  $ numo tasks get abc123 --json | jq '.text'`);
10271
- tasks.command("create").description("Create a new task").option("--text <text>", "Task text").option("--due <date>", "Due date YYYY-MM-DD").option("--tags <tags>", "Comma-separated tags").option("--private", "Make task private").option("--note <note>", "Task note").option("--priority <n>", "Priority 0.1\u20131.0").option("--difficulty <n>", "Difficulty 0\u20133").option("--duration <n>", "Duration in minutes").action(async function() {
9165
+ tasks.command("create").description("Create a new task").option("--text <text>", "Task text").option("--due <date>", "Due date YYYY-MM-DD").option("--tags <tags>", "Comma-separated tags").option("--public", "Make task public (default)").option("--private", "Make task private").option("--note <note>", "Task note").option("--priority <n>", "Priority 0.1\u20131.0").option("--difficulty <n>", "Difficulty 0\u20133").option("--duration <n>", "Duration in minutes").action(async function() {
10272
9166
  const opts = this.optsWithGlobals();
10273
9167
  const uid = requireUid();
10274
9168
  const text = await promptForMissing({ value: opts.text, message: "Task text", placeholder: "What do you need to do?" });
@@ -10331,6 +9225,16 @@ Examples:
10331
9225
  body.dueDate = fmt(today2);
10332
9226
  }
10333
9227
  }
9228
+ if (!opts.private && !opts.public) {
9229
+ const visibility = await promptSelect({
9230
+ message: "Visibility",
9231
+ options: [
9232
+ { value: "public", label: "Public \u2014 visible to your squad" },
9233
+ { value: "private", label: "Private \u2014 only you can see it" }
9234
+ ]
9235
+ });
9236
+ if (visibility === "private") body.isPublic = false;
9237
+ }
10334
9238
  const addDetails = await promptConfirm({
10335
9239
  message: "Add details? (tags, effort, time, note)",
10336
9240
  initialValue: false
@@ -10376,6 +9280,7 @@ Examples:
10376
9280
  if (note) body.note = note;
10377
9281
  }
10378
9282
  }
9283
+ if (opts.public) body.isPublic = true;
10379
9284
  if (opts.private) body.isPublic = false;
10380
9285
  if (opts.tags && !body.tags) body.tags = opts.tags.split(",");
10381
9286
  if (opts.note && !body.note) body.note = opts.note;
@@ -10390,6 +9295,7 @@ Examples:
10390
9295
  if (opts.priority) body.priority = parseFloat(opts.priority);
10391
9296
  if (opts.difficulty !== void 0) body.difficulty = parseInt(opts.difficulty);
10392
9297
  if (opts.duration) body.duration = parseInt(opts.duration);
9298
+ if (opts.public) body.isPublic = true;
10393
9299
  if (opts.private) body.isPublic = false;
10394
9300
  }
10395
9301
  await runCreate({
@@ -10398,11 +9304,10 @@ Examples:
10398
9304
  dataKey: "task",
10399
9305
  spinnerMessage: "Creating task...",
10400
9306
  onInteractive: (_task, payload) => {
10401
- const task = payload.task;
9307
+ const { task, karma } = payload;
10402
9308
  const check = import_picocolors8.default.green(SYM.check);
10403
9309
  console.log(`
10404
9310
  ${check} Created ${task.text}`);
10405
- const karma = payload.karma;
10406
9311
  if (karma) {
10407
9312
  console.log(` ${formatKarmaGain(karma)}${" ".repeat(20)}${import_picocolors8.default.dim(task.id)}`);
10408
9313
  }
@@ -10457,9 +9362,9 @@ Examples:
10457
9362
  fn: () => updateTask(uid, taskId, body),
10458
9363
  dataKey: "task",
10459
9364
  spinnerMessage: "Updating task...",
10460
- onInteractive: (task) => {
9365
+ onInteractive: (payload) => {
10461
9366
  console.log(`
10462
- ${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)}
10463
9368
  `);
10464
9369
  }
10465
9370
  });
@@ -10489,7 +9394,7 @@ Examples:
10489
9394
  let taskText = taskId;
10490
9395
  try {
10491
9396
  const task = await getTask(uid, taskId);
10492
- taskText = String(task.text ?? taskId);
9397
+ taskText = task.text ?? taskId;
10493
9398
  } catch {
10494
9399
  }
10495
9400
  const confirmed = await promptConfirm({
@@ -10507,9 +9412,8 @@ Examples:
10507
9412
  spinnerMessage: "Deleting task...",
10508
9413
  onInteractive: (data) => {
10509
9414
  const cross = import_picocolors8.default.red(SYM.cross);
10510
- const text = data.taskText || taskId;
10511
9415
  console.log(`
10512
- ${cross} Deleted ${text}`);
9416
+ ${cross} Deleted ${data.taskText || taskId}`);
10513
9417
  if (data.archived) console.log(` ${import_picocolors8.default.dim("Archived")}`);
10514
9418
  console.log("");
10515
9419
  }
@@ -10541,13 +9445,10 @@ Examples:
10541
9445
  spinnerMessage: "Completing task...",
10542
9446
  onInteractive: (data) => {
10543
9447
  const check = import_picocolors8.default.green(SYM.check);
10544
- const text = data.taskText ?? taskId;
10545
9448
  console.log(`
10546
- ${check} Done! ${text}`);
10547
- const karma = data.karma;
10548
- const checksInRow = data.checksInRow;
10549
- if (karma) {
10550
- console.log(` ${formatKarmaGain(karma, checksInRow)}`);
9449
+ ${check} Done! ${data.taskText ?? taskId}`);
9450
+ if (data.karma) {
9451
+ console.log(` ${formatKarmaGain(data.karma, data.checksInRow)}`);
10551
9452
  }
10552
9453
  console.log("");
10553
9454
  }
@@ -10579,9 +9480,8 @@ Examples:
10579
9480
  spinnerMessage: "Uncompleting task...",
10580
9481
  onInteractive: (data) => {
10581
9482
  const arrow = SYM.undo;
10582
- const text = data.text ?? taskId;
10583
9483
  console.log(`
10584
- ${import_picocolors8.default.yellow(arrow)} Reverted ${text}`);
9484
+ ${import_picocolors8.default.yellow(arrow)} Reverted ${data.task.text ?? taskId}`);
10585
9485
  console.log(` ${import_picocolors8.default.dim("Karma adjustment applied")}`);
10586
9486
  console.log("");
10587
9487
  }
@@ -10592,13 +9492,10 @@ Examples:
10592
9492
  }
10593
9493
 
10594
9494
  // src/cli/commands/posts.ts
10595
- init_define_ADMIN_UIDS();
10596
9495
  var import_picocolors10 = __toESM(require_picocolors(), 1);
10597
9496
 
10598
9497
  // src/cli/lib/pagination.ts
10599
- init_define_ADMIN_UIDS();
10600
9498
  var import_picocolors9 = __toESM(require_picocolors(), 1);
10601
- init_errors();
10602
9499
  function printPaginationHint(opts) {
10603
9500
  if (!opts.nextCursor) return;
10604
9501
  const parts = [opts.command, `--cursor ${opts.nextCursor}`];
@@ -10608,223 +9505,56 @@ ${import_picocolors9.default.dim("Next page:")} ${import_picocolors9.default.dim
10608
9505
  }
10609
9506
 
10610
9507
  // src/cli/services/posts.ts
10611
- init_define_ADMIN_UIDS();
10612
- function generateSlug(title) {
10613
- return title.toLowerCase().replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").slice(0, 80) + "-" + Date.now().toString(36);
10614
- }
10615
9508
  async function listPosts(opts) {
10616
- const lim = Math.min(opts.limit ?? 20, MAX_POSTS_PER_REQUEST);
10617
- const queryOpts = {
10618
- orderBy: [{ field: "createdAt", direction: "DESCENDING" }],
10619
- limit: lim + 1
10620
- };
10621
- if (opts.cursor) {
10622
- const cursorDoc = await getDoc(`posts/${opts.cursor}`);
10623
- if (cursorDoc.createdAt != null) {
10624
- queryOpts.startAfter = [cursorDoc.createdAt];
10625
- }
10626
- }
10627
- const docs = await runQuery("", "posts", queryOpts);
10628
- const hasMore = docs.length > lim;
10629
- const posts = docs.slice(0, lim);
10630
- return {
10631
- posts,
10632
- nextCursor: hasMore ? posts[posts.length - 1].id : void 0
10633
- };
9509
+ return api.get("/api/posts", {
9510
+ cursor: opts.cursor,
9511
+ limit: opts.limit?.toString()
9512
+ });
10634
9513
  }
10635
9514
  async function getPost(id) {
10636
- validateDocId(id, "Post ID");
10637
- const post = await getDoc(`posts/${id}`);
10638
- if (post.authorId) {
10639
- try {
10640
- const author = await getDoc(`users/${post.authorId}`);
10641
- post.authorName = author.username ?? null;
10642
- } catch {
10643
- }
10644
- }
10645
- return post;
9515
+ return api.get(`/api/posts/${encodeURIComponent(id)}`);
10646
9516
  }
10647
9517
  async function createPost(uid, body) {
10648
- const now2 = Date.now();
10649
- const postData = {
10650
- title: body.title,
10651
- body: body.body,
10652
- tag: body.tag,
10653
- authorId: uid,
10654
- slug: generateSlug(body.title),
10655
- isPublic: body.isPublic !== false,
10656
- createdAt: now2,
10657
- updatedAt: now2,
10658
- commentsCount: 0,
10659
- likesCount: 0
10660
- };
10661
- const doc = await createDoc("posts", postData);
10662
- const postId = doc.id;
10663
- try {
10664
- await giveKarma(uid, "createPost", postId, KARMA_POINTS.createPost, String(body.title));
10665
- } catch {
10666
- }
10667
- try {
10668
- await incrementField(`users/${uid}/activity/totals`, "posts.written", 1);
10669
- } catch {
10670
- }
10671
- return { post: doc, karma: KARMA_POINTS.createPost };
9518
+ return api.post("/api/posts", body);
10672
9519
  }
10673
9520
  async function updatePost(uid, id, body) {
10674
- validateDocId(id, "Post ID");
10675
- const post = await getDoc(`posts/${id}`);
10676
- checkOwnership(post, uid, "update");
10677
- const allowed = ["title", "body", "tag", "isPublic"];
10678
- const update = { updatedAt: Date.now() };
10679
- const fieldMask = ["updatedAt"];
10680
- for (const key of allowed) {
10681
- if (key in body) {
10682
- update[key] = body[key];
10683
- fieldMask.push(key);
10684
- }
10685
- }
10686
- await updateDoc(`posts/${id}`, update, fieldMask);
10687
- const updated = await getDoc(`posts/${id}`);
10688
- return { post: updated };
9521
+ return api.patch(`/api/posts/${encodeURIComponent(id)}`, body);
10689
9522
  }
10690
9523
  async function deletePost(uid, id) {
10691
- validateDocId(id, "Post ID");
10692
- const post = await getDoc(`posts/${id}`);
10693
- checkOwnership(post, uid, "delete");
10694
- await deleteDoc(`posts/${id}`);
9524
+ await api.del(`/api/posts/${encodeURIComponent(id)}`);
10695
9525
  }
10696
9526
 
10697
9527
  // src/cli/services/comments.ts
10698
- init_define_ADMIN_UIDS();
10699
9528
  async function listComments(postId, opts) {
10700
- validateDocId(postId, "Post ID");
10701
- const lim = Math.min(opts.limit ?? 20, MAX_COMMENTS_PER_REQUEST);
10702
- const queryOpts = {
10703
- orderBy: [{ field: "createdAt", direction: "ASCENDING" }],
10704
- limit: lim + 1
10705
- };
10706
- if (opts.cursor) {
10707
- const cursorDoc = await getDoc(`posts/${postId}/comments/${opts.cursor}`);
10708
- if (cursorDoc.createdAt != null) {
10709
- queryOpts.startAfter = [cursorDoc.createdAt];
10710
- }
10711
- }
10712
- const docs = await runQuery(`posts/${postId}`, "comments", queryOpts);
10713
- const hasMore = docs.length > lim;
10714
- const comments = docs.slice(0, lim);
10715
- const uids = [...new Set(comments.map((c) => c.userId).filter(Boolean))];
10716
- const userMap = {};
10717
- await Promise.all(uids.map(async (uid) => {
10718
- try {
10719
- const user = await getDoc(`users/${uid}`);
10720
- if (user.username) userMap[uid] = user.username;
10721
- } catch {
10722
- }
10723
- }));
10724
- for (const c of comments) {
10725
- c.authorName = userMap[c.userId] ?? null;
10726
- }
10727
- return {
10728
- comments,
10729
- nextCursor: hasMore ? comments[comments.length - 1].id : void 0
10730
- };
9529
+ return api.get(`/api/posts/${encodeURIComponent(postId)}/comments`, {
9530
+ cursor: opts.cursor,
9531
+ limit: opts.limit?.toString()
9532
+ });
10731
9533
  }
10732
9534
  async function createComment(uid, postId, text) {
10733
- validateDocId(postId, "Post ID");
10734
- const now2 = Date.now();
10735
- const commentData = {
10736
- postId,
10737
- userId: uid,
10738
- text,
10739
- createdAt: now2,
10740
- updatedAt: now2,
10741
- textLength: text.length,
10742
- likes: [],
10743
- repliesCount: 0
10744
- };
10745
- const doc = await createDoc(`posts/${postId}/comments`, commentData);
10746
- const commentId = doc.id;
10747
- await incrementField(`posts/${postId}`, "commentsCount", 1);
10748
- try {
10749
- await giveKarma(uid, "addComment", `${postId}_${commentId}`, KARMA_POINTS.addComment, text);
10750
- } catch {
10751
- }
10752
- try {
10753
- await incrementField(`users/${uid}/activity/totals`, "comments.written", 1);
10754
- } catch {
10755
- }
10756
- return { comment: doc, karma: KARMA_POINTS.addComment };
9535
+ return api.post(`/api/posts/${encodeURIComponent(postId)}/comments`, { text });
10757
9536
  }
10758
9537
  async function deleteComment(uid, postId, commentId) {
10759
- validateDocId(postId, "Post ID");
10760
- validateDocId(commentId, "Comment ID");
10761
- const comment = await getDoc(`posts/${postId}/comments/${commentId}`);
10762
- checkOwnership(comment, uid, "delete");
10763
- await deleteDoc(`posts/${postId}/comments/${commentId}`);
10764
- await incrementField(`posts/${postId}`, "commentsCount", -1);
9538
+ await api.del(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}`);
10765
9539
  }
10766
9540
 
10767
9541
  // src/cli/services/replies.ts
10768
- init_define_ADMIN_UIDS();
10769
9542
  async function listReplies(postId, commentId, opts) {
10770
- validateDocId(postId, "Post ID");
10771
- validateDocId(commentId, "Comment ID");
10772
- const lim = Math.min(opts.limit ?? 20, MAX_REPLIES_PER_REQUEST);
10773
- const queryOpts = {
10774
- orderBy: [{ field: "createdAt", direction: "ASCENDING" }],
10775
- limit: lim + 1
10776
- };
10777
- if (opts.cursor) {
10778
- const cursorDoc = await getDoc(`posts/${postId}/comments/${commentId}/replies/${opts.cursor}`);
10779
- if (cursorDoc.createdAt != null) {
10780
- queryOpts.startAfter = [cursorDoc.createdAt];
10781
- }
10782
- }
10783
- const docs = await runQuery(`posts/${postId}/comments/${commentId}`, "replies", queryOpts);
10784
- const hasMore = docs.length > lim;
10785
- const replies = docs.slice(0, lim);
10786
- return {
10787
- replies,
10788
- nextCursor: hasMore ? replies[replies.length - 1].id : void 0
10789
- };
9543
+ return api.get(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, {
9544
+ cursor: opts.cursor,
9545
+ limit: opts.limit?.toString()
9546
+ });
10790
9547
  }
10791
9548
  async function createReply(uid, postId, commentId, text) {
10792
- validateDocId(postId, "Post ID");
10793
- validateDocId(commentId, "Comment ID");
10794
- const now2 = Date.now();
10795
- const replyData = {
10796
- postId,
10797
- userId: uid,
10798
- text,
10799
- createdAt: now2,
10800
- updatedAt: now2,
10801
- textLength: text.length,
10802
- likes: [],
10803
- parentCommentId: commentId
10804
- };
10805
- const doc = await createDoc(`posts/${postId}/comments/${commentId}/replies`, replyData);
10806
- const replyId = doc.id;
10807
- await incrementField(`posts/${postId}/comments/${commentId}`, "repliesCount", 1);
10808
- try {
10809
- await giveKarma(uid, "addComment", `${postId}_${replyId}`, KARMA_POINTS.addComment, text);
10810
- } catch {
10811
- }
10812
- try {
10813
- await incrementField(`users/${uid}/activity/totals`, "replies.written", 1);
10814
- } catch {
10815
- }
10816
- return { reply: doc, karma: KARMA_POINTS.addComment };
9549
+ return api.post(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies`, { text });
10817
9550
  }
10818
9551
  async function deleteReply(uid, postId, commentId, replyId) {
10819
- validateDocId(postId, "Post ID");
10820
- validateDocId(commentId, "Comment ID");
10821
- validateDocId(replyId, "Reply ID");
10822
- const reply = await getDoc(`posts/${postId}/comments/${commentId}/replies/${replyId}`);
10823
- checkOwnership(reply, uid, "delete");
10824
- await deleteDoc(`posts/${postId}/comments/${commentId}/replies/${replyId}`);
10825
- await incrementField(`posts/${postId}/comments/${commentId}`, "repliesCount", -1);
9552
+ await api.del(`/api/posts/${encodeURIComponent(postId)}/comments/${encodeURIComponent(commentId)}/replies/${encodeURIComponent(replyId)}`);
10826
9553
  }
10827
9554
 
9555
+ // src/cli/types/api.ts
9556
+ var POST_TAGS = ["general", "hack", "story", "meme", "other", "question", "hack-tip", "activity"];
9557
+
10828
9558
  // src/cli/commands/posts.ts
10829
9559
  init_prompts();
10830
9560
  init_tty();
@@ -10837,7 +9567,7 @@ async function pickComment(postId, commentId) {
10837
9567
  message: "Select comment",
10838
9568
  options: comments.map((c) => ({
10839
9569
  value: c.id,
10840
- 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 ?? "")}`
10841
9571
  }))
10842
9572
  });
10843
9573
  }
@@ -10850,16 +9580,16 @@ async function pickReply(postId, commentId, replyId) {
10850
9580
  message: "Select reply",
10851
9581
  options: replies.map((r) => ({
10852
9582
  value: r.id,
10853
- 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 ?? "")}`
10854
9584
  }))
10855
9585
  });
10856
9586
  }
10857
9587
  function printPostLine(p) {
10858
- const tag = import_picocolors10.default.cyan(String(p.tag ?? ""));
10859
- const title = truncate(String(p.title ?? ""), 55);
9588
+ const tag = import_picocolors10.default.cyan(p.tag ?? "");
9589
+ const title = truncate(p.title, 55);
10860
9590
  const comments = p.commentsCount ? import_picocolors10.default.dim(`${p.commentsCount} comments`) : "";
10861
9591
  const time = formatRelativeDate(p.createdAt);
10862
- const id = import_picocolors10.default.dim(String(p.id ?? ""));
9592
+ const id = import_picocolors10.default.dim(p.id);
10863
9593
  const parts = [tag, import_picocolors10.default.bold(title)];
10864
9594
  if (comments) parts.push(comments);
10865
9595
  parts.push(import_picocolors10.default.dim(time));
@@ -10876,7 +9606,7 @@ function printPostDetail(p) {
10876
9606
  console.log(` ${import_picocolors10.default.dim(SYM.dash.repeat(40))}`);
10877
9607
  printRecord([
10878
9608
  ["ID", import_picocolors10.default.dim(p.id)],
10879
- ["Tag", import_picocolors10.default.cyan(String(p.tag ?? ""))],
9609
+ ["Tag", import_picocolors10.default.cyan(p.tag ?? "")],
10880
9610
  ["Author", p.authorName ?? p.authorId],
10881
9611
  ["Comments", p.commentsCount != null ? String(p.commentsCount) : null],
10882
9612
  ["Likes", p.likesCount != null ? String(p.likesCount) : null],
@@ -10885,18 +9615,18 @@ function printPostDetail(p) {
10885
9615
  console.log("");
10886
9616
  }
10887
9617
  function printCommentLine(c) {
10888
- const author = import_picocolors10.default.bold(String(c.authorName ?? c.userId ?? ""));
9618
+ const author = import_picocolors10.default.bold(c.authorName ?? c.userId ?? "");
10889
9619
  const time = import_picocolors10.default.dim(formatRelativeDate(c.createdAt));
10890
9620
  const replies = c.repliesCount ? import_picocolors10.default.dim(`\xB7 ${c.repliesCount} replies`) : "";
10891
- const text = String(c.text ?? "");
9621
+ const text = c.text ?? "";
10892
9622
  console.log(` ${author} ${time}${replies}`);
10893
9623
  console.log(` ${text}`);
10894
9624
  console.log("");
10895
9625
  }
10896
9626
  function printReplyLine(r) {
10897
- const text = truncate(String(r.text ?? ""), 60);
9627
+ const text = truncate(r.text ?? "", 60);
10898
9628
  const time = formatRelativeDate(r.createdAt);
10899
- const id = import_picocolors10.default.dim(String(r.id ?? ""));
9629
+ const id = import_picocolors10.default.dim(r.id);
10900
9630
  console.log(` ${text} ${import_picocolors10.default.dim(time)} ${id}`);
10901
9631
  }
10902
9632
  function registerPostsCommands(program3) {
@@ -10998,148 +9728,134 @@ Examples:
10998
9728
  }
10999
9729
  });
11000
9730
  });
11001
- if (isAdmin()) {
11002
- 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() {
11003
- const opts = this.optsWithGlobals();
11004
- const uid = requireAdmin();
11005
- const title = await promptForMissing({ value: opts.title, message: "Title", placeholder: "Post title" });
11006
- const postBody = await promptForMissing({ value: opts.body, message: "Body", placeholder: "Post body" });
11007
- let tag = opts.tag;
11008
- if (!tag) {
11009
- tag = await promptSelect({
11010
- message: "Tag",
11011
- options: POST_TAGS.map((t2) => ({ value: t2, label: t2 }))
11012
- });
11013
- }
11014
- const body = { title, body: postBody, tag };
11015
- await runCreate({
11016
- global: opts,
11017
- fn: () => createPost(uid, body),
11018
- dataKey: "post",
11019
- spinnerMessage: "Creating post...",
11020
- onInteractive: (post) => {
11021
- console.log(`
11022
- ${import_picocolors10.default.green("Posted!")} ${post.title} ${import_picocolors10.default.dim(post.id)}
11023
- `);
11024
- }
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 }))
11025
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
+ }
11026
9754
  });
11027
- posts.command("update [id]").description("Update a post").option("--title <title>").option("--body <body>").option("--tag <tag>").action(async function(id) {
11028
- const opts = this.optsWithGlobals();
11029
- const uid = requireAdmin();
11030
- const postId = await promptForMissing({ value: id, message: "Post ID" });
11031
- const body = {};
11032
- const hasAnyFlag = opts.title || opts.body || opts.tag;
11033
- if (!hasAnyFlag && isInteractive() && !opts.json) {
11034
- const title = await promptText({ message: "Title (enter to skip)", required: false });
11035
- if (title) body.title = title;
11036
- const postBody = await promptText({ message: "Body (enter to skip)", required: false });
11037
- if (postBody) body.body = postBody;
11038
- const changeTag = await promptText({ message: "Tag (enter to skip)", placeholder: POST_TAGS.join("|"), required: false });
11039
- if (changeTag) body.tag = changeTag;
11040
- } else {
11041
- if (opts.title) body.title = opts.title;
11042
- if (opts.body) body.body = opts.body;
11043
- if (opts.tag) body.tag = opts.tag;
11044
- }
11045
- await runWrite({
11046
- global: opts,
11047
- fn: () => updatePost(uid, postId, body),
11048
- dataKey: "post",
11049
- spinnerMessage: "Updating post...",
11050
- onInteractive: (post) => {
11051
- console.log(`
11052
- ${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)}
11053
9782
  `);
11054
- }
11055
- });
9783
+ }
11056
9784
  });
11057
- posts.command("delete [id]").description("Delete a post").action(async function(id) {
11058
- const uid = requireAdmin();
11059
- const postId = await promptForMissing({ value: id, message: "Post ID" });
11060
- await runDelete({
11061
- global: this.optsWithGlobals(),
11062
- fn: () => deletePost(uid, postId),
11063
- successMessage: ` ${import_picocolors10.default.green("Deleted!")} Post ${import_picocolors10.default.dim(postId)}`,
11064
- spinnerMessage: "Deleting post..."
11065
- });
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..."
11066
9794
  });
11067
- posts.command("comment [postId]").description("Add a comment to a post").option("--text <text>").action(async function(postId) {
11068
- const opts = this.optsWithGlobals();
11069
- const uid = requireAdmin();
11070
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11071
- const text = await promptForMissing({ value: opts.text, message: "Comment text", placeholder: "Your comment" });
11072
- await runCreate({
11073
- global: opts,
11074
- fn: () => createComment(uid, resolvedPostId, text),
11075
- dataKey: "comment",
11076
- spinnerMessage: "Adding comment...",
11077
- onInteractive: (comment) => {
11078
- console.log(`
11079
- ${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)}
11080
9809
  `);
11081
- }
11082
- });
9810
+ }
11083
9811
  });
11084
- posts.command("comment-delete [postId] [commentId]").description("Delete a comment").action(async function(postId, commentId) {
11085
- const uid = requireAdmin();
11086
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11087
- const resolvedCommentId = await pickComment(resolvedPostId, commentId);
11088
- await runDelete({
11089
- global: this.optsWithGlobals(),
11090
- fn: () => deleteComment(uid, resolvedPostId, resolvedCommentId),
11091
- successMessage: ` ${import_picocolors10.default.green("Deleted!")} Comment ${import_picocolors10.default.dim(resolvedCommentId)}`,
11092
- spinnerMessage: "Deleting comment..."
11093
- });
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..."
11094
9822
  });
11095
- posts.command("reply [postId] [commentId]").description("Add a reply to a comment").option("--text <text>").action(async function(postId, commentId) {
11096
- const opts = this.optsWithGlobals();
11097
- const uid = requireAdmin();
11098
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11099
- const resolvedCommentId = await pickComment(resolvedPostId, commentId);
11100
- const text = await promptForMissing({ value: opts.text, message: "Reply text", placeholder: "Your reply" });
11101
- await runCreate({
11102
- global: opts,
11103
- fn: () => createReply(uid, resolvedPostId, resolvedCommentId, text),
11104
- dataKey: "reply",
11105
- spinnerMessage: "Adding reply...",
11106
- onInteractive: (reply) => {
11107
- console.log(`
11108
- ${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)}
11109
9838
  `);
11110
- }
11111
- });
9839
+ }
11112
9840
  });
11113
- posts.command("reply-delete [postId] [commentId] [replyId]").description("Delete a reply").action(async function(postId, commentId, replyId) {
11114
- const uid = requireAdmin();
11115
- const resolvedPostId = await promptForMissing({ value: postId, message: "Post ID" });
11116
- const resolvedCommentId = await pickComment(resolvedPostId, commentId);
11117
- const resolvedReplyId = await pickReply(resolvedPostId, resolvedCommentId, replyId);
11118
- await runDelete({
11119
- global: this.optsWithGlobals(),
11120
- fn: () => deleteReply(uid, resolvedPostId, resolvedCommentId, resolvedReplyId),
11121
- successMessage: ` ${import_picocolors10.default.green("Deleted!")} Reply ${import_picocolors10.default.dim(resolvedReplyId)}`,
11122
- spinnerMessage: "Deleting reply..."
11123
- });
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..."
11124
9852
  });
11125
- }
9853
+ });
11126
9854
  }
11127
9855
 
11128
- // src/cli/commands/profile.ts
11129
- init_define_ADMIN_UIDS();
11130
-
11131
9856
  // src/cli/services/profile.ts
11132
- init_define_ADMIN_UIDS();
11133
9857
  async function getProfile() {
11134
- const creds = loadCredentials();
11135
- if (!creds) throw new Error("Not logged in. Run: numo login");
11136
- const doc = await getDoc(`users/${creds.uid}`);
11137
- return {
11138
- uid: creds.uid,
11139
- email: creds.email ?? null,
11140
- username: doc.username ?? null,
11141
- photoURL: doc.photoURL ?? null
11142
- };
9858
+ return api.get("/api/profile");
11143
9859
  }
11144
9860
 
11145
9861
  // src/cli/commands/profile.ts
@@ -11163,11 +9879,9 @@ function registerProfileCommands(program3) {
11163
9879
  }
11164
9880
 
11165
9881
  // src/cli/commands/doctor.ts
11166
- init_define_ADMIN_UIDS();
11167
9882
  var import_picocolors11 = __toESM(require_picocolors(), 1);
11168
- init_config();
11169
- init_config();
11170
9883
  init_tty();
9884
+ var API_BASE5 = process.env.NUMO_API_URL ?? "http://localhost:3000";
11171
9885
  async function runChecks() {
11172
9886
  const checks = [];
11173
9887
  const nodeVersion = process.version;
@@ -11177,6 +9891,11 @@ async function runChecks() {
11177
9891
  status: major >= 18 ? "ok" : "fail",
11178
9892
  message: major >= 18 ? `Node ${nodeVersion}` : `Node ${nodeVersion} \u2014 requires >= 18`
11179
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
+ });
11180
9899
  const creds = loadCredentials();
11181
9900
  checks.push({
11182
9901
  name: "credentials",
@@ -11193,18 +9912,11 @@ async function runChecks() {
11193
9912
  } else {
11194
9913
  checks.push({ name: "token", status: "fail", message: "Skipped (no credentials)" });
11195
9914
  }
11196
- const apiKey = getFirebaseApiKey();
11197
- checks.push({
11198
- name: "api_key",
11199
- status: apiKey ? "ok" : "fail",
11200
- message: apiKey ? "Firebase API key configured" : "NUMO_FIREBASE_API_KEY not set"
11201
- });
11202
9915
  try {
11203
- const baseUrl = getFirestoreBaseUrl();
11204
- const resp = await fetch(baseUrl, { signal: AbortSignal.timeout(5e3) });
11205
- 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})` });
11206
9918
  } catch (err) {
11207
- 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}` });
11208
9920
  }
11209
9921
  return checks;
11210
9922
  }
@@ -11235,19 +9947,18 @@ function registerDoctorCommand(program3) {
11235
9947
  }
11236
9948
 
11237
9949
  // src/cli/lib/update-check.ts
11238
- init_define_ADMIN_UIDS();
11239
- var fs5 = __toESM(require("fs"), 1);
11240
- var path3 = __toESM(require("path"), 1);
9950
+ var fs4 = __toESM(require("fs"), 1);
9951
+ var path2 = __toESM(require("path"), 1);
11241
9952
  var import_picocolors12 = __toESM(require_picocolors(), 1);
11242
9953
  init_tty();
11243
9954
  var CHECK_INTERVAL = 24 * 60 * 60 * 1e3;
11244
9955
  var PACKAGE_NAME = "numo-cli";
11245
9956
  function getStatePath() {
11246
- return path3.join(getConfigDir(), "update-check.json");
9957
+ return path2.join(getConfigDir(), "update-check.json");
11247
9958
  }
11248
9959
  function loadState() {
11249
9960
  try {
11250
- return JSON.parse(fs5.readFileSync(getStatePath(), "utf8"));
9961
+ return JSON.parse(fs4.readFileSync(getStatePath(), "utf8"));
11251
9962
  } catch {
11252
9963
  return { lastCheck: 0 };
11253
9964
  }
@@ -11255,7 +9966,7 @@ function loadState() {
11255
9966
  function saveState(state) {
11256
9967
  try {
11257
9968
  ensureConfigDir();
11258
- fs5.writeFileSync(getStatePath(), JSON.stringify(state), { mode: 384 });
9969
+ fs4.writeFileSync(getStatePath(), JSON.stringify(state), { mode: 384 });
11259
9970
  } catch {
11260
9971
  }
11261
9972
  }
@@ -11304,7 +10015,7 @@ function fetchLatestVersion(state) {
11304
10015
  // src/cli/cli.ts
11305
10016
  init_tty();
11306
10017
  init_errors();
11307
- var CLI_VERSION = true ? "1.1.0" : "0.0.0-dev";
10018
+ var CLI_VERSION = true ? "1.3.0" : "0.0.0-dev";
11308
10019
  var program2 = new Command();
11309
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) => {
11310
10021
  const opts = thisCommand.optsWithGlobals();
@@ -11326,7 +10037,7 @@ program2.command("login").description("Login with your Numo account").option("--
11326
10037
  Examples:
11327
10038
  $ numo login # Interactive (email/password)
11328
10039
  $ numo login --phone # SMS OTP flow`);
11329
- 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) => {
11330
10041
  await register(opts);
11331
10042
  }).addHelpText("after", `
11332
10043
  Examples:
@@ -11334,7 +10045,6 @@ Examples:
11334
10045
  $ numo register --email user@example.com --password s3cret # Non-interactive`);
11335
10046
  program2.command("logout").description("Clear stored credentials").action(() => {
11336
10047
  clearCredentials();
11337
- clearStreaks();
11338
10048
  console.log(import_picocolors13.default.green("Logged out."));
11339
10049
  });
11340
10050
  program2.command("whoami").description("Show current auth status (no API call)").action(function() {
@@ -11366,7 +10076,7 @@ program2.command("whoami").description("Show current auth status (no API call)")
11366
10076
  }
11367
10077
  });
11368
10078
  registerTasksCommands(program2);
11369
- program2.command("add [text...]").description("Quick-add a task (today, public, no wizard)").option("--due <date>", "Due date YYYY-MM-DD (default: today)").option("--tags <tags>", "Comma-separated tags").option("--private", "Make task private").action(async function(textParts) {
10079
+ program2.command("add [text...]").description("Quick-add a task (today, public, no wizard)").option("--due <date>", "Due date YYYY-MM-DD (default: today)").option("--tags <tags>", "Comma-separated tags").option("--public", "Make task public (default)").option("--private", "Make task private").action(async function(textParts) {
11370
10080
  const opts = this.optsWithGlobals();
11371
10081
  const uid = requireUid();
11372
10082
  const text = textParts?.join(" ");
@@ -11379,6 +10089,7 @@ program2.command("add [text...]").description("Quick-add a task (today, public,
11379
10089
  dueDate: opts.due ? parseHumanDate(opts.due) ?? opts.due : (/* @__PURE__ */ new Date()).toISOString().slice(0, 10)
11380
10090
  };
11381
10091
  if (opts.tags) body.tags = opts.tags.split(",");
10092
+ if (opts.public) body.isPublic = true;
11382
10093
  if (opts.private) body.isPublic = false;
11383
10094
  await runCreate({
11384
10095
  global: opts,
@@ -11386,17 +10097,16 @@ program2.command("add [text...]").description("Quick-add a task (today, public,
11386
10097
  dataKey: "task",
11387
10098
  spinnerMessage: "Creating task...",
11388
10099
  onInteractive: (_task, payload) => {
11389
- const task = payload.task;
10100
+ const { task, karma } = payload;
11390
10101
  const check = import_picocolors13.default.green(SYM.check);
11391
10102
  console.log(`
11392
10103
  ${check} Created ${task.text} ${import_picocolors13.default.dim(task.id)}`);
11393
- const karma = payload.karma;
11394
10104
  if (karma) console.log(` ${formatKarmaGain(karma)}`);
11395
10105
  console.log("");
11396
10106
  }
11397
10107
  });
11398
10108
  });
11399
- if (isAdmin()) registerPostsCommands(program2);
10109
+ registerPostsCommands(program2);
11400
10110
  registerProfileCommands(program2);
11401
10111
  registerDoctorCommand(program2);
11402
10112
  program2.command("commands").description("List all available commands").action(function() {