@staff0rd/assist 0.482.1 → 0.483.1

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.482.1",
9
+ version: "0.483.1",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -399,7 +399,7 @@ var DEFAULT_WAKE_WORDS = ["computer"];
399
399
  var DEFAULT_MODELS_DIR = "~/.assist/voice/models";
400
400
  var DEFAULT_BACKUP_DIR = "~/.assist/backups";
401
401
  var DEFAULT_CLONE_DIR = "~/git";
402
- var assistConfigSchema = z2.strictObject({
402
+ var assistConfigShape = {
403
403
  backup: z2.strictObject({
404
404
  dir: z2.string().default(DEFAULT_BACKUP_DIR)
405
405
  }).default({ dir: DEFAULT_BACKUP_DIR }),
@@ -451,7 +451,6 @@ var assistConfigSchema = z2.strictObject({
451
451
  required: z2.boolean().default(false),
452
452
  promptJira: z2.boolean().default(false)
453
453
  }).optional(),
454
- repos: z2.record(z2.string(), z2.record(z2.string(), z2.unknown())).optional(),
455
454
  worktree: z2.strictObject({
456
455
  enabled: z2.boolean().default(false),
457
456
  trunk: z2.boolean().default(false),
@@ -571,6 +570,11 @@ var assistConfigSchema = z2.strictObject({
571
570
  modelsDir: DEFAULT_MODELS_DIR,
572
571
  models: {}
573
572
  })
573
+ };
574
+ var repoConfigSchema = z2.strictObject(assistConfigShape).partial();
575
+ var assistConfigSchema = z2.strictObject({
576
+ ...assistConfigShape,
577
+ repos: z2.record(z2.string(), repoConfigSchema).optional()
574
578
  });
575
579
 
576
580
  // src/shared/loadConfigFrom.ts
@@ -9568,7 +9572,7 @@ function readConfigEntries(cwd) {
9568
9572
  value: getNestedValue(config, leaf.key),
9569
9573
  source,
9570
9574
  sources,
9571
- ...source === "repo" && layers.repoKey ? { repoKey: layers.repoKey } : {},
9575
+ ...layers.repoKey ? { repoKey: layers.repoKey } : {},
9572
9576
  globalOnly: isGlobalOnlyConfigKey(leaf.key),
9573
9577
  node: configEntryNode(schema2, leaf.key)
9574
9578
  };
@@ -10402,90 +10406,6 @@ function sessionView(_req, res) {
10402
10406
  respondJson(res, 200, { floatWaiting });
10403
10407
  }
10404
10408
 
10405
- // src/commands/config/setNestedValue.ts
10406
- function isPlainObject2(val) {
10407
- return val !== null && typeof val === "object" && !Array.isArray(val);
10408
- }
10409
- function isNumericKey(key) {
10410
- return /^\d+$/.test(key);
10411
- }
10412
- function resolveKey(key) {
10413
- return isNumericKey(key) ? Number.parseInt(key, 10) : key;
10414
- }
10415
- function getItem(container, key) {
10416
- if (Array.isArray(container)) return container[key];
10417
- return container[key];
10418
- }
10419
- function setItem(container, key, value) {
10420
- if (Array.isArray(container)) container[key] = value;
10421
- else container[key] = value;
10422
- }
10423
- function ensureArray(container, key) {
10424
- const existing = getItem(container, key);
10425
- const arr = Array.isArray(existing) ? [...existing] : [];
10426
- setItem(container, key, arr);
10427
- return arr;
10428
- }
10429
- function ensureObject(container, key) {
10430
- const existing = getItem(container, key);
10431
- const obj = isPlainObject2(existing) ? { ...existing } : {};
10432
- setItem(container, key, obj);
10433
- return obj;
10434
- }
10435
- function stepIntoNested(container, key, nextKey) {
10436
- const resolved = resolveKey(key);
10437
- if (nextKey !== void 0 && isNumericKey(nextKey)) {
10438
- return ensureArray(container, resolved);
10439
- }
10440
- return ensureObject(container, resolved);
10441
- }
10442
- function setNestedValue(obj, path71, value) {
10443
- const keys = path71.split(".");
10444
- const result = { ...obj };
10445
- let current = result;
10446
- for (let i = 0; i < keys.length - 1; i++) {
10447
- current = stepIntoNested(current, keys[i], keys[i + 1]);
10448
- }
10449
- setItem(current, resolveKey(keys[keys.length - 1]), value);
10450
- return result;
10451
- }
10452
-
10453
- // src/commands/config/validateConfig.ts
10454
- function validateConfig(updated, key) {
10455
- const result = assistConfigSchema.safeParse(updated);
10456
- if (result.success) return { ok: true };
10457
- return {
10458
- ok: false,
10459
- errors: result.error.issues.map((issue) => formatIssue(issue, key))
10460
- };
10461
- }
10462
- function formatIssue(issue, key) {
10463
- const path71 = issue.path.length > 0 ? issue.path.join(".") : key;
10464
- return `${path71}: ${issue.message}`;
10465
- }
10466
-
10467
- // src/commands/config/applyConfigSet.ts
10468
- function applyConfigSet(key, coerced, global, cwd = process.cwd()) {
10469
- if (!global && isGlobalOnlyConfigKey(key)) {
10470
- return {
10471
- ok: false,
10472
- errors: [
10473
- `"${key}" is a global-only key. Use --global to set it in ~/.assist.yml`
10474
- ]
10475
- };
10476
- }
10477
- const raw = global ? loadGlobalConfigRaw() : loadProjectConfig(cwd);
10478
- const updated = setNestedValue(raw, key, coerced);
10479
- const validation = validateConfig(updated, key);
10480
- if (!validation.ok) return validation;
10481
- if (global) {
10482
- saveGlobalConfig(updated);
10483
- return { ok: true, target: "global" };
10484
- }
10485
- saveConfig(updated, cwd);
10486
- return { ok: true, target: "project" };
10487
- }
10488
-
10489
10409
  // src/commands/config/coerceConfigScalar.ts
10490
10410
  function coerceConfigScalar(key, raw, type) {
10491
10411
  if (type === "boolean") return coerceBoolean(key, raw);
@@ -10534,10 +10454,10 @@ function coerceStructuredConfigValue(key, raw) {
10534
10454
  return { ok: true, value: parsed.data };
10535
10455
  return {
10536
10456
  ok: false,
10537
- error: parsed.error.issues.map((issue) => formatIssue2(key, issue)).join("\n")
10457
+ error: parsed.error.issues.map((issue) => formatIssue(key, issue)).join("\n")
10538
10458
  };
10539
10459
  }
10540
- function formatIssue2(key, issue) {
10460
+ function formatIssue(key, issue) {
10541
10461
  const path71 = formatConfigPath([
10542
10462
  { kind: "key", name: key },
10543
10463
  ...issue.path.map(toSegment)
@@ -10591,6 +10511,152 @@ function coerceUnion(key, raw, types) {
10591
10511
  return { ok: false, error: `${key}: expected ${types.join(" or ")}` };
10592
10512
  }
10593
10513
 
10514
+ // src/commands/config/setNestedValue.ts
10515
+ function isPlainObject2(val) {
10516
+ return val !== null && typeof val === "object" && !Array.isArray(val);
10517
+ }
10518
+ function isNumericKey(key) {
10519
+ return /^\d+$/.test(key);
10520
+ }
10521
+ function resolveKey(key) {
10522
+ return isNumericKey(key) ? Number.parseInt(key, 10) : key;
10523
+ }
10524
+ function getItem(container, key) {
10525
+ if (Array.isArray(container)) return container[key];
10526
+ return container[key];
10527
+ }
10528
+ function setItem(container, key, value) {
10529
+ if (Array.isArray(container)) container[key] = value;
10530
+ else container[key] = value;
10531
+ }
10532
+ function ensureArray(container, key) {
10533
+ const existing = getItem(container, key);
10534
+ const arr = Array.isArray(existing) ? [...existing] : [];
10535
+ setItem(container, key, arr);
10536
+ return arr;
10537
+ }
10538
+ function ensureObject(container, key) {
10539
+ const existing = getItem(container, key);
10540
+ const obj = isPlainObject2(existing) ? { ...existing } : {};
10541
+ setItem(container, key, obj);
10542
+ return obj;
10543
+ }
10544
+ function stepIntoNested(container, key, nextKey) {
10545
+ const resolved = resolveKey(key);
10546
+ if (nextKey !== void 0 && isNumericKey(nextKey)) {
10547
+ return ensureArray(container, resolved);
10548
+ }
10549
+ return ensureObject(container, resolved);
10550
+ }
10551
+ function setNestedValue(obj, path71, value) {
10552
+ const keys = path71.split(".");
10553
+ const result = { ...obj };
10554
+ let current = result;
10555
+ for (let i = 0; i < keys.length - 1; i++) {
10556
+ current = stepIntoNested(current, keys[i], keys[i + 1]);
10557
+ }
10558
+ setItem(current, resolveKey(keys[keys.length - 1]), value);
10559
+ return result;
10560
+ }
10561
+
10562
+ // src/commands/config/validateConfig.ts
10563
+ function validateConfig(updated, key, schema2 = assistConfigSchema) {
10564
+ const result = schema2.safeParse(updated);
10565
+ if (result.success) return { ok: true };
10566
+ return {
10567
+ ok: false,
10568
+ errors: result.error.issues.map((issue) => formatIssue2(issue, key))
10569
+ };
10570
+ }
10571
+ function formatIssue2(issue, key) {
10572
+ const path71 = issue.path.length > 0 ? issue.path.join(".") : key;
10573
+ return `${path71}: ${issue.message}`;
10574
+ }
10575
+
10576
+ // src/commands/config/applyConfigSet.ts
10577
+ function applyConfigSet(key, coerced, global, cwd = process.cwd()) {
10578
+ if (!global && isGlobalOnlyConfigKey(key)) {
10579
+ return {
10580
+ ok: false,
10581
+ errors: [
10582
+ `"${key}" is a global-only key. Use --global to set it in ~/.assist.yml`
10583
+ ]
10584
+ };
10585
+ }
10586
+ const raw = global ? loadGlobalConfigRaw() : loadProjectConfig(cwd);
10587
+ const updated = setNestedValue(raw, key, coerced);
10588
+ const validation = validateConfig(updated, key);
10589
+ if (!validation.ok) return validation;
10590
+ if (global) {
10591
+ saveGlobalConfig(updated);
10592
+ return { ok: true, target: "global" };
10593
+ }
10594
+ saveConfig(updated, cwd);
10595
+ return { ok: true, target: "project" };
10596
+ }
10597
+
10598
+ // src/shared/resolveNamedRepoWriteLabel.ts
10599
+ var UnknownRepoConfigError = class extends Error {
10600
+ name = "UnknownRepoConfigError";
10601
+ };
10602
+ function resolveNamedRepoWriteLabel(globalRaw, name) {
10603
+ const repos2 = globalRaw.repos;
10604
+ const entries = repos2 && typeof repos2 === "object" && !Array.isArray(repos2) ? repos2 : {};
10605
+ const candidates = originKeyCandidates(name);
10606
+ const matches = Object.keys(entries).filter((key) => candidates.has(key));
10607
+ if (matches.length === 0) {
10608
+ throw new UnknownRepoConfigError(
10609
+ `No repo in ~/.assist.yml matches "${name}". Use an existing repos key (full origin, org/repo, or bare repo name).`
10610
+ );
10611
+ }
10612
+ if (matches.length > 1) {
10613
+ throw new AmbiguousRepoConfigError(
10614
+ `Ambiguous repos config in ~/.assist.yml: keys ${matches.map((m) => `"${m}"`).join(", ")} all match "${name}". Keep a single key per repository.`
10615
+ );
10616
+ }
10617
+ return matches[0];
10618
+ }
10619
+
10620
+ // src/commands/config/applyRepoConfigSet.ts
10621
+ function applyRepoConfigSet(key, coerced, repoName, cwd = process.cwd()) {
10622
+ if (isGlobalOnlyConfigKey(key)) {
10623
+ return {
10624
+ ok: false,
10625
+ errors: [
10626
+ `"${key}" is a global-only key. Set it in ~/.assist.yml rather than under repos:`
10627
+ ]
10628
+ };
10629
+ }
10630
+ const globalRaw = loadGlobalConfigRaw();
10631
+ const label2 = repoName === void 0 ? resolveRepoWriteLabel(globalRaw, getCurrentOrigin(cwd)) : resolveNamedRepoWriteLabel(globalRaw, repoName);
10632
+ const repos2 = isPlainObject3(globalRaw.repos) ? { ...globalRaw.repos } : {};
10633
+ const existingBlock = isPlainObject3(repos2[label2]) ? repos2[label2] : {};
10634
+ const updatedBlock = setNestedValue(existingBlock, key, coerced);
10635
+ const validation = validateConfig(updatedBlock, key, repoConfigSchema);
10636
+ if (!validation.ok) return validation;
10637
+ repos2[label2] = updatedBlock;
10638
+ saveGlobalConfig({ ...globalRaw, repos: repos2 });
10639
+ return { ok: true, target: "repo", label: label2 };
10640
+ }
10641
+ function isPlainObject3(value) {
10642
+ return value !== null && typeof value === "object" && !Array.isArray(value);
10643
+ }
10644
+
10645
+ // src/commands/sessions/web/applyScopedConfigSet.ts
10646
+ function applyScopedConfigSet(key, value, cwd, scope) {
10647
+ if (scope === "repo") {
10648
+ const result2 = applyRepoConfigSet(key, value, void 0, cwd);
10649
+ return result2.ok ? { ok: true, payload: { target: result2.target, repoKey: result2.label } } : result2;
10650
+ }
10651
+ const result = applyConfigSet(key, value, scope === "global", cwd);
10652
+ return result.ok ? { ok: true, payload: { target: result.target } } : result;
10653
+ }
10654
+
10655
+ // src/commands/config/ConfigWriteScope.ts
10656
+ function isConfigWriteScope(value) {
10657
+ return value === "project" || value === "global" || value === "repo";
10658
+ }
10659
+
10594
10660
  // src/commands/sessions/web/readJsonBody.ts
10595
10661
  async function readJsonBody(req) {
10596
10662
  const chunks = [];
@@ -10611,9 +10677,9 @@ async function parseConfigWriteRequest(req) {
10611
10677
  return { ok: false, error: "Missing key" };
10612
10678
  if (typeof cwd !== "string" || cwd === "")
10613
10679
  return { ok: false, error: "Missing cwd" };
10614
- if (scope !== "project" && scope !== "global")
10615
- return { ok: false, error: 'scope must be "project" or "global"' };
10616
- return { ok: true, key, value: body.value, cwd, global: scope === "global" };
10680
+ if (!isConfigWriteScope(scope))
10681
+ return { ok: false, error: 'scope must be "project", "global" or "repo"' };
10682
+ return { ok: true, key, value: body.value, cwd, scope };
10617
10683
  }
10618
10684
 
10619
10685
  // src/commands/sessions/web/handleConfigWrite.ts
@@ -10645,22 +10711,21 @@ function setConfig(req, res) {
10645
10711
  return handleConfigWrite(req, res, (request) => {
10646
10712
  const coerced = coerceConfigValue(request.key, request.value);
10647
10713
  if (!coerced.ok) return { ok: false, errors: [coerced.error] };
10648
- const result = applyConfigSet(
10714
+ return applyScopedConfigSet(
10649
10715
  request.key,
10650
10716
  coerced.value,
10651
- request.global,
10652
- request.cwd
10717
+ request.cwd,
10718
+ request.scope
10653
10719
  );
10654
- return result.ok ? { ok: true, payload: { target: result.target } } : result;
10655
10720
  });
10656
10721
  }
10657
10722
 
10658
10723
  // src/commands/config/unsetNestedValue.ts
10659
- function isPlainObject3(val) {
10724
+ function isPlainObject4(val) {
10660
10725
  return val !== null && typeof val === "object" && !Array.isArray(val);
10661
10726
  }
10662
10727
  function isContainer(val) {
10663
- return Array.isArray(val) || isPlainObject3(val);
10728
+ return Array.isArray(val) || isPlainObject4(val);
10664
10729
  }
10665
10730
  function isNumericKey2(key) {
10666
10731
  return /^\d+$/.test(key);
@@ -10696,7 +10761,7 @@ function withChild(container, key, child) {
10696
10761
  return { ...container, [key]: child };
10697
10762
  }
10698
10763
  function isPrunable(container) {
10699
- return isPlainObject3(container) && Object.keys(container).length === 0;
10764
+ return isPlainObject4(container) && Object.keys(container).length === 0;
10700
10765
  }
10701
10766
  function replaceOrPrune(container, key, child) {
10702
10767
  return isPrunable(child) ? withoutKey(container, key) : withChild(container, key, child);
@@ -10755,7 +10820,18 @@ function unsetConfig(req, res) {
10755
10820
  return handleConfigWrite(req, res, (request) => {
10756
10821
  if (!isKnownConfigKey(request.key))
10757
10822
  return { ok: false, errors: [`Unknown config key "${request.key}"`] };
10758
- const result = applyConfigUnset(request.key, request.global, request.cwd);
10823
+ if (request.scope === "repo")
10824
+ return {
10825
+ ok: false,
10826
+ errors: [
10827
+ "Clearing a repos override is not supported \u2014 remove the key from repos: in ~/.assist.yml"
10828
+ ]
10829
+ };
10830
+ const result = applyConfigUnset(
10831
+ request.key,
10832
+ request.scope === "global",
10833
+ request.cwd
10834
+ );
10759
10835
  return result.ok ? {
10760
10836
  ok: true,
10761
10837
  payload: { target: result.target, removed: result.removed }
@@ -16166,28 +16242,6 @@ function exitKeyNotSet(key) {
16166
16242
  // src/commands/config/configSet.ts
16167
16243
  import chalk131 from "chalk";
16168
16244
 
16169
- // src/shared/resolveNamedRepoWriteLabel.ts
16170
- var UnknownRepoConfigError = class extends Error {
16171
- name = "UnknownRepoConfigError";
16172
- };
16173
- function resolveNamedRepoWriteLabel(globalRaw, name) {
16174
- const repos2 = globalRaw.repos;
16175
- const entries = repos2 && typeof repos2 === "object" && !Array.isArray(repos2) ? repos2 : {};
16176
- const candidates = originKeyCandidates(name);
16177
- const matches = Object.keys(entries).filter((key) => candidates.has(key));
16178
- if (matches.length === 0) {
16179
- throw new UnknownRepoConfigError(
16180
- `No repo in ~/.assist.yml matches "${name}". Use an existing repos key (full origin, org/repo, or bare repo name).`
16181
- );
16182
- }
16183
- if (matches.length > 1) {
16184
- throw new AmbiguousRepoConfigError(
16185
- `Ambiguous repos config in ~/.assist.yml: keys ${matches.map((m) => `"${m}"`).join(", ")} all match "${name}". Keep a single key per repository.`
16186
- );
16187
- }
16188
- return matches[0];
16189
- }
16190
-
16191
16245
  // src/commands/config/exitWithConfigErrors.ts
16192
16246
  import chalk130 from "chalk";
16193
16247
  function exitWithConfigErrors(errors) {
@@ -16195,23 +16249,6 @@ function exitWithConfigErrors(errors) {
16195
16249
  process.exit(1);
16196
16250
  }
16197
16251
 
16198
- // src/commands/config/applyRepoConfigSet.ts
16199
- function applyRepoConfigSet(key, coerced, repoName) {
16200
- const globalRaw = loadGlobalConfigRaw();
16201
- const label2 = repoName === void 0 ? resolveRepoWriteLabel(globalRaw, getCurrentOrigin(process.cwd())) : resolveNamedRepoWriteLabel(globalRaw, repoName);
16202
- const repos2 = isPlainObject4(globalRaw.repos) ? { ...globalRaw.repos } : {};
16203
- const existingBlock = isPlainObject4(repos2[label2]) ? repos2[label2] : {};
16204
- const updatedBlock = setNestedValue(existingBlock, key, coerced);
16205
- const validation = validateConfig(updatedBlock, key);
16206
- if (!validation.ok) exitWithConfigErrors(validation.errors);
16207
- repos2[label2] = updatedBlock;
16208
- saveGlobalConfig({ ...globalRaw, repos: repos2 });
16209
- return label2;
16210
- }
16211
- function isPlainObject4(value) {
16212
- return value !== null && typeof value === "object" && !Array.isArray(value);
16213
- }
16214
-
16215
16252
  // src/commands/config/resolveRepoTarget.ts
16216
16253
  function resolveRepoTarget(key, value, repo) {
16217
16254
  if (typeof repo === "string") {
@@ -16237,7 +16274,7 @@ function configSet(key, value, options2 = {}) {
16237
16274
  process.exit(1);
16238
16275
  }
16239
16276
  const coerced = coerceValue(resolved.value);
16240
- const target = resolved.useRepo ? `repo: ${applyRepoConfigSet(resolved.key, coerced, resolved.repoName)}` : applyOrExit(resolved.key, coerced, options2.global ?? false);
16277
+ const target = resolved.useRepo ? `repo: ${applyRepoOrExit(resolved.key, coerced, resolved.repoName)}` : applyOrExit(resolved.key, coerced, options2.global ?? false);
16241
16278
  console.log(
16242
16279
  chalk131.green(`Set ${resolved.key} = ${JSON.stringify(coerced)} (${target})`)
16243
16280
  );
@@ -16247,6 +16284,11 @@ function applyOrExit(key, coerced, global) {
16247
16284
  if (!result.ok) exitWithConfigErrors(result.errors);
16248
16285
  return result.target;
16249
16286
  }
16287
+ function applyRepoOrExit(key, coerced, repoName) {
16288
+ const result = applyRepoConfigSet(key, coerced, repoName);
16289
+ if (!result.ok) exitWithConfigErrors(result.errors);
16290
+ return result.label;
16291
+ }
16250
16292
  function coerceValue(value) {
16251
16293
  if (value === "true") return true;
16252
16294
  if (value === "false") return false;
@@ -31582,11 +31624,32 @@ function safeParse2(line) {
31582
31624
  }
31583
31625
  }
31584
31626
 
31627
+ // src/commands/sessions/daemon/repoDirExists.ts
31628
+ import { existsSync as existsSync71 } from "fs";
31629
+ function repoDirExists(cwd) {
31630
+ return existsSync71(toGitCwd(cwd));
31631
+ }
31632
+
31585
31633
  // src/commands/sessions/daemon/withRepoGroups.ts
31586
31634
  function withRepoGroups(sessions) {
31635
+ const existence = /* @__PURE__ */ new Map();
31636
+ const exists = (dir) => {
31637
+ const cached = existence.get(dir);
31638
+ if (cached !== void 0) return cached;
31639
+ const result = repoDirExists(dir);
31640
+ existence.set(dir, result);
31641
+ return result;
31642
+ };
31587
31643
  return sessions.map((session) => {
31588
31644
  const repoGroup = repoGroupForCwd(session.cwd);
31589
- return repoGroup ? { ...session, repoGroup } : session;
31645
+ const dir = repoGroup?.clone || session.cwd;
31646
+ const cwdMissing = Boolean(dir) && !exists(dir);
31647
+ if (!repoGroup && !cwdMissing) return session;
31648
+ return {
31649
+ ...session,
31650
+ ...repoGroup && { repoGroup },
31651
+ ...cwdMissing && { cwdMissing }
31652
+ };
31590
31653
  });
31591
31654
  }
31592
31655
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.482.1",
3
+ "version": "0.483.1",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {