@hasna/todos 0.15.49 → 0.15.51

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.
@@ -323,23 +323,118 @@ var init_types = __esm(() => {
323
323
  };
324
324
  });
325
325
 
326
- // src/lib/sync-utils.ts
327
- import { existsSync, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
328
- import { createHash } from "crypto";
326
+ // node_modules/.bun/@hasna+paths@0.1.0/node_modules/@hasna/paths/dist/index.js
329
327
  import { homedir } from "os";
330
328
  import { join } from "path";
329
+ function assertApp(app) {
330
+ if (typeof app !== "string" || app.length === 0) {
331
+ throw new TypeError("paths: app must be a non-empty string");
332
+ }
333
+ if (!APP_SLUG_RE.test(app)) {
334
+ throw new TypeError(`paths: invalid app slug "${app}" \u2014 expected lowercase kebab-case ([a-z0-9]+(-[a-z0-9]+)*)`);
335
+ }
336
+ }
337
+ function envOf(options) {
338
+ return options.env ?? process.env;
339
+ }
340
+ function envValue(options, kind) {
341
+ const value = envOf(options)[KIND_ENV[kind]];
342
+ return typeof value === "string" && value.length > 0 ? value : undefined;
343
+ }
344
+ function isMacOS(platform) {
345
+ return platform === "darwin";
346
+ }
347
+ function baseDir(kind, options) {
348
+ const override = envValue(options, kind);
349
+ if (override)
350
+ return override;
351
+ const home = options.home ?? homedir();
352
+ const platform = options.platform ?? process.platform;
353
+ if (isMacOS(platform)) {
354
+ switch (kind) {
355
+ case "config":
356
+ case "data":
357
+ return join(home, "Library", "Application Support", "Hasna");
358
+ case "cache":
359
+ return join(home, "Library", "Caches", "Hasna");
360
+ case "state":
361
+ return join(home, "Library", "Logs", "Hasna");
362
+ }
363
+ }
364
+ switch (kind) {
365
+ case "config":
366
+ return join(home, ".config", "hasna");
367
+ case "data":
368
+ return join(home, ".local", "share", "hasna");
369
+ case "state":
370
+ return join(home, ".local", "state", "hasna");
371
+ case "cache":
372
+ return join(home, ".cache", "hasna");
373
+ }
374
+ }
375
+ function resolvePath(kind, options) {
376
+ assertApp(options.app);
377
+ const appSegment = options.internal === true ? join("internal", options.app) : options.app;
378
+ return join(baseDir(kind, options), appSegment);
379
+ }
380
+ function dataDir(options) {
381
+ return resolvePath("data", options);
382
+ }
383
+ var KIND_ENV, APP_SLUG_RE;
384
+ var init_dist = __esm(() => {
385
+ KIND_ENV = {
386
+ config: "HASNA_CONFIG_HOME",
387
+ data: "HASNA_DATA_HOME",
388
+ state: "HASNA_STATE_HOME",
389
+ cache: "HASNA_CACHE_HOME"
390
+ };
391
+ APP_SLUG_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
392
+ });
393
+
394
+ // src/lib/paths.ts
395
+ import { existsSync } from "fs";
396
+ import { homedir as homedir2 } from "os";
397
+ import { join as join2, resolve } from "path";
398
+ function effectiveHome(env = process.env) {
399
+ return env.HOME || env.USERPROFILE || homedir2();
400
+ }
401
+ function legacyHomeDir(env = process.env) {
402
+ return join2(effectiveHome(env), ".hasna", "todos");
403
+ }
404
+ function resolverHome(env = process.env) {
405
+ return dataDir({ app: "todos", home: effectiveHome(env), env });
406
+ }
407
+ function adoptResolverHome(resolved, env = process.env) {
408
+ const dataOverride = env.HASNA_DATA_HOME;
409
+ if (typeof dataOverride === "string" && dataOverride.trim().length > 0)
410
+ return true;
411
+ return existsSync(join2(resolved, "todos.db")) || existsSync(join2(resolved, "config.json"));
412
+ }
413
+ function getTodosDir(env = process.env) {
414
+ const resolved = resolverHome(env);
415
+ return resolve(adoptResolverHome(resolved, env) ? resolved : legacyHomeDir(env));
416
+ }
417
+ var init_paths = __esm(() => {
418
+ init_dist();
419
+ });
420
+
421
+ // src/lib/sync-utils.ts
422
+ import { existsSync as existsSync2, mkdirSync, readFileSync, readdirSync, statSync, writeFileSync } from "fs";
423
+ import { createHash } from "crypto";
424
+ import { homedir as homedir3 } from "os";
425
+ import { join as join3 } from "path";
331
426
  function getHomeDir() {
332
- return process.env["HOME"] || process.env["USERPROFILE"] || homedir();
427
+ return process.env["HOME"] || process.env["USERPROFILE"] || homedir3();
333
428
  }
334
429
  function getTodosGlobalDir() {
335
- return join(getHomeDir(), ".hasna", "todos");
430
+ return getTodosDir();
336
431
  }
337
432
  function ensureDir(dir) {
338
- if (!existsSync(dir))
433
+ if (!existsSync2(dir))
339
434
  mkdirSync(dir, { recursive: true });
340
435
  }
341
436
  function listJsonFiles(dir) {
342
- if (!existsSync(dir))
437
+ if (!existsSync2(dir))
343
438
  return [];
344
439
  return readdirSync(dir).filter((f) => f.endsWith(".json"));
345
440
  }
@@ -355,14 +450,14 @@ function writeJsonFile(path, data) {
355
450
  `);
356
451
  }
357
452
  function readHighWaterMark(dir) {
358
- const path = join(dir, ".highwatermark");
359
- if (!existsSync(path))
453
+ const path = join3(dir, ".highwatermark");
454
+ if (!existsSync2(path))
360
455
  return 1;
361
456
  const val = parseInt(readFileSync(path, "utf-8").trim(), 10);
362
457
  return isNaN(val) ? 1 : val;
363
458
  }
364
459
  function writeHighWaterMark(dir, value) {
365
- writeFileSync(join(dir, ".highwatermark"), String(value));
460
+ writeFileSync(join3(dir, ".highwatermark"), String(value));
366
461
  }
367
462
  function getFileMtimeMs(path) {
368
463
  try {
@@ -417,6 +512,7 @@ function hasSyncFingerprintChanged(record) {
417
512
  }
418
513
  var TODO_SYNC_FINGERPRINT_KEY = "todos_sync_fingerprint", HOME;
419
514
  var init_sync_utils = __esm(() => {
515
+ init_paths();
420
516
  HOME = getHomeDir();
421
517
  });
422
518
 
@@ -429,10 +525,10 @@ var init_creator_identity = __esm(() => {
429
525
  });
430
526
 
431
527
  // src/lib/config.ts
432
- import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
433
- import { dirname, join as join2 } from "path";
528
+ import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
529
+ import { dirname, join as join4 } from "path";
434
530
  function getConfigPath() {
435
- return join2(getTodosGlobalDir(), "config.json");
531
+ return join4(getTodosGlobalDir(), "config.json");
436
532
  }
437
533
  function normalizeAgent(agent) {
438
534
  return agent.trim().toLowerCase();
@@ -440,7 +536,7 @@ function normalizeAgent(agent) {
440
536
  function loadConfig() {
441
537
  if (cached)
442
538
  return cached;
443
- if (!existsSync2(getConfigPath())) {
539
+ if (!existsSync3(getConfigPath())) {
444
540
  cached = {};
445
541
  return cached;
446
542
  }
@@ -463,7 +559,7 @@ function updateConfig(patch) {
463
559
  }
464
560
  function getTodosAiConfig() {
465
561
  const configPath = getConfigPath();
466
- if (!existsSync2(configPath))
562
+ if (!existsSync3(configPath))
467
563
  return {};
468
564
  const parsed = JSON.parse(readFileSync2(configPath, "utf8"));
469
565
  if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
@@ -653,7 +749,7 @@ var init_redaction = __esm(() => {
653
749
  });
654
750
 
655
751
  // src/lib/secret-redaction.ts
656
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
752
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "fs";
657
753
  function registerCustomRedactor(fn) {
658
754
  customRedactors.push(fn);
659
755
  }
@@ -718,7 +814,7 @@ function scanAndRedactText(text, options = {}) {
718
814
  };
719
815
  }
720
816
  function scanFileForSecrets(path, options = {}) {
721
- if (!existsSync3(path))
817
+ if (!existsSync4(path))
722
818
  throw new Error(`File not found: ${path}`);
723
819
  const content = readFileSync3(path, "utf8");
724
820
  return scanAndRedactText(content, options);
@@ -4400,9 +4496,9 @@ var init_schema = __esm(() => {
4400
4496
  });
4401
4497
 
4402
4498
  // src/db/machines.ts
4403
- import { existsSync as existsSync4 } from "fs";
4499
+ import { existsSync as existsSync5 } from "fs";
4404
4500
  import { hostname as osHostname, platform as osPlatform, arch as osArch } from "os";
4405
- import { resolve } from "path";
4501
+ import { resolve as resolve2 } from "path";
4406
4502
  import { spawnSync } from "child_process";
4407
4503
  function parseMetadata(value) {
4408
4504
  if (!value)
@@ -4433,7 +4529,7 @@ function discoverGitRoot(workspacePath) {
4433
4529
  }
4434
4530
  function topologyMetadata(input, existing = {}) {
4435
4531
  const next = { ...existing };
4436
- const workspacePath = input.workspace_path ? resolve(input.workspace_path) : undefined;
4532
+ const workspacePath = input.workspace_path ? resolve2(input.workspace_path) : undefined;
4437
4533
  const entries = {
4438
4534
  tailscale_name: input.tailscale_name,
4439
4535
  tailscale_ip: input.tailscale_ip,
@@ -4610,7 +4706,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
4610
4706
  message: `${project.name} has ${distinctPaths.length} different machine-local paths`
4611
4707
  });
4612
4708
  }
4613
- if (localRow && !existsSync4(localRow.path)) {
4709
+ if (localRow && !existsSync5(localRow.path)) {
4614
4710
  pathIssues.push({
4615
4711
  type: "path_missing",
4616
4712
  project_id: project.id,
@@ -4621,7 +4717,7 @@ function getMachineTopologyDiagnostics(opts = {}, db, at = new Date) {
4621
4717
  message: `Local path does not exist on this machine: ${localRow.path}`
4622
4718
  });
4623
4719
  }
4624
- if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync4(project.path)) {
4720
+ if (!localRow && project.path && machineById.has(localMachine.id) && !existsSync5(project.path)) {
4625
4721
  pathIssues.push({
4626
4722
  type: "path_missing",
4627
4723
  project_id: project.id,
@@ -5187,18 +5283,18 @@ __export(exports_database, {
5187
5283
  LOCK_EXPIRY_MINUTES: () => LOCK_EXPIRY_MINUTES
5188
5284
  });
5189
5285
  import { Database } from "bun:sqlite";
5190
- import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
5191
- import { dirname as dirname2, join as join3, resolve as resolve2 } from "path";
5286
+ import { existsSync as existsSync6, mkdirSync as mkdirSync2 } from "fs";
5287
+ import { dirname as dirname2, join as join5, resolve as resolve3 } from "path";
5192
5288
  function isInMemoryDb(path) {
5193
5289
  return path === ":memory:" || path.startsWith("file::memory:");
5194
5290
  }
5195
5291
  function findNearestProjectDb(startDir) {
5196
5292
  const gitRoot = findGitRoot(startDir);
5197
- const stopAt = gitRoot ? resolve2(gitRoot) : resolve2(startDir);
5198
- let dir = resolve2(startDir);
5293
+ const stopAt = gitRoot ? resolve3(gitRoot) : resolve3(startDir);
5294
+ let dir = resolve3(startDir);
5199
5295
  while (true) {
5200
- const candidate = join3(dir, ".hasna", "todos", "todos.db");
5201
- if (existsSync5(candidate))
5296
+ const candidate = join5(dir, ".hasna", "todos", "todos.db");
5297
+ if (existsSync6(candidate))
5202
5298
  return candidate;
5203
5299
  if (dir === stopAt)
5204
5300
  break;
@@ -5210,9 +5306,9 @@ function findNearestProjectDb(startDir) {
5210
5306
  return null;
5211
5307
  }
5212
5308
  function findGitRoot(startDir) {
5213
- let dir = resolve2(startDir);
5309
+ let dir = resolve3(startDir);
5214
5310
  while (true) {
5215
- if (existsSync5(join3(dir, ".git")))
5311
+ if (existsSync6(join5(dir, ".git")))
5216
5312
  return dir;
5217
5313
  const parent = dirname2(dir);
5218
5314
  if (parent === dir)
@@ -5222,7 +5318,7 @@ function findGitRoot(startDir) {
5222
5318
  return null;
5223
5319
  }
5224
5320
  function getGlobalDbPath() {
5225
- return join3(getHomeDir(), ".hasna", "todos", "todos.db");
5321
+ return join5(getTodosGlobalDir(), "todos.db");
5226
5322
  }
5227
5323
  function hasExplicitProjectArg(args = process.argv.slice(2)) {
5228
5324
  return args.some((arg) => arg === "--project" || arg.startsWith("--project="));
@@ -5260,7 +5356,7 @@ function getDbPath() {
5260
5356
  if (process.env["TODOS_DB_SCOPE"] === "project") {
5261
5357
  const gitRoot = findGitRoot(cwd);
5262
5358
  if (gitRoot && canCreateScopedProjectDb()) {
5263
- return join3(gitRoot, ".hasna", "todos", "todos.db");
5359
+ return join5(gitRoot, ".hasna", "todos", "todos.db");
5264
5360
  }
5265
5361
  }
5266
5362
  return getGlobalDbPath();
@@ -5271,8 +5367,8 @@ function getDatabasePath() {
5271
5367
  function ensureDir2(filePath) {
5272
5368
  if (isInMemoryDb(filePath))
5273
5369
  return;
5274
- const dir = dirname2(resolve2(filePath));
5275
- if (!existsSync5(dir)) {
5370
+ const dir = dirname2(resolve3(filePath));
5371
+ if (!existsSync6(dir)) {
5276
5372
  mkdirSync2(dir, { recursive: true });
5277
5373
  }
5278
5374
  }
@@ -5924,14 +6020,14 @@ var init_completion_guard = __esm(() => {
5924
6020
 
5925
6021
  // src/lib/event-emission-safety.ts
5926
6022
  import { tmpdir } from "os";
5927
- import { resolve as resolve3, sep } from "path";
6023
+ import { resolve as resolve4, sep } from "path";
5928
6024
  function envFlag(name) {
5929
6025
  const value = process.env[name]?.trim().toLowerCase();
5930
6026
  return value === "1" || value === "true" || value === "yes" || value === "on";
5931
6027
  }
5932
6028
  function isUnder(parent, child) {
5933
- const normalizedParent = resolve3(parent);
5934
- const normalizedChild = resolve3(child);
6029
+ const normalizedParent = resolve4(parent);
6030
+ const normalizedChild = resolve4(child);
5935
6031
  return normalizedChild === normalizedParent || normalizedChild.startsWith(`${normalizedParent}${sep}`);
5936
6032
  }
5937
6033
  function databasePathFromDatabase(db) {
@@ -5974,9 +6070,9 @@ var init_event_emission_safety = __esm(() => {
5974
6070
  });
5975
6071
 
5976
6072
  // src/lib/workspace-trust.ts
5977
- import { relative, resolve as resolve4 } from "path";
6073
+ import { relative, resolve as resolve5 } from "path";
5978
6074
  function normalizePath(path) {
5979
- return resolve4(path);
6075
+ return resolve5(path);
5980
6076
  }
5981
6077
  function unique2(values) {
5982
6078
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -6158,9 +6254,9 @@ var init_workspace_trust = __esm(() => {
6158
6254
  });
6159
6255
 
6160
6256
  // src/lib/runner-sandbox.ts
6161
- import { relative as relative2, resolve as resolve5 } from "path";
6257
+ import { relative as relative2, resolve as resolve6 } from "path";
6162
6258
  function normalizePath2(path) {
6163
- return resolve5(path);
6259
+ return resolve6(path);
6164
6260
  }
6165
6261
  function unique3(values) {
6166
6262
  return Array.from(new Set((values || []).map((value) => value.trim()).filter(Boolean)));
@@ -6353,7 +6449,7 @@ var init_runner_sandbox = __esm(() => {
6353
6449
  // src/lib/event-hooks.ts
6354
6450
  import { createHash as createHash4, randomUUID as randomUUID2 } from "crypto";
6355
6451
  import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
6356
- import { dirname as dirname3, resolve as resolve6 } from "path";
6452
+ import { dirname as dirname3, resolve as resolve7 } from "path";
6357
6453
  import { createConnection } from "net";
6358
6454
  function safeName(name) {
6359
6455
  const trimmed = name.trim();
@@ -6491,7 +6587,7 @@ async function deliverHook(hook, envelope) {
6491
6587
  if (hook.target === "stdout") {
6492
6588
  output = line.trim();
6493
6589
  } else if (hook.target === "file") {
6494
- const filePath = resolve6(hook.file_path);
6590
+ const filePath = resolve7(hook.file_path);
6495
6591
  mkdirSync3(dirname3(filePath), { recursive: true });
6496
6592
  appendFileSync(filePath, line);
6497
6593
  } else if (hook.target === "socket") {
@@ -6612,9 +6708,9 @@ var init_event_hooks = __esm(() => {
6612
6708
  // node_modules/.bun/@hasna+events@0.1.16/node_modules/@hasna/events/dist/index.js
6613
6709
  import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
6614
6710
  import { Buffer as Buffer2 } from "buffer";
6615
- import { existsSync as existsSync6 } from "fs";
6616
- import { homedir as homedir2 } from "os";
6617
- import { join as join4 } from "path";
6711
+ import { existsSync as existsSync7 } from "fs";
6712
+ import { homedir as homedir4 } from "os";
6713
+ import { join as join6 } from "path";
6618
6714
  import { createHmac, timingSafeEqual } from "crypto";
6619
6715
  import { randomUUID as randomUUID3 } from "crypto";
6620
6716
  import { spawn } from "child_process";
@@ -6715,7 +6811,7 @@ function channelMatchesEvent(channel, event) {
6715
6811
  return channel.filters.some((filter) => eventMatchesFilter(event, filter));
6716
6812
  }
6717
6813
  function getEventsDataDir(override) {
6718
- return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join4(homedir2(), ".hasna", "events");
6814
+ return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join6(homedir4(), ".hasna", "events");
6719
6815
  }
6720
6816
 
6721
6817
  class JsonEventsStore {
@@ -6724,12 +6820,12 @@ class JsonEventsStore {
6724
6820
  channelsPath;
6725
6821
  eventsPath;
6726
6822
  deliveriesPath;
6727
- constructor(dataDir = getEventsDataDir()) {
6728
- this.dataDir = dataDir;
6729
- this.runtime = localJsonRuntime(dataDir);
6730
- this.channelsPath = join4(dataDir, "channels.json");
6731
- this.eventsPath = join4(dataDir, "events.json");
6732
- this.deliveriesPath = join4(dataDir, "deliveries.json");
6823
+ constructor(dataDir2 = getEventsDataDir()) {
6824
+ this.dataDir = dataDir2;
6825
+ this.runtime = localJsonRuntime(dataDir2);
6826
+ this.channelsPath = join6(dataDir2, "channels.json");
6827
+ this.eventsPath = join6(dataDir2, "events.json");
6828
+ this.deliveriesPath = join6(dataDir2, "deliveries.json");
6733
6829
  }
6734
6830
  async init() {
6735
6831
  await mkdir(this.dataDir, { recursive: true, mode: 448 });
@@ -6846,7 +6942,7 @@ class JsonEventsStore {
6846
6942
  };
6847
6943
  }
6848
6944
  async ensureArrayFile(path) {
6849
- if (!existsSync6(path)) {
6945
+ if (!existsSync7(path)) {
6850
6946
  await writeFile(path, `[]
6851
6947
  `, { encoding: "utf-8", mode: 384 });
6852
6948
  }
@@ -6876,7 +6972,7 @@ class JsonEventsStore {
6876
6972
  });
6877
6973
  }
6878
6974
  }
6879
- function localJsonRuntime(dataDir = getEventsDataDir()) {
6975
+ function localJsonRuntime(dataDir2 = getEventsDataDir()) {
6880
6976
  return {
6881
6977
  mode: "local-files",
6882
6978
  name: "json-events-store",
@@ -6889,7 +6985,7 @@ function localJsonRuntime(dataDir = getEventsDataDir()) {
6889
6985
  durable: true,
6890
6986
  idempotency: "best-effort-local",
6891
6987
  replayCursors: true,
6892
- description: `Local JSON files in ${dataDir}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
6988
+ description: `Local JSON files in ${dataDir2}; no SQLite, Postgres, S3, or AWS runtime is configured by this store.`
6893
6989
  };
6894
6990
  }
