@staff0rd/assist 0.955.2 → 0.956.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,7 +6,7 @@ import { Command } from "commander";
6
6
  // package.json
7
7
  var package_default = {
8
8
  name: "@staff0rd/assist",
9
- version: "0.955.2",
9
+ version: "0.956.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -605,16 +605,87 @@ function mergeRawConfigs(globalRaw, projectRaw) {
605
605
  return merged;
606
606
  }
607
607
 
608
+ // src/shared/readRepoConfigCache.ts
609
+ import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
610
+ import { join as join6 } from "path";
611
+
612
+ // src/shared/loadJson.ts
613
+ import {
614
+ existsSync as existsSync3,
615
+ mkdirSync as mkdirSync2,
616
+ readFileSync as readFileSync3,
617
+ renameSync,
618
+ rmSync as rmSync2,
619
+ writeFileSync
620
+ } from "fs";
621
+ import { homedir as homedir3 } from "os";
622
+ import { join as join5 } from "path";
623
+ function getStoreDir() {
624
+ return process.env.ASSIST_STORE_DIR || join5(homedir3(), ".assist");
625
+ }
626
+ function getStorePath(filename) {
627
+ return join5(getStoreDir(), filename);
628
+ }
629
+ function loadJson(filename) {
630
+ const path108 = getStorePath(filename);
631
+ if (existsSync3(path108)) {
632
+ try {
633
+ return JSON.parse(readFileSync3(path108, "utf8"));
634
+ } catch {
635
+ return {};
636
+ }
637
+ }
638
+ return {};
639
+ }
640
+ function saveJson(filename, data) {
641
+ const dir = getStoreDir();
642
+ if (!existsSync3(dir)) {
643
+ mkdirSync2(dir, { recursive: true });
644
+ }
645
+ const path108 = getStorePath(filename);
646
+ const tmp = `${path108}.${process.pid}.tmp`;
647
+ try {
648
+ writeFileSync(tmp, JSON.stringify(data, null, 2));
649
+ renameSync(tmp, path108);
650
+ } catch (error) {
651
+ rmSync2(tmp, { force: true });
652
+ throw error;
653
+ }
654
+ }
655
+
656
+ // src/shared/readRepoConfigCache.ts
657
+ function repoConfigCachePath() {
658
+ return join6(getStoreDir(), "repo-config-cache.json");
659
+ }
660
+ function readConfigCacheFile() {
661
+ const path108 = repoConfigCachePath();
662
+ if (!existsSync4(path108)) return {};
663
+ try {
664
+ const parsed = JSON.parse(readFileSync4(path108, "utf8"));
665
+ return parsed ?? {};
666
+ } catch {
667
+ return {};
668
+ }
669
+ }
670
+ function readRepoConfigCache() {
671
+ return readConfigCacheFile().repos ?? {};
672
+ }
673
+
674
+ // src/shared/readUserConfigCache.ts
675
+ function readUserConfigCache() {
676
+ return readConfigCacheFile().user ?? {};
677
+ }
678
+
608
679
  // src/shared/resolveRepoLayer.ts
609
680
  import chalk2 from "chalk";
610
681
 
611
682
  // src/shared/findRepoRoot.ts
612
- import { existsSync as existsSync3 } from "fs";
683
+ import { existsSync as existsSync5 } from "fs";
613
684
  import path from "path";