6895
6991
  function encodeLocalJsonEventCursor(offset, options = {}) {
@@ -7068,7 +7164,7 @@ async function dispatchCommand(event, channel) {
7068
7164
  HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
7069
7165
  HASNA_EVENT_JSON: eventJson
7070
7166
  };
7071
- return new Promise((resolve7) => {
7167
+ return new Promise((resolve8) => {
7072
7168
  const child = spawn(channel.command.command, channel.command.args ?? [], {
7073
7169
  cwd: channel.command.cwd,
7074
7170
  env,
@@ -7086,7 +7182,7 @@ async function dispatchCommand(event, channel) {
7086
7182
  });
7087
7183
  child.on("error", (error) => {
7088
7184
  clearTimeout(timeout);
7089
- resolve7({
7185
+ resolve8({
7090
7186
  attempt: 1,
7091
7187
  status: "failed",
7092
7188
  startedAt,
@@ -7099,7 +7195,7 @@ async function dispatchCommand(event, channel) {
7099
7195
  child.on("close", (code, signal) => {
7100
7196
  clearTimeout(timeout);
7101
7197
  const success = code === 0;
7102
- resolve7({
7198
+ resolve8({
7103
7199
  attempt: 1,
7104
7200
  status: success ? "success" : "failed",
7105
7201
  startedAt,
@@ -7441,7 +7537,7 @@ function normalizeRetryPolicy(policy) {
7441
7537
  };
7442
7538
  }
7443
7539
  var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR", HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME", LOCAL_JSON_EVENT_CURSOR_PREFIX = "local-json-v1:", DEFAULT_EVENT_PAGE_LIMIT = 100, MAX_EVENT_PAGE_LIMIT = 1000, DEFAULT_SIGNATURE_TOLERANCE_MS, EventValidationError, defaultEventTypeCatalog, APP_EVENT_V1_MAX_DATA_BYTES;
7444
- var init_dist = __esm(() => {
7540
+ var init_dist2 = __esm(() => {
7445
7541
  DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
7446
7542
  EventValidationError = class EventValidationError extends Error {
7447
7543
  eventType;
@@ -7864,7 +7960,7 @@ function emitSharedTaskEventQuiet(input) {
7864
7960
  }
7865
7961
  var SOURCE = "todos";
7866
7962
  var init_shared_events = __esm(() => {
7867
- init_dist();
7963
+ init_dist2();
7868
7964
  init_database();
7869
7965
  init_projects();
7870
7966
  init_task_lists();
@@ -9782,17 +9878,17 @@ function sanitizeCreateTaskInput(input) {
9782
9878
  return {
9783
9879
  ...input,
9784
9880
  title: sanitizePreWriteText(input.title, "task.title"),
9785
- description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
9881
+ description: input.description == null ? input.description : sanitizePreWriteText(input.description, "task.description"),
9786
9882
  tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
9787
9883
  metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined,
9788
- reason: input.reason !== undefined ? sanitizePreWriteText(input.reason, "task.reason") : undefined
9884
+ reason: input.reason == null ? input.reason : sanitizePreWriteText(input.reason, "task.reason")
9789
9885
  };
9790
9886
  }
9791
9887
  function sanitizeUpdateTaskInput(input) {
9792
9888
  return {
9793
9889
  ...input,
9794
9890
  title: input.title !== undefined ? sanitizePreWriteText(input.title, "task.title") : undefined,
9795
- description: input.description !== undefined ? sanitizePreWriteText(input.description, "task.description") : undefined,
9891
+ description: input.description == null ? input.description : sanitizePreWriteText(input.description, "task.description"),
9796
9892
  tags: input.tags !== undefined ? sanitizePreWriteValue(input.tags, "task.tags") : undefined,
9797
9893
  metadata: input.metadata !== undefined ? sanitizePreWriteValue(input.metadata, "task.metadata") : undefined
9798
9894
  };
@@ -11646,28 +11742,28 @@ var init_boards = __esm(() => {
11646
11742
 
11647
11743
  // src/lib/artifact-store.ts
11648
11744
  import { createHash as createHash5 } from "crypto";
11649
- import { existsSync as existsSync7, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11650
- import { basename, dirname as dirname4, join as join5, resolve as resolve7 } from "path";
11745
+ import { existsSync as existsSync8, mkdirSync as mkdirSync4, readFileSync as readFileSync4, rmSync, statSync as statSync2, writeFileSync as writeFileSync2 } from "fs";
11746
+ import { basename, dirname as dirname4, join as join7, resolve as resolve8 } from "path";
11651
11747
  import { tmpdir as tmpdir2 } from "os";
11652
11748
  function isInMemoryDb2(path) {
11653
11749
  return path === ":memory:" || path.startsWith("file::memory:");
11654
11750
  }
11655
11751
  function artifactStoreRoot() {
11656
11752
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11657
- return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11753
+ return resolve8(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11658
11754
  if (process.env["TODOS_ARTIFACTS_DIR"])
11659
- return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11755
+ return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
11660
11756
  const dbPath = getDatabasePath();
11661
11757
  if (isInMemoryDb2(dbPath))
11662
- return join5(tmpdir2(), "hasna-todos-artifacts");
11663
- return join5(dirname4(resolve7(dbPath)), "artifacts");
11758
+ return join7(tmpdir2(), "hasna-todos-artifacts");
11759
+ return join7(dirname4(resolve8(dbPath)), "artifacts");
11664
11760
  }
11665
11761
  function artifactStorePath(relativePath) {
11666
11762
  const normalized = relativePath.replace(/\\/g, "/");
11667
11763
  if (normalized.includes("..") || normalized.startsWith("/") || normalized.length === 0) {
11668
11764
  throw new Error("Invalid artifact store path");
11669
11765
  }
11670
- return join5(artifactStoreRoot(), normalized);
11766
+ return join7(artifactStoreRoot(), normalized);
11671
11767
  }
11672
11768
  function sha256(buffer) {
11673
11769
  return createHash5("sha256").update(buffer).digest("hex");
@@ -11707,8 +11803,8 @@ function mediaTypeFor(path, textLike) {
11707
11803
  return "application/octet-stream";
11708
11804
  }
11709
11805
  function storeArtifactContent(input) {
11710
- const sourcePath = resolve7(input.path);
11711
- if (!existsSync7(sourcePath))
11806
+ const sourcePath = resolve8(input.path);
11807
+ if (!existsSync8(sourcePath))
11712
11808
  return null;
11713
11809
  const sourceStat = statSync2(sourcePath);
11714
11810
  if (!sourceStat.isFile())
@@ -11725,9 +11821,9 @@ function storeArtifactContent(input) {
11725
11821
  redactionStatus = "redacted";
11726
11822
  }
11727
11823
  const storedSha = sha256(storedBuffer);
11728
- const relativePath = join5("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
11824
+ const relativePath = join7("sha256", storedSha.slice(0, 2), storedSha).replace(/\\/g, "/");
11729
11825
  const destination = artifactStorePath(relativePath);
11730
- if (!existsSync7(destination)) {
11826
+ if (!existsSync8(destination)) {
11731
11827
  mkdirSync4(dirname4(destination), { recursive: true });
11732
11828
  writeFileSync2(destination, storedBuffer);
11733
11829
  }
@@ -11787,7 +11883,7 @@ function verifyStoredArtifact(input) {
11787
11883
  };
11788
11884
  }
11789
11885
  const storedPath = artifactStorePath(store.relative_path);
11790
- if (!existsSync7(storedPath)) {
11886
+ if (!existsSync8(storedPath)) {
11791
11887
  return {
11792
11888
  id: input.id,
11793
11889
  path: input.path,
@@ -11862,20 +11958,20 @@ function importStoredArtifactContent(content) {
11862
11958
  }
11863
11959
  function getArtifactStoreRoot(dbPath) {
11864
11960
  if (process.env["HASNA_TODOS_ARTIFACTS_DIR"])
11865
- return resolve7(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11961
+ return resolve8(process.env["HASNA_TODOS_ARTIFACTS_DIR"]);
11866
11962
  if (process.env["TODOS_ARTIFACTS_DIR"])
11867
- return resolve7(process.env["TODOS_ARTIFACTS_DIR"]);
11963
+ return resolve8(process.env["TODOS_ARTIFACTS_DIR"]);
11868
11964
  const path = dbPath ?? getDatabasePath();
11869
11965
  if (isInMemoryDb2(path))
11870
- return join5(tmpdir2(), "hasna-todos-artifacts");
11871
- return join5(dirname4(resolve7(path)), "artifacts");
11966
+ return join7(tmpdir2(), "hasna-todos-artifacts");
11967
+ return join7(dirname4(resolve8(path)), "artifacts");
11872
11968
  }
11873
11969
  function computeContentHash(path) {
11874
- return sha256(readFileSync4(resolve7(path)));
11970
+ return sha256(readFileSync4(resolve8(path)));
11875
11971
  }
11876
11972
  function storeArtifactFile(input) {
11877
- const sourcePath = resolve7(input.sourcePath);
11878
- if (!existsSync7(sourcePath)) {
11973
+ const sourcePath = resolve8(input.sourcePath);
11974
+ if (!existsSync8(sourcePath)) {
11879
11975
  throw new Error(`Source file not found: ${input.sourcePath}`);
11880
11976
  }
11881
11977
  if (!statSync2(sourcePath).isFile()) {
@@ -11888,7 +11984,7 @@ function storeArtifactFile(input) {
11888
11984
  let localPath = sourcePath;
11889
11985
  if (storageMode === "copy") {
11890
11986
  const fileName = input.name && input.name.trim().length > 0 ? basename(input.name) : basename(sourcePath);
11891
- const destination = join5(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
11987
+ const destination = join7(getArtifactStoreRoot(input.dbPath), input.artifactId, fileName);
11892
11988
  mkdirSync4(dirname4(destination), { recursive: true });
11893
11989
  writeFileSync2(destination, buffer);
11894
11990
  localPath = destination;
@@ -11898,7 +11994,7 @@ function storeArtifactFile(input) {
11898
11994
  function deleteStoredArtifactFile(localPath, storageMode, _dbPath2) {
11899
11995
  if (storageMode === "reference")
11900
11996
  return false;
11901
- if (!localPath || !existsSync7(localPath))
11997
+ if (!localPath || !existsSync8(localPath))
11902
11998
  return false;
11903
11999
  rmSync(localPath, { force: true });
11904
12000
  try {
@@ -11923,7 +12019,7 @@ function buildArtifactExportManifest(artifacts, dbPath) {
11923
12019
  };
11924
12020
  }
11925
12021
  function writeArtifactExportManifest(manifest, outputPath) {
11926
- const destination = resolve7(outputPath);
12022
+ const destination = resolve8(outputPath);
11927
12023
  mkdirSync4(dirname4(destination), { recursive: true });
11928
12024
  writeFileSync2(destination, `${JSON.stringify(manifest, null, 2)}
11929
12025
  `);
@@ -13083,7 +13179,7 @@ import { createHash as createHash7 } from "crypto";
13083
13179
  // package.json
13084
13180
  var package_default = {
13085
13181
  name: "@hasna/todos",
13086
- version: "0.15.49",
13182
+ version: "0.15.51",
13087
13183
  description: "Universal task management for AI coding agents - CLI + MCP server + interactive TUI",
13088
13184
  type: "module",
13089
13185
  main: "dist/index.js",
@@ -13142,6 +13238,7 @@ var package_default = {
13142
13238
  files: [
13143
13239
  "dist",
13144
13240
  "dashboard/dist",
13241
+ "postinstall.js",
13145
13242
  "LICENSE",
13146
13243
  "README.md"
13147
13244
  ],
@@ -13168,7 +13265,7 @@ var package_default = {
13168
13265
  "test:attested-container-compatibility-vector": "bun test scripts/attested-container-compatibility-vector.test.ts",
13169
13266
  "issue:release-review": "bun run scripts/issue-npm-release-agent-review.ts",
13170
13267
  prepublishOnly: "bun run scripts/verify-public-release.ts --mode=publish",
13171
- postinstall: "mkdir -p $HOME/.hasna/todos $HOME/.hasna/todos/training 2>/dev/null || true"
13268
+ postinstall: "node postinstall.js"
13172
13269
  },
13173
13270
  keywords: [
13174
13271
  "todos",
@@ -13202,13 +13299,15 @@ var package_default = {
13202
13299
  author: "Andrei Hasna <andrei@hasna.com>",
13203
13300
  license: "Apache-2.0",
13204
13301
  dependencies: {
13205
- "@hasna/contracts": "0.14.0",
13302
+ "@hasna/contracts": "0.14.2",
13206
13303
  "@hasna/events": "^0.1.11",
13304
+ "@hasna/paths": "0.1.0",
13207
13305
  "@modelcontextprotocol/sdk": "^1.12.1",
13208
13306
  chalk: "^5.4.1",
13209
13307
  commander: "^13.1.0",
13210
13308
  ink: "^5.2.0",
13211
13309
  react: "^18.3.1",
13310
+ "signal-exit": "3.0.7",
13212
13311
  zod: "3.25.76"
13213
13312
  },
13214
13313
  overrides: {
@@ -16640,7 +16739,7 @@ async function retryOnTransientPostgresError(fn, attempts = 2, delayMs = 150) {
16640
16739
  lastError = error;
16641
16740
  if (!isTransientPostgresError(error) || attempt === attempts)
16642
16741
  throw error;
16643
- await new Promise((resolve) => setTimeout(resolve, delayMs * attempt));
16742
+ await new Promise((resolve2) => setTimeout(resolve2, delayMs * attempt));
16644
16743
  }
16645
16744
  }
16646
16745
  throw lastError;
@@ -19494,8 +19593,8 @@ class SqliteTodosProjectRegistrationBackend {
19494
19593
  async transaction(fn) {
19495
19594
  const previous = sqliteTransactionTails.get(this.db) ?? Promise.resolve();
19496
19595
  let release;
19497
- const current = new Promise((resolve8) => {
19498
- release = resolve8;
19596
+ const current = new Promise((resolve9) => {
19597
+ release = resolve9;
19499
19598
  });
19500
19599
  sqliteTransactionTails.set(this.db, current);
19501
19600
  await previous;