614
685
  function findRepoRoot(dir) {
615
686
  let current = dir;
616
687
  while (current !== path.dirname(current)) {
617
- if (existsSync3(path.join(current, ".git"))) {
688
+ if (existsSync5(path.join(current, ".git"))) {
618
689
  return current;
619
690
  }
620
691
  current = path.dirname(current);
@@ -703,69 +774,6 @@ function getCachedOrigin(cwd) {
703
774
  return origin;
704
775
  }
705
776
 
706
- // src/shared/readRepoConfigCache.ts
707
- import { existsSync as existsSync5, readFileSync as readFileSync4 } from "fs";
708
- import { join as join6 } from "path";
709
-
710
- // src/shared/loadJson.ts
711
- import {
712
- existsSync as existsSync4,
713
- mkdirSync as mkdirSync2,
714
- readFileSync as readFileSync3,
715
- renameSync,
716
- rmSync as rmSync2,
717
- writeFileSync
718
- } from "fs";
719
- import { homedir as homedir3 } from "os";
720
- import { join as join5 } from "path";
721
- function getStoreDir() {
722
- return process.env.ASSIST_STORE_DIR || join5(homedir3(), ".assist");
723
- }
724
- function getStorePath(filename) {
725
- return join5(getStoreDir(), filename);
726
- }
727
- function loadJson(filename) {
728
- const path108 = getStorePath(filename);
729
- if (existsSync4(path108)) {
730
- try {
731
- return JSON.parse(readFileSync3(path108, "utf8"));
732
- } catch {
733
- return {};
734
- }
735
- }
736
- return {};
737
- }
738
- function saveJson(filename, data) {
739
- const dir = getStoreDir();
740
- if (!existsSync4(dir)) {
741
- mkdirSync2(dir, { recursive: true });
742
- }
743
- const path108 = getStorePath(filename);
744
- const tmp = `${path108}.${process.pid}.tmp`;
745
- try {
746
- writeFileSync(tmp, JSON.stringify(data, null, 2));
747
- renameSync(tmp, path108);
748
- } catch (error) {
749
- rmSync2(tmp, { force: true });
750
- throw error;
751
- }
752
- }
753
-
754
- // src/shared/readRepoConfigCache.ts
755
- function repoConfigCachePath() {
756
- return join6(getStoreDir(), "repo-config-cache.json");
757
- }
758
- function readRepoConfigCache() {
759
- const path108 = repoConfigCachePath();
760
- if (!existsSync5(path108)) return {};
761
- try {
762
- const parsed = JSON.parse(readFileSync4(path108, "utf8"));
763
- return parsed?.repos ?? {};
764
- } catch {
765
- return {};
766
- }
767
- }
768
-
769
777
  // src/commands/backlog/originDisplayName.ts
770
778
  function originDisplayName(origin) {
771
779
  if (origin.startsWith("local:")) {
@@ -1444,7 +1452,7 @@ function loadConfigFrom(cwd, globalConfigPath = getGlobalConfigPath()) {
1444
1452
  const globalRaw = loadRawYaml(globalConfigPath);
1445
1453
  const projectRaw = loadRawYaml(projectConfigPathFrom(cwd));
1446
1454
  const globalWithRepo = mergeRawConfigs(
1447
- globalRaw,
1455
+ mergeRawConfigs(readUserConfigCache(), globalRaw),
1448
1456
  resolveRepoLayer(globalRaw, cwd).override
1449
1457
  );
1450
1458
  const merged = stripLegacyConfigKeys(
@@ -1995,7 +2003,7 @@ function expandTilde(value) {
1995
2003
  import { asc, eq } from "drizzle-orm";
1996
2004
 
1997
2005
  // src/shared/db/schema.ts
1998
- import { integer as integer13, pgTable as pgTable15, text as text15 } from "drizzle-orm/pg-core";
2006
+ import { integer as integer13, pgTable as pgTable16, text as text15 } from "drizzle-orm/pg-core";
1999
2007
 
2000
2008
  // src/shared/db/backups.ts
2001
2009
  import { bigint, integer, pgTable, text, timestamp } from "drizzle-orm/pg-core";
@@ -2251,12 +2259,20 @@ var usagePeaks = pgTable14(
2251
2259
  (t) => [primaryKey10({ columns: [t.window, t.resetsAt, t.segment] })]
2252
2260
  );
2253
2261
 
2262
+ // src/shared/db/userConfig.ts
2263
+ import { jsonb as jsonb2, pgTable as pgTable15, smallint, timestamp as timestamp7 } from "drizzle-orm/pg-core";
2264
+ var userConfig = pgTable15("user_config", {
2265
+ id: smallint().primaryKey().default(1),
2266
+ config: jsonb2().$type().notNull(),
2267
+ updatedAt: timestamp7("updated_at", { withTimezone: true }).notNull().defaultNow()
2268
+ });
2269
+
2254
2270
  // src/shared/db/schema.ts
2255
- var metadata = pgTable15("metadata", {
2271
+ var metadata = pgTable16("metadata", {
2256
2272
  key: text15().primaryKey(),
2257
2273
  value: text15().notNull()
2258
2274
  });
2259
- var feeds = pgTable15("feeds", {
2275
+ var feeds = pgTable16("feeds", {
2260
2276
  id: integer13().generatedByDefaultAsIdentity().primaryKey(),
2261
2277
  url: text15().notNull().unique()
2262
2278
  });
@@ -2276,7 +2292,8 @@ var schema = {
2276
2292
  phaseCycleContext,
2277
2293
  phaseSessions,
2278
2294
  repoConfigs,
2279
- inboxMessages
2295
+ inboxMessages,
2296
+ userConfig
2280
2297
  };
2281
2298
 
2282
2299
  // src/shared/db/planPhaseCounts.ts
@@ -3366,6 +3383,20 @@ var migration0009InboxMessages = {
3366
3383
  sql: sql12
3367
3384
  };
3368
3385
 
3386
+ // src/shared/db/migrations/migration0010UserConfig.ts
3387
+ var sql13 = `
3388
+ CREATE TABLE IF NOT EXISTS user_config (
3389
+ id SMALLINT PRIMARY KEY DEFAULT 1 CHECK (id = 1),
3390
+ config JSONB NOT NULL,
3391
+ updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
3392
+ );
3393
+ `;
3394
+ var migration0010UserConfig = {
3395
+ id: 10,
3396
+ name: "user-config",
3397
+ sql: sql13
3398
+ };
3399
+
3369
3400
  // src/shared/db/migrations.ts
3370
3401
  var migrations = [
3371
3402
  migration0001Baseline,
@@ -3376,7 +3407,8 @@ var migrations = [
3376
3407
  migration0006BackfillPhaseSessions,
3377
3408
  migration0007GithubIssue,
3378
3409
  migration0008RepoConfigs,
3379
- migration0009InboxMessages
3410
+ migration0009InboxMessages,
3411
+ migration0010UserConfig
3380
3412
  ];
3381
3413
  var latestMigrationId = migrations.reduce(
3382
3414
  (max, m) => Math.max(max, m.id),
@@ -3459,8 +3491,8 @@ async function assertMigrationsInSync(exec3) {
3459
3491
  // src/shared/db/migrations/MigrationExecutor.ts
3460
3492
  function pgExecutor(queryable) {
3461
3493
  return {
3462
- exec: (sql29) => queryable.query(sql29),
3463
- query: async (sql29, params) => (await queryable.query(sql29, params)).rows
3494
+ exec: (sql30) => queryable.query(sql30),
3495
+ query: async (sql30, params) => (await queryable.query(sql30, params)).rows
3464
3496
  };
3465
3497
  }
3466
3498
 
@@ -3614,13 +3646,13 @@ async function loadItem(orm, id) {
3614
3646
  }
3615
3647
 
3616
3648
  // src/commands/backlog/saveAllItems.ts
3617
- import { eq as eq8, sql as sql13 } from "drizzle-orm";
3649
+ import { eq as eq8, sql as sql14 } from "drizzle-orm";
3618
3650
 
3619
3651
  // src/commands/backlog/deleteItemRelations.ts
3620
3652
  import { eq as eq7 } from "drizzle-orm";
3621
3653
 
3622
3654
  // src/commands/backlog/searchItemIds.ts
3623
- import { and as and3, asc as asc5, eq as eq9, ilike, or, sql as sql14 } from "drizzle-orm";
3655
+ import { and as and3, asc as asc5, eq as eq9, ilike, or, sql as sql15 } from "drizzle-orm";
3624
3656
  async function searchItemIds(orm, query, origin) {
3625
3657
  const pattern2 = `%${query}%`;
3626
3658
  const numberPattern = `%${query.replace(/^#/, "")}%`;
@@ -3634,7 +3666,7 @@ async function searchItemIds(orm, query, origin) {
3634
3666
  ilike(items.jiraKey, pattern2),
3635
3667
  ilike(comments.text, pattern2),
3636
3668
  ilike(planPhases.name, pattern2),
3637
- ilike(sql14`${items.id}::text`, numberPattern)
3669
+ ilike(sql15`${items.id}::text`, numberPattern)
3638
3670
  )
3639
3671
  )
3640
3672
  ).orderBy(asc5(items.id));
@@ -4052,7 +4084,7 @@ import * as fs4 from "fs";
4052
4084
  function extractSessionMeta(lines2) {
4053
4085
  let sessionId = "";
4054
4086
  let cwd = "";
4055
- let timestamp7 = "";
4087
+ let timestamp8 = "";
4056
4088
  let name = "";
4057
4089
  let commandName = "";
4058
4090
  let commandArgs = "";
@@ -4060,14 +4092,14 @@ function extractSessionMeta(lines2) {
4060
4092
  const entry = safeParse(line);
4061
4093
  if (!entry) continue;
4062
4094
  sessionId ||= strField(entry, "sessionId");
4063
- timestamp7 ||= strField(entry, "timestamp");
4095
+ timestamp8 ||= strField(entry, "timestamp");
4064
4096
  cwd ||= strField(entry, "cwd");
4065
4097
  if (entry.type === "user" && !entry.isMeta) {
4066
4098
  ({ name, commandName, commandArgs } = parseFirstUserEntry(entry));
4067
4099
  break;
4068
4100
  }
4069
4101
  }
4070
- return { sessionId, cwd, timestamp: timestamp7, name, commandName, commandArgs };
4102
+ return { sessionId, cwd, timestamp: timestamp8, name, commandName, commandArgs };
4071
4103
  }
4072
4104
  function parseFirstUserEntry(entry) {
4073
4105
  const raw = messageText(entry);
@@ -4120,13 +4152,13 @@ async function parseSessionFile(filePath, origin = "wsl") {
4120
4152
  handle = await fs4.promises.open(filePath, "r");
4121
4153
  const meta = extractSessionMeta(await readHeadLines(handle));
4122
4154
  if (!meta.sessionId) return null;
4123
- const timestamp7 = meta.timestamp || (await fs4.promises.stat(filePath)).mtime.toISOString();
4155
+ const timestamp8 = meta.timestamp || (await fs4.promises.stat(filePath)).mtime.toISOString();
4124
4156
  return {
4125
4157
  sessionId: meta.sessionId,
4126
4158
  name: meta.name || `Session ${meta.sessionId.slice(0, 8)}`,
4127
4159
  project: deriveProject(meta.cwd, filePath, origin),
4128
4160
  cwd: meta.cwd,
4129
- timestamp: timestamp7,
4161
+ timestamp: timestamp8,
4130
4162
  origin,
4131
4163
  ...deriveHistoryFields(meta.commandName, meta.commandArgs, meta.name)
4132
4164
  };
@@ -5917,6 +5949,7 @@ function readRawConfigLayers(cwd, globalConfigPath = getGlobalConfigPath()) {
5917
5949
  return {
5918
5950
  project: loadRawYaml(projectConfigPathFrom(cwd)),
5919
5951
  global,
5952
+ user: readUserConfigCache(),
5920
5953
  repoOverride: repo.override,
5921
5954
  ...repo.key === void 0 ? {} : { repoKey: repo.key },
5922
5955
  ...repo.source === void 0 ? {} : { repoSource: repo.source }
@@ -6022,7 +6055,8 @@ function getNestedValue(obj, path108) {
6022
6055
  var LAYERS = [
6023
6056
  ["project", "project"],
6024
6057
  ["repo", "repoOverride"],
6025
- ["global", "global"]
6058
+ ["global", "global"],
6059
+ ["user", "user"]
6026
6060
  ];
6027
6061
  function configEntryLayers(key, layers, node) {
6028
6062
  const entryLayers = {};
@@ -6831,6 +6865,7 @@ function resolveConfigSources(key, layers) {
6831
6865
  if (getNestedValue(layers.repoOverride, key) !== void 0)
6832
6866
  sources.push("repo");
6833
6867
  if (getNestedValue(layers.global, key) !== void 0) sources.push("global");
6868
+ if (getNestedValue(layers.user, key) !== void 0) sources.push("user");
6834
6869
  return sources;
6835
6870
  }
6836
6871
 
@@ -7533,11 +7568,11 @@ async function deleteItemComment(res, itemId2, commentId) {
7533
7568
  }
7534
7569
 
7535
7570
  // src/commands/backlog/appendComment.ts
7536
- import { sql as sql15 } from "drizzle-orm";
7571
+ import { sql as sql16 } from "drizzle-orm";
7537
7572
  async function appendComment(orm, itemId2, text18, opts = {}) {
7538
7573
  await orm.insert(comments).values({
7539
7574
  itemId: itemId2,
7540
- idx: sql15`(SELECT COALESCE(MAX(${comments.idx}) + 1, 0) FROM ${comments} WHERE ${comments.itemId} = ${itemId2})`,
7575
+ idx: sql16`(SELECT COALESCE(MAX(${comments.idx}) + 1, 0) FROM ${comments} WHERE ${comments.itemId} = ${itemId2})`,
7541
7576
  text: text18,
7542
7577
  phase: opts.phase ?? null,
7543
7578
  timestamp: (/* @__PURE__ */ new Date()).toISOString(),
@@ -8030,11 +8065,11 @@ async function historySearch(req, res) {
8030
8065
  }
8031
8066
 
8032
8067
  // src/shared/db/listInboxMessages.ts
8033
- import { desc as desc2, isNull as isNull2, sql as sql16 } from "drizzle-orm";
8068
+ import { desc as desc2, isNull as isNull2, sql as sql17 } from "drizzle-orm";
8034
8069
  function listInboxMessages(orm) {
8035
8070
  return orm.select().from(inboxMessages).where(isNull2(inboxMessages.dismissedAt)).orderBy(
8036
8071
  desc2(
8037
- sql16`coalesce(${inboxMessages.receivedAt}, ${inboxMessages.flaggedAt})`
8072
+ sql17`coalesce(${inboxMessages.receivedAt}, ${inboxMessages.flaggedAt})`
8038
8073
  )
8039
8074
  );
8040
8075
  }
@@ -9180,12 +9215,12 @@ async function countUsagePeaks(db, window) {
9180
9215
  import { and as and7, desc as desc3, eq as eq18, getTableColumns } from "drizzle-orm";
9181
9216
 
9182
9217
  // src/shared/db/avgContextPerCycle.ts
9183
- import { count as count2, sql as sql17 } from "drizzle-orm";
9218
+ import { count as count2, sql as sql18 } from "drizzle-orm";
9184
9219
  function avgContextPerCycle(db) {
9185
9220
  return db.select({
9186
9221
  window: phaseCycleContext.window,
9187
9222
  resetsAt: phaseCycleContext.resetsAt,
9188
- avgContextPct: sql17`cast(avg(${phaseCycleContext.peakContextPct}) as double precision)`.as(
9223
+ avgContextPct: sql18`cast(avg(${phaseCycleContext.peakContextPct}) as double precision)`.as(
9189
9224
  "avg_context_pct"
9190
9225
  ),
9191
9226
  phaseCount: count2().as("phase_count")
@@ -9274,9 +9309,9 @@ function listUsageHistory(req, res) {
9274
9309
  }
9275
9310
 
9276
9311
  // src/shared/db/countItemUsageByOrigin.ts
9277
- import { asc as asc8, desc as desc4, eq as eq19, sql as sql18 } from "drizzle-orm";
9312
+ import { asc as asc8, desc as desc4, eq as eq19, sql as sql19 } from "drizzle-orm";
9278
9313
  async function countItemUsageByOrigin(db) {
9279
- const count10 = sql18`count(distinct ${phaseUsage.itemId})::int`;
9314
+ const count10 = sql19`count(distinct ${phaseUsage.itemId})::int`;
9280
9315
  const rows = await db.select({ origin: items.origin, count: count10 }).from(items).innerJoin(phaseUsage, eq19(phaseUsage.itemId, items.id)).groupBy(items.origin).orderBy(desc4(count10), asc8(items.origin));
9281
9316
  return rows.map((row3) => ({ origin: row3.origin, count: Number(row3.count) }));
9282
9317
  }
@@ -9318,22 +9353,22 @@ function parseItemUsageSort(field, direction) {
9318
9353
  }
9319
9354
 
9320
9355
  // src/shared/db/itemUsageStats.ts
9321
- import { eq as eq21, sql as sql19 } from "drizzle-orm";
9356
+ import { eq as eq21, sql as sql20 } from "drizzle-orm";
9322
9357
  function median(value) {
9323
- return sql19`coalesce(percentile_cont(0.5) within group (order by (${value})::double precision), 0)`;
9358
+ return sql20`coalesce(percentile_cont(0.5) within group (order by (${value})::double precision), 0)`;
9324
9359
  }
9325
9360
  async function itemUsageStats(db, options2) {
9326
9361
  const totals = phaseUsageTotals(db);
9327
9362
  const planned = planPhaseCounts(db);
9328
9363
  const [row3] = await db.select({
9329
- itemCount: sql19`count(*)::int`,
9330
- doneCount: sql19`count(*) filter (where ${items.status} = 'done')::int`,
9331
- repoCount: sql19`count(distinct ${items.origin})::int`,
9364
+ itemCount: sql20`count(*)::int`,
9365
+ doneCount: sql20`count(*) filter (where ${items.status} = 'done')::int`,
9366
+ repoCount: sql20`count(distinct ${items.origin})::int`,
9332
9367
  medianPhases: median(
9333
- sql19`coalesce(${planned.count}, ${totals.recordedPhases})`
9368
+ sql20`coalesce(${planned.count}, ${totals.recordedPhases})`
9334
9369
  ),
9335
9370
  medianActiveMs: median(totals.activeMs),
9336
- medianTokens: median(sql19`${totals.tokensUp} + ${totals.tokensDown}`)
9371
+ medianTokens: median(sql20`${totals.tokensUp} + ${totals.tokensDown}`)
9337
9372
  }).from(items).innerJoin(totals, eq21(totals.itemId, items.id)).leftJoin(planned, eq21(planned.itemId, items.id)).where(itemUsageWhere(options2));
9338
9373
  return {
9339
9374
  itemCount: Number(row3?.itemCount ?? 0),
@@ -9349,27 +9384,27 @@ async function itemUsageStats(db, options2) {
9349
9384
  import { eq as eq22 } from "drizzle-orm";
9350
9385
 
9351
9386
  // src/shared/db/itemUsageOrderBy.ts
9352
- import { desc as desc5, sql as sql20 } from "drizzle-orm";
9387
+ import { desc as desc5, sql as sql21 } from "drizzle-orm";
9353
9388
  function itemUsageOrderBy(joins, sort = defaultItemUsageSort) {
9354
9389
  const { totals, planned, lastPhase } = joins;
9355
9390
  const sortable = {
9356
- phases: sql20`coalesce(${planned.count}, ${totals.recordedPhases})`,
9357
- active: sql20`${totals.activeMs}`,
9358
- tokens: sql20`${totals.tokensUp} + ${totals.tokensDown}`,
9359
- peakContext: sql20`${totals.peakContextPct}`,
9360
- lastPhase: sql20`${lastPhase.at}`
9391
+ phases: sql21`coalesce(${planned.count}, ${totals.recordedPhases})`,
9392
+ active: sql21`${totals.activeMs}`,
9393
+ tokens: sql21`${totals.tokensUp} + ${totals.tokensDown}`,
9394
+ peakContext: sql21`${totals.peakContextPct}`,
9395
+ lastPhase: sql21`${lastPhase.at}`
9361
9396
  };
9362
- const order = sort.direction === "asc" ? sql20`asc` : sql20`desc`;
9363
- return [sql20`${sortable[sort.field]} ${order} nulls last`, desc5(items.id)];
9397
+ const order = sort.direction === "asc" ? sql21`asc` : sql21`desc`;
9398
+ return [sql21`${sortable[sort.field]} ${order} nulls last`, desc5(items.id)];
9364
9399
  }
9365
9400
 
9366
9401
  // src/shared/db/lastPhaseActivity.ts
9367
- import { sql as sql21 } from "drizzle-orm";
9402
+ import { sql as sql22 } from "drizzle-orm";
9368
9403
  function lastPhaseActivity(db) {
9369
9404
  return db.select({
9370
9405
  itemId: phaseSessions.itemId,
9371
- at: sql21`max(${phaseSessions.createdAt})`.as("last_phase_at"),
9372
- atIso: sql21`to_char(max(${phaseSessions.createdAt}) at time zone 'utc', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`.as(
9406
+ at: sql22`max(${phaseSessions.createdAt})`.as("last_phase_at"),
9407
+ atIso: sql22`to_char(max(${phaseSessions.createdAt}) at time zone 'utc', 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')`.as(
9373
9408
  "last_phase_at_iso"
9374
9409
  )
9375
9410
  }).from(phaseSessions).groupBy(phaseSessions.itemId).as("last_phase_activity");
@@ -11206,15 +11241,22 @@ async function saveRepoConfig(orm, repoKey, config) {
11206
11241
  });
11207
11242
  }
11208
11243
 
11244
+ // src/shared/db/readUserConfig.ts
11245
+ async function readUserConfig(orm) {
11246
+ const [row3] = await orm.select().from(userConfig);
11247
+ return row3?.config ?? {};
11248
+ }
11249
+
11209
11250
  // src/shared/writeRepoConfigCache.ts
11210
11251
  import { mkdirSync as mkdirSync6, renameSync as renameSync3, writeFileSync as writeFileSync5 } from "fs";
11211
11252
  import { dirname as dirname12 } from "path";
11212
- function writeRepoConfigCache(repos2) {
11253
+ function writeRepoConfigCache(repos2, user = {}) {
11213
11254
  const path108 = repoConfigCachePath();
11214
11255
  mkdirSync6(dirname12(path108), { recursive: true });
11215
11256
  const cache7 = {
11216
11257
  refreshedAt: (/* @__PURE__ */ new Date()).toISOString(),
11217
- repos: repos2
11258
+ repos: repos2,
11259
+ user
11218
11260
  };
11219
11261
  const tmp = `${path108}.${process.pid}.tmp`;
11220
11262
  writeFileSync5(tmp, JSON.stringify(cache7, null, 2));
@@ -11223,8 +11265,9 @@ function writeRepoConfigCache(repos2) {
11223
11265
 
11224
11266
  // src/shared/refreshRepoConfigCache.ts
11225
11267
  async function refreshRepoConfigCache(orm) {
11226
- const repos2 = await listRepoConfigs(orm ?? await getDb());
11227
- writeRepoConfigCache(repos2);
11268
+ const db = orm ?? await getDb();
11269
+ const repos2 = await listRepoConfigs(db);
11270
+ writeRepoConfigCache(repos2, await readUserConfig(db));
11228
11271
  return Object.keys(repos2).length;
11229
11272
  }
11230
11273
 
@@ -11247,12 +11290,66 @@ async function applySharedRepoConfigSet(key, coerced, repoName, cwd = process.cw
11247
11290
  return { ok: true, target: "repo", label };
11248
11291
  }
11249
11292
 
11293
+ // src/shared/db/updateUserConfig.ts
11294
+ import { eq as eq24 } from "drizzle-orm";
11295
+ async function updateUserConfig(orm, update3) {
11296
+ return orm.transaction(async (tx) => {
11297
+ const [row3] = await tx.select().from(userConfig).where(eq24(userConfig.id, 1)).for("update");
11298
+ const outcome = update3(row3?.config ?? {});
11299
+ if (!outcome.ok) return outcome;
11300
+ if (Object.keys(outcome.config).length === 0) {
11301
+ await tx.delete(userConfig).where(eq24(userConfig.id, 1));
11302
+ return outcome;
11303
+ }
11304
+ await tx.insert(userConfig).values({ id: 1, config: outcome.config }).onConflictDoUpdate({
11305
+ target: userConfig.id,
11306
+ set: { config: outcome.config, updatedAt: /* @__PURE__ */ new Date() }
11307
+ });
11308
+ return outcome;
11309
+ });
11310
+ }
11311
+
11312
+ // src/commands/config/userDbKeyError.ts
11313
+ var DB_EXCLUDED_ROOTS = ["repos", "database"];
11314
+ function isNodeOnlyKey(key) {
11315
+ const root2 = key.split(".")[0];
11316
+ return isGlobalOnlyConfigKey(key) || DB_EXCLUDED_ROOTS.includes(root2);
11317
+ }
11318
+ function userDbKeyError(key, verb) {
11319
+ if (!isNodeOnlyKey(key)) return void 0;
11320
+ return {
11321
+ ok: false,
11322
+ errors: [
11323
+ `"${key}" can't be stored in user (db); it stays in user (node). ${verb} it in ~/.assist.yml with -g`
11324
+ ]
11325
+ };
11326
+ }
11327
+
11328
+ // src/commands/config/applyUserConfigSet.ts
11329
+ async function applyUserConfigSet(key, coerced) {
11330
+ const nodeOnly = userDbKeyError(key, "Set");
11331
+ if (nodeOnly) return nodeOnly;
11332
+ const orm = await getDb();
11333
+ const outcome = await updateUserConfig(orm, (current) => {
11334
+ const config = setNestedValue(current, key, coerced);
11335
+ const validation = validateConfig(config, key);
11336
+ return validation.ok ? { ok: true, config, result: void 0 } : validation;
11337
+ });
11338
+ if (!outcome.ok) return outcome;
11339
+ await refreshRepoConfigCache(orm);
11340
+ return { ok: true, target: "user" };
11341
+ }
11342
+
11250
11343
  // src/commands/sessions/web/applyScopedConfigSet.ts
11251
11344
  async function applyScopedConfigSet(key, value, cwd, scope, globalConfigPath) {
11252
11345
  if (scope === "repo") {
11253
11346
  const result2 = await applySharedRepoConfigSet(key, value, void 0, cwd);
11254
11347
  return result2.ok ? { ok: true, payload: { target: result2.target, repoKey: result2.label } } : result2;
11255
11348
  }
11349
+ if (scope === "user") {
11350
+ const result2 = await applyUserConfigSet(key, value);
11351
+ return result2.ok ? { ok: true, payload: { target: result2.target } } : result2;
11352
+ }
11256
11353
  const result = applyConfigSet(
11257
11354
  key,
11258
11355
  value,
@@ -11265,7 +11362,7 @@ async function applyScopedConfigSet(key, value, cwd, scope, globalConfigPath) {
11265
11362
 
11266
11363
  // src/commands/config/ConfigWriteScope.ts
11267
11364
  function isConfigWriteScope(value) {
11268
- return value === "project" || value === "global" || value === "repo";
11365
+ return value === "project" || value === "global" || value === "repo" || value === "user";
11269
11366
  }
11270
11367
 
11271
11368
  // src/commands/sessions/web/parseConfigWriteRequest.ts
@@ -11452,6 +11549,22 @@ async function applySharedRepoConfigUnset(key, repoName, cwd = process.cwd()) {
11452
11549
  return { ok: true, target: "repo", label, removed: unset.removed };
11453
11550
  }
11454
11551
 
11552
+ // src/commands/config/applyUserConfigUnset.ts
11553
+ async function applyUserConfigUnset(key) {
11554
+ const nodeOnly = userDbKeyError(key, "Unset");
11555
+ if (nodeOnly) return nodeOnly;
11556
+ const orm = await getDb();
11557
+ const outcome = await updateUserConfig(orm, (current) => {
11558
+ const { config, removed } = unsetNestedValue(current, key);
11559
+ if (!removed) return { ok: true, config: current, result: false };
11560
+ const validation = validateConfig(config, key);
11561
+ return validation.ok ? { ok: true, config, result: true } : validation;
11562
+ });
11563
+ if (!outcome.ok) return outcome;
11564
+ if (outcome.result) await refreshRepoConfigCache(orm);
11565
+ return { ok: true, target: "user", removed: outcome.result };
11566
+ }
11567
+
11455
11568
  // src/commands/sessions/web/applyScopedConfigUnset.ts
11456
11569
  async function applyScopedConfigUnset(key, cwd, scope, globalConfigPath) {
11457
11570
  if (scope === "repo") {
@@ -11465,6 +11578,13 @@ async function applyScopedConfigUnset(key, cwd, scope, globalConfigPath) {
11465
11578
  }
11466
11579
  } : result2;
11467
11580
  }
11581
+ if (scope === "user") {
11582
+ const result2 = await applyUserConfigUnset(key);
11583
+ return result2.ok ? {
11584
+ ok: true,
11585
+ payload: { target: result2.target, removed: result2.removed }
11586
+ } : result2;
11587
+ }
11468
11588
  const result = applyConfigUnset(
11469
11589
  key,
11470
11590
  scope === "global",
@@ -14963,8 +15083,8 @@ async function buildDump(tables, copyOut) {
14963
15083
  // src/commands/backlog/dump/copyTableOut.ts
14964
15084
  import { to as copyTo } from "pg-copy-streams";
14965
15085
  async function copyTableOut(client, table) {
14966
- const sql29 = `COPY ${table.name} (${table.columns.join(", ")}) TO STDOUT`;
14967
- const stream = client.query(copyTo(sql29));
15086
+ const sql30 = `COPY ${table.name} (${table.columns.join(", ")}) TO STDOUT`;
15087
+ const stream = client.query(copyTo(sql30));
14968
15088
  const chunks = [];
14969
15089
  for await (const chunk of stream) {
14970
15090
  chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
@@ -15076,8 +15196,8 @@ async function backup({ out }) {
15076
15196
  const dir = expandTilde(out ?? loadConfig().backup.dir);
15077
15197
  await mkdir3(dir, { recursive: true });
15078
15198
  const start2 = Date.now();
15079
- const timestamp7 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
15080
- const filePath = resolve15(join42(dir, `backup-${timestamp7}.dump`));
15199
+ const timestamp8 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
15200
+ const filePath = resolve15(join42(dir, `backup-${timestamp8}.dump`));
15081
15201
  await exportBacklog(filePath);
15082
15202
  const { size } = await stat4(filePath);
15083
15203
  const durationMs = Date.now() - start2;
@@ -15428,11 +15548,11 @@ function registerActivityCommands(cmd) {
15428
15548
 
15429
15549
  // src/commands/backlog/associateGithub.ts
15430
15550
  import chalk43 from "chalk";
15431
- import { eq as eq25 } from "drizzle-orm";
15551
+ import { eq as eq26 } from "drizzle-orm";
15432
15552
 
15433
15553
  // src/commands/backlog/beginAssociation.ts
15434
15554
  import chalk42 from "chalk";
15435
- import { eq as eq24 } from "drizzle-orm";
15555
+ import { eq as eq25 } from "drizzle-orm";
15436
15556
  async function beginAssociation(id, options2, clearPatch, label) {
15437
15557
  const found = await findOneItem(id);
15438
15558
  if (!found) {
@@ -15442,7 +15562,7 @@ async function beginAssociation(id, options2, clearPatch, label) {
15442
15562
  const { orm } = found;
15443
15563
  const itemId2 = found.item.id;
15444
15564
  if (options2.clear) {
15445
- await orm.update(items).set(clearPatch).where(eq24(items.id, itemId2));
15565
+ await orm.update(items).set(clearPatch).where(eq25(items.id, itemId2));
15446
15566
  console.log(
15447
15567
  chalk42.green(
15448
15568
  `Cleared ${label} association on item ${formatItemId(itemId2)}.`
@@ -15510,7 +15630,7 @@ async function associateGithub(id, issue, options2) {
15510
15630
  return;
15511
15631
  }
15512
15632
  const title = fetchGithubIssueTitle(normalized);
15513
- await orm.update(items).set({ githubIssue: normalized, jiraKey: null }).where(eq25(items.id, itemId2));
15633
+ await orm.update(items).set({ githubIssue: normalized, jiraKey: null }).where(eq26(items.id, itemId2));
15514
15634
  console.log(
15515
15635
  chalk43.green(`Associated ${normalized} with item ${formatItemId(itemId2)}.`),
15516
15636
  title ? chalk43.dim(`(${title})`) : ""
@@ -15524,7 +15644,7 @@ function registerAssociateGithubCommand(cmd) {
15524
15644
 
15525
15645
  // src/commands/backlog/associateJira.ts
15526
15646
  import chalk45 from "chalk";
15527
- import { eq as eq26 } from "drizzle-orm";
15647
+ import { eq as eq27 } from "drizzle-orm";
15528
15648
 
15529
15649
  // src/commands/jira/fetchIssue.ts
15530
15650
  import { execSync as execSync17 } from "child_process";
@@ -15589,7 +15709,7 @@ async function associateJira(id, key, options2) {
15589
15709
  const parsed = fetchIssue(normalized, "summary");
15590
15710
  const fields = parsed?.fields;
15591
15711
  const summary = fields?.summary;
15592
- await orm.update(items).set({ jiraKey: normalized, githubIssue: null }).where(eq26(items.id, itemId2));
15712
+ await orm.update(items).set({ jiraKey: normalized, githubIssue: null }).where(eq27(items.id, itemId2));
15593
15713
  console.log(
15594
15714
  chalk45.green(`Associated ${normalized} with item ${formatItemId(itemId2)}.`),
15595
15715
  summary ? chalk45.dim(`(${summary})`) : ""
@@ -15871,8 +15991,8 @@ async function confirmReplace(client, tables, incoming, fromStdin) {
15871
15991
  import { finished } from "stream/promises";
15872
15992
  import { from as copyFrom } from "pg-copy-streams";
15873
15993
  async function copyTableIn(client, table, data) {
15874
- const sql29 = `COPY ${table.name} (${table.columns.join(", ")}) FROM STDIN`;
15875
- const stream = client.query(copyFrom(sql29));
15994
+ const sql30 = `COPY ${table.name} (${table.columns.join(", ")}) FROM STDIN`;
15995
+ const stream = client.query(copyFrom(sql30));
15876
15996
  stream.end(data);
15877
15997
  await finished(stream);
15878
15998
  }
@@ -15974,15 +16094,15 @@ function isClaudeCode() {
15974
16094
  }
15975
16095
 
15976
16096
  // src/commands/backlog/insertPhaseAt.ts
15977
- import { eq as eq28 } from "drizzle-orm";
16097
+ import { eq as eq29 } from "drizzle-orm";
15978
16098
 
15979
16099
  // src/commands/backlog/shiftPhasesUp.ts
15980
- import { and as and9, desc as desc6, eq as eq27, gte } from "drizzle-orm";
16100
+ import { and as and9, desc as desc6, eq as eq28, gte } from "drizzle-orm";
15981
16101
  async function shiftPhasesUp(db, itemId2, fromIdx) {
15982
- const toShift = await db.select({ idx: planPhases.idx }).from(planPhases).where(and9(eq27(planPhases.itemId, itemId2), gte(planPhases.idx, fromIdx))).orderBy(desc6(planPhases.idx));
16102
+ const toShift = await db.select({ idx: planPhases.idx }).from(planPhases).where(and9(eq28(planPhases.itemId, itemId2), gte(planPhases.idx, fromIdx))).orderBy(desc6(planPhases.idx));
15983
16103
  for (const p of toShift) {
15984
- await db.update(planTasks).set({ phaseIdx: p.idx + 1 }).where(and9(eq27(planTasks.itemId, itemId2), eq27(planTasks.phaseIdx, p.idx)));
15985
- await db.update(planPhases).set({ idx: p.idx + 1 }).where(and9(eq27(planPhases.itemId, itemId2), eq27(planPhases.idx, p.idx)));
16104
+ await db.update(planTasks).set({ phaseIdx: p.idx + 1 }).where(and9(eq28(planTasks.itemId, itemId2), eq28(planTasks.phaseIdx, p.idx)));
16105
+ await db.update(planPhases).set({ idx: p.idx + 1 }).where(and9(eq28(planPhases.itemId, itemId2), eq28(planPhases.idx, p.idx)));
15986
16106
  }
15987
16107
  }
15988
16108
 
@@ -15995,7 +16115,7 @@ async function insertPhaseAt(orm, itemId2, phaseIdx, name, tasks, manualChecks,
15995
16115
  await tx.insert(planTasks).values(tasks.map((task, i) => ({ itemId: itemId2, phaseIdx, idx: i, task })));
15996
16116
  }
15997
16117
  if (currentPhase !== void 0 && currentPhase - 1 >= phaseIdx) {
15998
- await tx.update(items).set({ currentPhase: phaseIdx + 1 }).where(eq28(items.id, itemId2));
16118
+ await tx.update(items).set({ currentPhase: phaseIdx + 1 }).where(eq29(items.id, itemId2));
15999
16119
  }
16000
16120
  });
16001
16121
  }
@@ -16152,9 +16272,9 @@ import chalk56 from "chalk";
16152
16272
 
16153
16273
  // src/commands/backlog/resolveInsertPosition.ts
16154
16274
  import chalk55 from "chalk";
16155
- import { count as count3, eq as eq29 } from "drizzle-orm";
16275
+ import { count as count3, eq as eq30 } from "drizzle-orm";
16156
16276
  async function resolveInsertPosition(orm, itemId2, position) {
16157
- const [row3] = await orm.select({ cnt: count3() }).from(planPhases).where(eq29(planPhases.itemId, itemId2));
16277
+ const [row3] = await orm.select({ cnt: count3() }).from(planPhases).where(eq30(planPhases.itemId, itemId2));
16158
16278
  const phaseCount = row3?.cnt ?? 0;
16159
16279
  if (position === void 0) return phaseCount;
16160
16280
  const pos = Number.parseInt(position, 10);
@@ -16574,9 +16694,9 @@ function hasCycle(adjacency, fromId, toId) {
16574
16694
  }
16575
16695
 
16576
16696
  // src/commands/backlog/loadDependencyGraph.ts
16577
- import { eq as eq30 } from "drizzle-orm";
16697
+ import { eq as eq31 } from "drizzle-orm";
16578
16698
  async function loadDependencyGraph(orm) {
16579
- const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq30(links.type, "depends-on"));
16699
+ const rows = await orm.select({ itemId: links.itemId, targetId: links.targetId }).from(links).where(eq31(links.type, "depends-on"));
16580
16700
  const graph = /* @__PURE__ */ new Map();
16581
16701
  for (const { itemId: itemId2, targetId } of rows) {
16582
16702
  const bucket = graph.get(itemId2);
@@ -16643,7 +16763,7 @@ async function link(fromId, toId, opts) {
16643
16763
 
16644
16764
  // src/commands/backlog/unlink.ts
16645
16765
  import chalk64 from "chalk";
16646
- import { and as and10, eq as eq31 } from "drizzle-orm";
16766
+ import { and as and10, eq as eq32 } from "drizzle-orm";
16647
16767
  async function unlink(fromId, toId) {
16648
16768
  const fromNum = parseItemId(fromId);
16649
16769
  const toNum = parseItemId(toId);
@@ -16667,7 +16787,7 @@ async function unlink(fromId, toId) {
16667
16787
  );
16668
16788
  return;
16669
16789
  }
16670
- await orm.delete(links).where(and10(eq31(links.itemId, fromNum), eq31(links.targetId, toNum)));
16790
+ await orm.delete(links).where(and10(eq32(links.itemId, fromNum), eq32(links.targetId, toNum)));
16671
16791
  console.log(
16672
16792
  chalk64.green(
16673
16793
  `Removed link from ${formatItemId(fromNum)} to ${formatItemId(toNum)}.`
@@ -16687,7 +16807,7 @@ function registerLinkCommands(cmd) {
16687
16807
 
16688
16808
  // src/commands/backlog/moveRepo.ts
16689
16809
  import chalk66 from "chalk";
16690
- import { eq as eq33 } from "drizzle-orm";
16810
+ import { eq as eq34 } from "drizzle-orm";
16691
16811
 
16692
16812
  // src/commands/backlog/move-repo/confirmMove.ts
16693
16813
  import chalk65 from "chalk";
@@ -16702,9 +16822,9 @@ async function confirmMove(cnt, oldOrigin, newOrigin) {
16702
16822
  }
16703
16823
 
16704
16824
  // src/commands/backlog/move-repo/countByOrigin.ts
16705
- import { count as count4, eq as eq32 } from "drizzle-orm";
16825
+ import { count as count4, eq as eq33 } from "drizzle-orm";
16706
16826
  async function countByOrigin(orm, origin) {
16707
- const [{ cnt }] = await orm.select({ cnt: count4() }).from(items).where(eq32(items.origin, origin));
16827
+ const [{ cnt }] = await orm.select({ cnt: count4() }).from(items).where(eq33(items.origin, origin));
16708
16828
  return cnt;
16709
16829
  }
16710
16830
 
@@ -16747,7 +16867,7 @@ async function moveRepo(oldOriginRaw, newOriginRaw, options2 = {}) {
16747
16867
  console.log(chalk66.yellow("Move cancelled; no changes made."));
16748
16868
  return;
16749
16869
  }
16750
- await orm.update(items).set({ origin: newOrigin }).where(eq33(items.origin, oldOrigin));
16870
+ await orm.update(items).set({ origin: newOrigin }).where(eq34(items.origin, oldOrigin));
16751
16871
  console.log(
16752
16872
  chalk66.green(
16753
16873
  `Moved ${pluralItems(cnt)} from "${oldOrigin}" to "${newOrigin}".`
@@ -16911,9 +17031,9 @@ function processStartedAt(pid) {
16911
17031
 
16912
17032
  // src/commands/backlog/isStaleLock.ts
16913
17033
  var clockSlackMs = 5e3;
16914
- function isStaleLock(pid, timestamp7) {
16915
- if (typeof timestamp7 !== "string") return false;
16916
- const lockedAt = Date.parse(timestamp7);
17034
+ function isStaleLock(pid, timestamp8) {
17035
+ if (typeof timestamp8 !== "string") return false;
17036
+ const lockedAt = Date.parse(timestamp8);
16917
17037
  if (Number.isNaN(lockedAt)) return false;
16918
17038
  if (lockedAt < bootedAt() - clockSlackMs) return true;
16919
17039
  const startedAt = processStartedAt(pid);
@@ -17219,12 +17339,12 @@ function message(error) {
17219
17339
  import chalk70 from "chalk";
17220
17340
 
17221
17341
  // src/shared/db/findPhaseBySessionId.ts
17222
- import { and as and11, eq as eq34 } from "drizzle-orm";
17342
+ import { and as and11, eq as eq35 } from "drizzle-orm";
17223
17343
  async function findPhaseBySessionId(db, itemId2, claudeSessionId) {
17224
17344
  const rows = await db.select({ phaseIdx: phaseUsage.phaseIdx }).from(phaseUsage).where(
17225
17345
  and11(
17226
- eq34(phaseUsage.itemId, itemId2),
17227
- eq34(phaseUsage.claudeSessionId, claudeSessionId)
17346
+ eq35(phaseUsage.itemId, itemId2),
17347
+ eq35(phaseUsage.claudeSessionId, claudeSessionId)
17228
17348
  )
17229
17349
  ).limit(1);
17230
17350
  return rows[0]?.phaseIdx;
@@ -17834,7 +17954,7 @@ import { randomUUID as randomUUID6 } from "crypto";
17834
17954
  import os4 from "os";
17835
17955
 
17836
17956
  // src/shared/db/recordPhaseSession.ts
17837
- import { sql as sql22 } from "drizzle-orm";
17957
+ import { sql as sql23 } from "drizzle-orm";
17838
17958
  async function recordPhaseSession(db, itemId2, phaseIdx, claudeSessionId, hostname2, osUser) {
17839
17959
  await db.insert(phaseSessions).values({ itemId: itemId2, phaseIdx, claudeSessionId, hostname: hostname2, osUser }).onConflictDoUpdate({
17840
17960
  target: [
@@ -17843,7 +17963,7 @@ async function recordPhaseSession(db, itemId2, phaseIdx, claudeSessionId, hostna
17843
17963
  phaseSessions.claudeSessionId
17844
17964
  ],
17845
17965
  set: { hostname: hostname2, osUser },
17846
- setWhere: sql22`${phaseSessions.hostname} = 'unknown'`
17966
+ setWhere: sql23`${phaseSessions.hostname} = 'unknown'`
17847
17967
  });
17848
17968
  }
17849
17969
 
@@ -18704,10 +18824,10 @@ async function start(id) {
18704
18824
 
18705
18825
  // src/commands/backlog/stop.ts
18706
18826
  import chalk93 from "chalk";
18707
- import { and as and12, eq as eq35 } from "drizzle-orm";
18827
+ import { and as and12, eq as eq36 } from "drizzle-orm";
18708
18828
  async function stop() {
18709
18829
  const { orm } = await getReady();
18710
- const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(and12(eq35(items.status, "in-progress"), eq35(items.origin, getOrigin()))).returning({ id: items.id, name: items.name });
18830
+ const stopped = await orm.update(items).set({ status: "todo", currentPhase: 1 }).where(and12(eq36(items.status, "in-progress"), eq36(items.origin, getOrigin()))).returning({ id: items.id, name: items.name });
18711
18831
  if (stopped.length === 0) {
18712
18832
  console.log(chalk93.yellow("No in-progress items to stop."));
18713
18833
  return;
@@ -18765,20 +18885,20 @@ function registerStatusCommands(cmd) {
18765
18885
 
18766
18886
  // src/commands/backlog/movePhase.ts
18767
18887
  import chalk96 from "chalk";
18768
- import { count as count6, eq as eq38 } from "drizzle-orm";
18888
+ import { count as count6, eq as eq39 } from "drizzle-orm";
18769
18889
 
18770
18890
  // src/commands/backlog/reorderPhaseRows.ts
18771
- import { and as and14, asc as asc10, eq as eq37 } from "drizzle-orm";
18891
+ import { and as and14, asc as asc10, eq as eq38 } from "drizzle-orm";
18772
18892
 
18773
18893
  // src/commands/backlog/reindexPhases.ts
18774
- import { and as and13, asc as asc9, count as count5, eq as eq36 } from "drizzle-orm";
18894
+ import { and as and13, asc as asc9, count as count5, eq as eq37 } from "drizzle-orm";
18775
18895
  async function reindexPhases(db, itemId2) {
18776
- const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq36(planPhases.itemId, itemId2)).orderBy(asc9(planPhases.idx));
18896
+ const remaining = await db.select({ idx: planPhases.idx }).from(planPhases).where(eq37(planPhases.itemId, itemId2)).orderBy(asc9(planPhases.idx));
18777
18897
  for (let i = 0; i < remaining.length; i++) {
18778
18898
  const oldIdx = remaining[i].idx;
18779
18899
  if (oldIdx === i) continue;
18780
- await db.update(planTasks).set({ phaseIdx: i }).where(and13(eq36(planTasks.itemId, itemId2), eq36(planTasks.phaseIdx, oldIdx)));
18781
- await db.update(planPhases).set({ idx: i }).where(and13(eq36(planPhases.itemId, itemId2), eq36(planPhases.idx, oldIdx)));
18900
+ await db.update(planTasks).set({ phaseIdx: i }).where(and13(eq37(planTasks.itemId, itemId2), eq37(planTasks.phaseIdx, oldIdx)));
18901
+ await db.update(planPhases).set({ idx: i }).where(and13(eq37(planPhases.itemId, itemId2), eq37(planPhases.idx, oldIdx)));
18782
18902
  }
18783
18903
  }
18784
18904
  async function adjustCurrentPhase(db, item, removedIdx) {
@@ -18786,24 +18906,24 @@ async function adjustCurrentPhase(db, item, removedIdx) {
18786
18906
  if (currentPhase === void 0) return;
18787
18907
  const currentIdx = currentPhase - 1;
18788
18908
  if (removedIdx < currentIdx) {
18789
- await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq36(items.id, item.id));
18909
+ await db.update(items).set({ currentPhase: currentPhase - 1 }).where(eq37(items.id, item.id));
18790
18910
  return;
18791
18911
  }
18792
18912
  if (removedIdx !== currentIdx) return;
18793
- const [row3] = await db.select({ cnt: count5() }).from(planPhases).where(eq36(planPhases.itemId, item.id));
18913
+ const [row3] = await db.select({ cnt: count5() }).from(planPhases).where(eq37(planPhases.itemId, item.id));
18794
18914
  const cnt = row3?.cnt ?? 0;
18795
- await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq36(items.id, item.id));
18915
+ await db.update(items).set({ currentPhase: cnt === 0 ? null : Math.min(currentPhase, cnt) }).where(eq37(items.id, item.id));
18796
18916
  }
18797
18917
 
18798
18918
  // src/commands/backlog/reorderPhaseRows.ts
18799
18919
  async function reorderPhaseRows(orm, itemId2, fromIdx, toIdx) {
18800
18920
  await orm.transaction(async (tx) => {
18801
- const [phaseRow] = await tx.select({ name: planPhases.name, manualChecks: planPhases.manualChecks }).from(planPhases).where(and14(eq37(planPhases.itemId, itemId2), eq37(planPhases.idx, fromIdx)));
18802
- const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(and14(eq37(planTasks.itemId, itemId2), eq37(planTasks.phaseIdx, fromIdx))).orderBy(asc10(planTasks.idx));
18921
+ const [phaseRow] = await tx.select({ name: planPhases.name, manualChecks: planPhases.manualChecks }).from(planPhases).where(and14(eq38(planPhases.itemId, itemId2), eq38(planPhases.idx, fromIdx)));
18922
+ const tasks = await tx.select({ idx: planTasks.idx, task: planTasks.task }).from(planTasks).where(and14(eq38(planTasks.itemId, itemId2), eq38(planTasks.phaseIdx, fromIdx))).orderBy(asc10(planTasks.idx));
18803
18923
  await tx.delete(planTasks).where(
18804
- and14(eq37(planTasks.itemId, itemId2), eq37(planTasks.phaseIdx, fromIdx))
18924
+ and14(eq38(planTasks.itemId, itemId2), eq38(planTasks.phaseIdx, fromIdx))
18805
18925
  );
18806
- await tx.delete(planPhases).where(and14(eq37(planPhases.itemId, itemId2), eq37(planPhases.idx, fromIdx)));
18926
+ await tx.delete(planPhases).where(and14(eq38(planPhases.itemId, itemId2), eq38(planPhases.idx, fromIdx)));
18807
18927
  await reindexPhases(tx, itemId2);
18808
18928
  await shiftPhasesUp(tx, itemId2, toIdx);
18809
18929
  await tx.insert(planPhases).values({
@@ -18844,7 +18964,7 @@ async function movePhase(id, from, to) {
18844
18964
  if (!found) return;
18845
18965
  const { orm, item } = found;
18846
18966
  const itemId2 = item.id;
18847
- const [row3] = await orm.select({ cnt: count6() }).from(planPhases).where(eq38(planPhases.itemId, itemId2));
18967
+ const [row3] = await orm.select({ cnt: count6() }).from(planPhases).where(eq39(planPhases.itemId, itemId2));
18848
18968
  const phaseCount = row3?.cnt ?? 0;
18849
18969
  const fromIdx = toIndex2(from, phaseCount);
18850
18970
  if (fromIdx === void 0) return;
@@ -18860,22 +18980,22 @@ async function movePhase(id, from, to) {
18860
18980
  import chalk98 from "chalk";
18861
18981
 
18862
18982
  // src/commands/backlog/applyPhaseUpdate.ts
18863
- import { and as and15, eq as eq39 } from "drizzle-orm";
18983
+ import { and as and15, eq as eq40 } from "drizzle-orm";
18864
18984
  async function applyPhaseUpdate(orm, itemId2, phaseIdx, fields) {
18865
18985
  await orm.transaction(async (tx) => {
18866
18986
  if (fields.name) {
18867
18987
  await tx.update(planPhases).set({ name: fields.name }).where(
18868
- and15(eq39(planPhases.itemId, itemId2), eq39(planPhases.idx, phaseIdx))
18988
+ and15(eq40(planPhases.itemId, itemId2), eq40(planPhases.idx, phaseIdx))
18869
18989
  );
18870
18990
  }
18871
18991
  if (fields.manualCheck) {
18872
18992
  await tx.update(planPhases).set({ manualChecks: JSON.stringify(fields.manualCheck) }).where(
18873
- and15(eq39(planPhases.itemId, itemId2), eq39(planPhases.idx, phaseIdx))
18993
+ and15(eq40(planPhases.itemId, itemId2), eq40(planPhases.idx, phaseIdx))
18874
18994
  );
18875
18995
  }
18876
18996
  if (fields.task) {
18877
18997
  await tx.delete(planTasks).where(
18878
- and15(eq39(planTasks.itemId, itemId2), eq39(planTasks.phaseIdx, phaseIdx))
18998
+ and15(eq40(planTasks.itemId, itemId2), eq40(planTasks.phaseIdx, phaseIdx))
18879
18999
  );
18880
19000
  if (fields.task.length) {
18881
19001
  await tx.insert(planTasks).values(
@@ -18893,14 +19013,14 @@ async function applyPhaseUpdate(orm, itemId2, phaseIdx, fields) {
18893
19013
 
18894
19014
  // src/commands/backlog/findPhase.ts
18895
19015
  import chalk97 from "chalk";
18896
- import { and as and16, count as count7, eq as eq40 } from "drizzle-orm";
19016
+ import { and as and16, count as count7, eq as eq41 } from "drizzle-orm";
18897
19017
  async function findPhase(id, phase) {
18898
19018
  const found = await findOneItem(id);
18899
19019
  if (!found) return void 0;
18900
19020
  const { orm, item } = found;
18901
19021
  const itemId2 = item.id;
18902
19022
  const phaseIdx = Number.parseInt(phase, 10) - 1;
18903
- const [row3] = await orm.select({ cnt: count7() }).from(planPhases).where(and16(eq40(planPhases.itemId, itemId2), eq40(planPhases.idx, phaseIdx)));
19023
+ const [row3] = await orm.select({ cnt: count7() }).from(planPhases).where(and16(eq41(planPhases.itemId, itemId2), eq41(planPhases.idx, phaseIdx)));
18904
19024
  if (!row3 || row3.cnt === 0) {
18905
19025
  console.log(
18906
19026
  chalk97.red(
@@ -19073,16 +19193,16 @@ function registerUpdatePhaseCommand(cmd) {
19073
19193
 
19074
19194
  // src/commands/backlog/removePhase.ts
19075
19195
  import chalk99 from "chalk";
19076
- import { and as and17, eq as eq41 } from "drizzle-orm";
19196
+ import { and as and17, eq as eq42 } from "drizzle-orm";
19077
19197
  async function removePhase(id, phase) {
19078
19198
  const found = await findPhase(id, phase);
19079
19199
  if (!found) return;
19080
19200
  const { item, orm, itemId: itemId2, phaseIdx } = found;
19081
19201
  await orm.transaction(async (tx) => {
19082
19202
  await tx.delete(planTasks).where(
19083
- and17(eq41(planTasks.itemId, itemId2), eq41(planTasks.phaseIdx, phaseIdx))
19203
+ and17(eq42(planTasks.itemId, itemId2), eq42(planTasks.phaseIdx, phaseIdx))
19084
19204
  );
19085
- await tx.delete(planPhases).where(and17(eq41(planPhases.itemId, itemId2), eq41(planPhases.idx, phaseIdx)));
19205
+ await tx.delete(planPhases).where(and17(eq42(planPhases.itemId, itemId2), eq42(planPhases.idx, phaseIdx)));
19086
19206
  await reindexPhases(tx, itemId2);
19087
19207
  await adjustCurrentPhase(tx, item, phaseIdx);
19088
19208
  });
@@ -19095,7 +19215,7 @@ async function removePhase(id, phase) {
19095
19215
 
19096
19216
  // src/commands/backlog/update/update.ts
19097
19217
  import chalk103 from "chalk";
19098
- import { eq as eq42 } from "drizzle-orm";
19218
+ import { eq as eq43 } from "drizzle-orm";
19099
19219
 
19100
19220
  // src/commands/backlog/update/buildUpdateValues.ts
19101
19221
  import chalk100 from "chalk";
@@ -19206,7 +19326,7 @@ async function update(id, options2) {
19206
19326
  if (!built) return;
19207
19327
  const { orm } = found;
19208
19328
  const itemId2 = found.item.id;
19209
- await orm.update(items).set(built.set).where(eq42(items.id, itemId2));
19329
+ await orm.update(items).set(built.set).where(eq43(items.id, itemId2));
19210
19330
  console.log(
19211
19331
  chalk103.green(`Updated ${built.fields} on item ${formatItemId(itemId2)}.`)
19212
19332
  );
@@ -19252,7 +19372,7 @@ function readPlanUpdate(source) {
19252
19372
  }
19253
19373
 
19254
19374
  // src/commands/backlog/updatePlan/replacePlan.ts
19255
- import { eq as eq43 } from "drizzle-orm";
19375
+ import { eq as eq44 } from "drizzle-orm";
19256
19376
 
19257
19377
  // src/commands/backlog/updatePlan/clampCurrentPhase.ts
19258
19378
  function clampCurrentPhase(currentPhase, phaseCount) {
@@ -19263,8 +19383,8 @@ function clampCurrentPhase(currentPhase, phaseCount) {
19263
19383
  // src/commands/backlog/updatePlan/replacePlan.ts
19264
19384
  async function replacePlan(orm, itemId2, phases, currentPhase) {
19265
19385
  await orm.transaction(async (tx) => {
19266
- await tx.delete(planTasks).where(eq43(planTasks.itemId, itemId2));
19267
- await tx.delete(planPhases).where(eq43(planPhases.itemId, itemId2));
19386
+ await tx.delete(planTasks).where(eq44(planTasks.itemId, itemId2));
19387
+ await tx.delete(planPhases).where(eq44(planPhases.itemId, itemId2));
19268
19388
  if (phases.length > 0) {
19269
19389
  await tx.insert(planPhases).values(
19270
19390
  phases.map((phase, idx) => ({
@@ -19282,7 +19402,7 @@ async function replacePlan(orm, itemId2, phases, currentPhase) {
19282
19402
  if (currentPhase === void 0) return;
19283
19403
  const clamped = clampCurrentPhase(currentPhase, phases.length);
19284
19404
  if (clamped === currentPhase) return;
19285
- await tx.update(items).set({ currentPhase: clamped }).where(eq43(items.id, itemId2));
19405
+ await tx.update(items).set({ currentPhase: clamped }).where(eq44(items.id, itemId2));
19286
19406
  });
19287
19407
  }
19288
19408
 
@@ -23018,8 +23138,8 @@ async function setDbBackedConfigKey(key, value, useRepo) {
23018
23138
 
23019
23139
  // src/commands/config/setFileConfigKey.ts
23020
23140
  import chalk134 from "chalk";
23021
- async function setFileConfigKey({ key, useRepo, repoName, global }, coerced) {
23022
- const target = useRepo ? `repo: ${await applyRepoOrExit(key, coerced, repoName)}` : applyOrExit(key, coerced, global);
23141
+ async function setFileConfigKey({ key, useRepo, repoName, global, user }, coerced) {
23142
+ const target = useRepo ? `repo: ${await applyRepoOrExit(key, coerced, repoName)}` : user ? await applyUserOrExit(key, coerced) : applyOrExit(key, coerced, global);
23023
23143
  const shown = JSON.stringify(maskConfigKeySecrets(key, coerced));
23024
23144
  console.log(chalk134.green(`Set ${key} = ${shown} (${target})`));
23025
23145
  }
@@ -23028,20 +23148,30 @@ function applyOrExit(key, coerced, global) {
23028
23148
  if (!result.ok) exitWithConfigErrors(result.errors);
23029
23149
  return result.target;
23030
23150
  }
23151
+ async function applyUserOrExit(key, coerced) {
23152
+ const result = await applyUserConfigSet(key, coerced);
23153
+ if (!result.ok) exitWithConfigErrors(result.errors);
23154
+ return "user (db)";
23155
+ }
23031
23156
  async function applyRepoOrExit(key, coerced, repoName) {
23032
23157
  const result = await applySharedRepoConfigSet(key, coerced, repoName);
23033
23158
  if (!result.ok) exitWithConfigErrors(result.errors);
23034
23159
  return result.label;
23035
23160
  }
23036
23161
 
23162
+ // src/commands/config/scopeOptionsError.ts
23163
+ function scopeOptionsError(options2, repoAction) {
23164
+ if (options2.user && (options2.global || options2.repo !== void 0))
23165
+ return "--user writes user (db); drop -g and --repo";
23166
+ if (options2.repo !== void 0 && !options2.global)
23167
+ return `--repo ${repoAction} the global config; add -g (e.g. -g --repo)`;
23168
+ return void 0;
23169
+ }
23170
+
23037
23171
  // src/commands/config/configSet.ts
23038
23172
  async function configSet(key, value, options2 = {}) {
23039
- if (options2.repo !== void 0 && !options2.global) {
23040
- console.error(
23041
- chalk135.red("--repo writes to the global config; add -g (e.g. -g --repo)")
23042
- );
23043
- process.exit(1);
23044
- }
23173
+ const scopeError = scopeOptionsError(options2, "writes to");
23174
+ if (scopeError) exitWithConfigErrors([scopeError]);
23045
23175
  const resolved = resolveRepoTarget(key, value, options2.repo);
23046
23176
  if (resolved.value === void 0) {
23047
23177
  console.error(chalk135.red(`Missing required argument for '${resolved.key}'`));
@@ -23052,7 +23182,11 @@ async function configSet(key, value, options2 = {}) {
23052
23182
  if (isDbBackedConfigKey(resolved.key))
23053
23183
  return setDbBackedConfigKey(resolved.key, coercion.value, resolved.useRepo);
23054
23184
  await setFileConfigKey(
23055
- { ...resolved, global: options2.global ?? false },
23185
+ {
23186
+ ...resolved,
23187
+ global: options2.global ?? false,
23188
+ user: options2.user ?? false
23189
+ },
23056
23190
  coercion.value
23057
23191
  );
23058
23192
  }
@@ -23086,9 +23220,10 @@ async function unsetFileConfigKey({
23086
23220
  key,
23087
23221
  useRepo,
23088
23222
  repoName,
23089
- global
23223
+ global,
23224
+ user
23090
23225
  }) {
23091
- const result = useRepo ? await applySharedRepoConfigUnset(key, repoName) : applyConfigUnset(key, global);
23226
+ const result = useRepo ? await applySharedRepoConfigUnset(key, repoName) : user ? await applyUserConfigUnset(key) : applyConfigUnset(key, global);
23092
23227
  if (!result.ok) exitWithConfigErrors(result.errors);
23093
23228
  if (!result.removed) {
23094
23229
  console.log(chalk137.yellow(`${key} is not set in ${whereLabel(result)}`));
@@ -23097,22 +23232,20 @@ async function unsetFileConfigKey({
23097
23232
  console.log(chalk137.green(`Unset ${key} (${targetLabel(result)})`));
23098
23233
  }
23099
23234
  function whereLabel(result) {
23100
- return result.target === "repo" ? `repos.${result.label}` : `the ${result.target} config`;
23235
+ if (result.target === "repo") return `repos.${result.label}`;
23236
+ if (result.target === "user") return "user (db)";
23237
+ return `the ${result.target} config`;
23101
23238
  }
23102
23239
  function targetLabel(result) {
23103
- return result.target === "repo" ? `repo: ${result.label}` : result.target;
23240
+ if (result.target === "repo") return `repo: ${result.label}`;
23241
+ if (result.target === "user") return "user (db)";
23242
+ return result.target;
23104
23243
  }
23105
23244
 
23106
23245
  // src/commands/config/configUnset.ts
23107
23246
  async function configUnset(key, options2 = {}) {
23108
- if (options2.repo !== void 0 && !options2.global) {
23109
- console.error(
23110
- chalk138.red(
23111
- "--repo removes from the global config; add -g (e.g. -g --repo)"
23112
- )
23113
- );
23114
- process.exit(1);
23115
- }
23247
+ const scopeError = scopeOptionsError(options2, "removes from");
23248
+ if (scopeError) exitWithConfigErrors([scopeError]);
23116
23249
  const resolved = resolveRepoUnsetTarget(key, options2.repo);
23117
23250
  if (resolved.key === void 0) {
23118
23251
  console.error(chalk138.red("Missing required argument 'key'"));
@@ -23125,25 +23258,34 @@ async function configUnset(key, options2 = {}) {
23125
23258
  key: resolved.key,
23126
23259
  useRepo: resolved.useRepo,
23127
23260
  repoName: resolved.repoName,
23128
- global: options2.global ?? false
23261
+ global: options2.global ?? false,
23262
+ user: options2.user ?? false
23129
23263
  });
23130
23264
  }
23131
23265
 
23132
- // src/commands/registerConfig.ts
23133
- function registerConfig(program) {
23134
- const configCommand = configHelp(
23135
- program.command("config").description("View and modify assist.yml configuration"),
23136
- configConfigHelp,
23137
- "Run 'assist config keys [filter]' to list every config key with its default,\nwhat it does, and the command that sets it."
23138
- );
23266
+ // src/commands/config/registerConfigWrites.ts
23267
+ function registerConfigWrites(configCommand) {
23139
23268
  configCommand.command("set <key> [value]").description("Set a config value (e.g. commit.push true)").option("-g, --global", "Write to global ~/.assist.yml").option(
23140
23269
  "-r, --repo [name]",
23141
23270
  "Requires -g: write a repo's override to the shared db, seen by every node (defaults to the current repo)"
23271
+ ).option(
23272
+ "-u, --user",
23273
+ "Write to user (db): the shared db, applied to every repo on every node"
23142
23274
  ).action((key, value, options2) => configSet(key, value, options2));
23143
23275
  configCommand.command("unset [key]").description("Remove a config value (e.g. commit.push)").option("-g, --global", "Remove from global ~/.assist.yml").option(
23144
23276
  "-r, --repo [name]",
23145
23277
  "Requires -g: remove the key from a repo's shared db override (defaults to the current repo)"
23146
- ).action((key, options2) => configUnset(key, options2));
23278
+ ).option("-u, --user", "Remove from user (db) in the shared db").action((key, options2) => configUnset(key, options2));
23279
+ }
23280
+
23281
+ // src/commands/registerConfig.ts
23282
+ function registerConfig(program) {
23283
+ const configCommand = configHelp(
23284
+ program.command("config").description("View and modify assist.yml configuration"),
23285
+ configConfigHelp,
23286
+ "Run 'assist config keys [filter]' to list every config key with its default,\nwhat it does, and the command that sets it."
23287
+ );
23288
+ registerConfigWrites(configCommand);
23147
23289
  configCommand.command("import-repos").description(
23148
23290
  "Move this machine's ~/.assist.yml repos: overrides into the shared db, showing a per-key diff and asking first"
23149
23291
  ).action(configImportRepos);
@@ -23246,8 +23388,8 @@ import { homedir as homedir21 } from "os";
23246
23388
  import { join as join54 } from "path";
23247
23389
  function snapshotPath(tabUrl, now) {
23248
23390
  const host = new URL(tabUrl).host.replace(/[^\w.-]/g, "-");
23249
- const timestamp7 = now.toISOString().replace(/[:.]/g, "-");
23250
- return join54(homedir21(), ".assist", "browser", `${host}-${timestamp7}.html`);
23391
+ const timestamp8 = now.toISOString().replace(/[:.]/g, "-");
23392
+ return join54(homedir21(), ".assist", "browser", `${host}-${timestamp8}.html`);
23251
23393
  }
23252
23394
 
23253
23395
  // src/commands/browser/browserSnapshot.ts
@@ -27746,10 +27888,10 @@ async function list3() {
27746
27888
  import chalk173 from "chalk";
27747
27889
 
27748
27890
  // src/shared/db/readInboxLastScan.ts
27749
- import { eq as eq44 } from "drizzle-orm";
27891
+ import { eq as eq45 } from "drizzle-orm";
27750
27892
  var INBOX_LAST_SCAN_KEY = "inbox.lastScanAt";
27751
27893
  async function readInboxLastScan(orm) {
27752
- const [row3] = await orm.select().from(metadata).where(eq44(metadata.key, INBOX_LAST_SCAN_KEY));
27894
+ const [row3] = await orm.select().from(metadata).where(eq45(metadata.key, INBOX_LAST_SCAN_KEY));
27753
27895
  return row3 ? new Date(row3.value) : void 0;
27754
27896
  }
27755
27897
 
@@ -30486,9 +30628,9 @@ async function list4() {
30486
30628
  import chalk189 from "chalk";
30487
30629
 
30488
30630
  // src/commands/backlog/removeFeed.ts
30489
- import { eq as eq45 } from "drizzle-orm";
30631
+ import { eq as eq46 } from "drizzle-orm";
30490
30632
  async function removeFeed(db, url) {
30491
- const deleted = await db.delete(feeds).where(eq45(feeds.url, url)).returning({ id: feeds.id });
30633
+ const deleted = await db.delete(feeds).where(eq46(feeds.url, url)).returning({ id: feeds.id });
30492
30634
  return deleted.length > 0;
30493
30635
  }
30494
30636
 
@@ -32601,8 +32743,8 @@ function bucketPr(pr) {
32601
32743
  }
32602
32744
 
32603
32745
  // src/commands/prs/status/describeAge.ts
32604
- function describeAge(timestamp7, now = Date.now()) {
32605
- const parsed = new Date(timestamp7).getTime();
32746
+ function describeAge(timestamp8, now = Date.now()) {
32747
+ const parsed = new Date(timestamp8).getTime();
32606
32748
  if (Number.isNaN(parsed)) return { hours: null, label: "unknown" };
32607
32749
  const hours = Math.max(0, Math.floor((now - parsed) / 36e5));
32608
32750
  if (hours < 1) return { hours, label: "<1h" };
@@ -34173,8 +34315,8 @@ function buildOutputPath(outputDir, processName) {
34173
34315
  if (!existsSync65(outputDir)) {
34174
34316
  mkdirSync24(outputDir, { recursive: true });
34175
34317
  }
34176
- const timestamp7 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
34177
- return resolve24(outputDir, `${processName}-${timestamp7}.png`);
34318
+ const timestamp8 = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
34319
+ return resolve24(outputDir, `${processName}-${timestamp8}.png`);
34178
34320
  }
34179
34321
  function runPowerShellScript(processName, outputPath) {
34180
34322
  const scriptPath = join83(tmpdir7(), `assist-screenshot-${Date.now()}.ps1`);
@@ -41132,8 +41274,8 @@ function filterToSql(filter) {
41132
41274
  // src/commands/seq/fetchSeqData.ts
41133
41275
  function buildDataParams(filter, count10, from, to) {
41134
41276
  const sqlFilter = filterToSql(filter);
41135
- const sql29 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count10}`;
41136
- const params = new URLSearchParams({ q: sql29 });
41277
+ const sql30 = `select @Timestamp, @Level, @Exception, @Message from stream where ${sqlFilter} order by @Timestamp desc limit ${count10}`;
41278
+ const params = new URLSearchParams({ q: sql30 });
41137
41279
  if (from) params.set("rangeStartUtc", from);
41138
41280
  if (to) params.set("rangeEndUtc", to);
41139
41281
  return params;
@@ -41647,26 +41789,26 @@ import chalk259 from "chalk";
41647
41789
  // src/commands/sql/loadConnections.ts
41648
41790
  function loadConnections3() {
41649
41791
  const raw = loadGlobalConfigRaw();
41650
- const sql29 = raw.sql;
41651
- return sql29?.connections ?? [];
41792
+ const sql30 = raw.sql;
41793
+ return sql30?.connections ?? [];
41652
41794
  }
41653
41795
  function saveConnections3(connections) {
41654
41796
  const raw = loadGlobalConfigRaw();
41655
- const sql29 = raw.sql ?? {};
41656
- sql29.connections = connections;
41657
- raw.sql = sql29;
41797
+ const sql30 = raw.sql ?? {};
41798
+ sql30.connections = connections;
41799
+ raw.sql = sql30;
41658
41800
  saveGlobalConfig(raw);
41659
41801
  }
41660
41802
  function getDefaultConnection2() {
41661
41803
  const raw = loadGlobalConfigRaw();
41662
- const sql29 = raw.sql;
41663
- return sql29?.defaultConnection;
41804
+ const sql30 = raw.sql;
41805
+ return sql30?.defaultConnection;
41664
41806
  }
41665
41807
  function setDefaultConnection2(name) {
41666
41808
  const raw = loadGlobalConfigRaw();
41667
- const sql29 = raw.sql ?? {};
41668
- sql29.defaultConnection = name;
41669
- raw.sql = sql29;
41809
+ const sql30 = raw.sql ?? {};
41810
+ sql30.defaultConnection = name;
41811
+ raw.sql = sql30;
41670
41812
  saveGlobalConfig(raw);
41671
41813
  }
41672
41814
 
@@ -41737,9 +41879,9 @@ function resolveConnection3(name) {
41737
41879
  }
41738
41880
 
41739
41881
  // src/commands/sql/sqlConnect.ts
41740
- import sql23 from "mssql";
41882
+ import sql24 from "mssql";
41741
41883
  async function sqlConnect(conn) {
41742
- return await sql23.connect({
41884
+ return await sql24.connect({
41743
41885
  server: conn.server,
41744
41886
  port: conn.port,
41745
41887
  user: conn.user,
@@ -41802,11 +41944,11 @@ var MUTATION_PATTERN = new RegExp(
41802
41944
  `\\b(${MUTATION_KEYWORDS.join("|")})\\b`,
41803
41945
  "i"
41804
41946
  );
41805
- function stripComments(sql29) {
41806
- return sql29.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
41947
+ function stripComments(sql30) {
41948
+ return sql30.replace(/\/\*[\s\S]*?\*\//g, " ").replace(/--[^\n]*/g, " ");
41807
41949
  }
41808
- function isMutation(sql29) {
41809
- const stripped = stripComments(sql29);
41950
+ function isMutation(sql30) {
41951
+ const stripped = stripComments(sql30);
41810
41952
  if (MUTATION_PATTERN.test(stripped)) return true;
41811
41953
  return /\bSELECT\b[\s\S]+\bINTO\s+\w/i.test(stripped);
41812
41954
  }
@@ -44830,12 +44972,12 @@ function parseEnvPairs(pairs) {
44830
44972
  if (pairs.length === 0) return void 0;
44831
44973
  const env = {};
44832
44974
  for (const pair of pairs) {
44833
- const eq48 = pair.indexOf("=");
44834
- if (eq48 <= 0) {
44975
+ const eq49 = pair.indexOf("=");
44976
+ if (eq49 <= 0) {
44835
44977
  console.error(`--env must be KEY=VALUE, got "${pair}"`);
44836
44978
  process.exit(1);
44837
44979
  }
44838
- env[pair.slice(0, eq48)] = pair.slice(eq48 + 1);
44980
+ env[pair.slice(0, eq49)] = pair.slice(eq49 + 1);
44839
44981
  }
44840
44982
  return env;
44841
44983
  }
@@ -44848,11 +44990,11 @@ function fail9(spec) {
44848
44990
  process.exit(1);
44849
44991
  }
44850
44992
  function parseParamSpec(spec) {
44851
- const eq48 = spec.indexOf("=");
44852
- const name = eq48 === -1 ? spec : spec.slice(0, eq48);
44853
- if (eq48 !== -1) {
44854
- if (eq48 === 0 || name.endsWith("!")) fail9(spec);
44855
- return { name, default: spec.slice(eq48 + 1) };
44993
+ const eq49 = spec.indexOf("=");
44994
+ const name = eq49 === -1 ? spec : spec.slice(0, eq49);
44995
+ if (eq49 !== -1) {
44996
+ if (eq49 === 0 || name.endsWith("!")) fail9(spec);
44997
+ return { name, default: spec.slice(eq49 + 1) };
44856
44998
  }
44857
44999
  if (name.endsWith("!")) {
44858
45000
  if (name.length === 1) fail9(spec);
@@ -45362,6 +45504,9 @@ function createAutoExit(exit, graceMs = DEFAULT_GRACE_MS) {
45362
45504
 
45363
45505
  // src/commands/sessions/daemon/pollRepoConfigCache.ts
45364
45506
  var POLL_MS = 6e4;
45507
+ function cacheSnapshot() {
45508
+ return JSON.stringify([readRepoConfigCache(), readUserConfigCache()]);
45509
+ }
45365
45510
  function pollRepoConfigCache(intervalMs = POLL_MS) {
45366
45511
  let inFlight2 = false;
45367
45512
  let failing = false;
@@ -45369,11 +45514,11 @@ function pollRepoConfigCache(intervalMs = POLL_MS) {
45369
45514
  if (inFlight2) return;
45370
45515
  inFlight2 = true;
45371
45516
  try {
45372
- const before = JSON.stringify(readRepoConfigCache());
45517
+ const before = cacheSnapshot();
45373
45518
  const count10 = await refreshRepoConfigCache();
45374
45519
  if (failing) daemonLog("repo config cache poll recovered");
45375
45520
  failing = false;
45376
- if (JSON.stringify(readRepoConfigCache()) !== before)
45521
+ if (cacheSnapshot() !== before)
45377
45522
  daemonLog(`repo config cache updated (${count10} repos)`);
45378
45523
  } catch (error) {
45379
45524
  if (!failing)
@@ -47509,13 +47654,13 @@ function drainSessions(sessions, notify2) {
47509
47654
  }
47510
47655
 
47511
47656
  // src/shared/db/recordPhaseActiveMs.ts
47512
- import { sql as sql24 } from "drizzle-orm";
47657
+ import { sql as sql25 } from "drizzle-orm";
47513
47658
  async function recordPhaseActiveMs(db, itemId2, phaseIdx, activeMs) {
47514
47659
  if (activeMs <= 0) return;
47515
47660
  await db.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, activeMs }).onConflictDoUpdate({
47516
47661
  target: [phaseUsage.itemId, phaseUsage.phaseIdx],
47517
47662
  set: {
47518
- activeMs: sql24`${phaseUsage.activeMs} + excluded.active_ms`
47663
+ activeMs: sql25`${phaseUsage.activeMs} + excluded.active_ms`
47519
47664
  }
47520
47665
  });
47521
47666
  }
@@ -48116,10 +48261,10 @@ function applyReviewPause(session, activity2) {
48116
48261
  }
48117
48262
 
48118
48263
  // src/shared/db/getPhaseActiveMs.ts
48119
- import { and as and18, eq as eq46 } from "drizzle-orm";
48264
+ import { and as and18, eq as eq47 } from "drizzle-orm";
48120
48265
  async function getPhaseActiveMs(db, itemId2, phaseIdx) {
48121
48266
  const [row3] = await db.select({ activeMs: phaseUsage.activeMs }).from(phaseUsage).where(
48122
- and18(eq46(phaseUsage.itemId, itemId2), eq46(phaseUsage.phaseIdx, phaseIdx))
48267
+ and18(eq47(phaseUsage.itemId, itemId2), eq47(phaseUsage.phaseIdx, phaseIdx))
48123
48268
  );
48124
48269
  return row3?.activeMs ?? 0;
48125
48270
  }
@@ -48904,7 +49049,7 @@ async function readTranscriptUsage(transcriptPath2) {
48904
49049
  }
48905
49050
 
48906
49051
  // src/shared/db/recordPhaseCycleContext.ts
48907
- import { sql as sql25 } from "drizzle-orm";
49052
+ import { sql as sql26 } from "drizzle-orm";
48908
49053
  async function recordPhaseCycleContext(db, itemId2, phaseIdx, window, resetsAt, pct) {
48909
49054
  if (pct <= 0) return;
48910
49055
  await db.insert(phaseCycleContext).values({ itemId: itemId2, phaseIdx, window, resetsAt, peakContextPct: pct }).onConflictDoUpdate({
@@ -48915,24 +49060,24 @@ async function recordPhaseCycleContext(db, itemId2, phaseIdx, window, resetsAt,
48915
49060
  phaseCycleContext.resetsAt
48916
49061
  ],
48917
49062
  set: {
48918
- peakContextPct: sql25`GREATEST(${phaseCycleContext.peakContextPct}, ${pct})`
49063
+ peakContextPct: sql26`GREATEST(${phaseCycleContext.peakContextPct}, ${pct})`
48919
49064
  }
48920
49065
  });
48921
49066
  }
48922
49067
 
48923
49068
  // src/shared/db/recordPhasePeakContext.ts
48924
- import { sql as sql26 } from "drizzle-orm";
49069
+ import { sql as sql27 } from "drizzle-orm";
48925
49070
  async function recordPhasePeakContext(db, itemId2, phaseIdx, pct) {
48926
49071
  await db.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, peakContextPct: pct }).onConflictDoUpdate({
48927
49072
  target: [phaseUsage.itemId, phaseUsage.phaseIdx],
48928
49073
  set: {
48929
- peakContextPct: sql26`GREATEST(${phaseUsage.peakContextPct}, ${pct})`
49074
+ peakContextPct: sql27`GREATEST(${phaseUsage.peakContextPct}, ${pct})`
48930
49075
  }
48931
49076
  });
48932
49077
  }
48933
49078
 
48934
49079
  // src/shared/db/recordPhaseTranscriptUsage.ts
48935
- import { sql as sql27 } from "drizzle-orm";
49080
+ import { sql as sql28 } from "drizzle-orm";
48936
49081
  async function recordPhaseTranscriptUsage(db, itemId2, phaseIdx, responses) {
48937
49082
  const byId = /* @__PURE__ */ new Map();
48938
49083
  for (const r of responses) byId.set(r.messageId, r);
@@ -48952,8 +49097,8 @@ async function recordPhaseTranscriptUsage(db, itemId2, phaseIdx, responses) {
48952
49097
  await tx.insert(phaseUsage).values({ itemId: itemId2, phaseIdx, tokensUp, tokensDown }).onConflictDoUpdate({
48953
49098
  target: [phaseUsage.itemId, phaseUsage.phaseIdx],
48954
49099
  set: {
48955
- tokensUp: sql27`${phaseUsage.tokensUp} + ${tokensUp}`,
48956
- tokensDown: sql27`${phaseUsage.tokensDown} + ${tokensDown}`
49100
+ tokensUp: sql28`${phaseUsage.tokensUp} + ${tokensUp}`,
49101
+ tokensDown: sql28`${phaseUsage.tokensDown} + ${tokensDown}`
48957
49102
  }
48958
49103
  });
48959
49104
  return { tokensUp, tokensDown };
@@ -48961,17 +49106,17 @@ async function recordPhaseTranscriptUsage(db, itemId2, phaseIdx, responses) {
48961
49106
  }
48962
49107
 
48963
49108
  // src/shared/db/recordWindowTokens.ts
48964
- import { and as and19, eq as eq47, sql as sql28 } from "drizzle-orm";
49109
+ import { and as and19, eq as eq48, sql as sql29 } from "drizzle-orm";
48965
49110
  async function recordWindowTokens(db, window, resetsAt, tokensUp, tokensDown) {
48966
49111
  if (tokensUp <= 0 && tokensDown <= 0) return;
48967
49112
  await db.update(usagePeaks).set({
48968
- tokensUp: sql28`${usagePeaks.tokensUp} + ${tokensUp}`,
48969
- tokensDown: sql28`${usagePeaks.tokensDown} + ${tokensDown}`
49113
+ tokensUp: sql29`${usagePeaks.tokensUp} + ${tokensUp}`,
49114
+ tokensDown: sql29`${usagePeaks.tokensDown} + ${tokensDown}`
48970
49115
  }).where(
48971
49116
  and19(
48972
- eq47(usagePeaks.window, window),
48973
- eq47(usagePeaks.resetsAt, resetsAt),
48974
- eq47(usagePeaks.segment, 0)
49117
+ eq48(usagePeaks.window, window),
49118
+ eq48(usagePeaks.resetsAt, resetsAt),
49119
+ eq48(usagePeaks.segment, 0)
48975
49120
  )
48976
49121
  );
48977
49122
  }