@staff0rd/assist 0.454.1 → 0.456.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.454.1",
9
+ version: "0.456.0",
10
10
  type: "module",
11
11
  main: "dist/index.js",
12
12
  bin: {
@@ -3587,7 +3587,7 @@ function unwrapSchemaNode(node) {
3587
3587
  return { inner: current, defaultValue };
3588
3588
  }
3589
3589
 
3590
- // src/shared/describeConfigLeaves.ts
3590
+ // src/shared/scalarUnionMembers.ts
3591
3591
  var LEAF_TYPES = {
3592
3592
  string: "string",
3593
3593
  number: "number",
@@ -3596,9 +3596,39 @@ var LEAF_TYPES = {
3596
3596
  array: "array",
3597
3597
  record: "record"
3598
3598
  };
3599
- function leafType(node) {
3599
+ var SCALAR_LEAF_TYPES = /* @__PURE__ */ new Set([
3600
+ "string",
3601
+ "number",
3602
+ "boolean",
3603
+ "enum"
3604
+ ]);
3605
+ function leafTypeOf(node) {
3600
3606
  return LEAF_TYPES[node.def?.type ?? ""] ?? "other";
3601
3607
  }
3608
+ function isScalarLeafType(type) {
3609
+ return SCALAR_LEAF_TYPES.has(type);
3610
+ }
3611
+ function scalarUnionMembers(node) {
3612
+ const options2 = node.def?.options;
3613
+ if (node.def?.type !== "union" || !options2?.length) return void 0;
3614
+ const members = [];
3615
+ for (const option of options2) {
3616
+ const type = leafTypeOf(unwrapSchemaNode(option).inner);
3617
+ if (!isScalarLeafType(type)) return void 0;
3618
+ members.push(type);
3619
+ }
3620
+ return members;
3621
+ }
3622
+
3623
+ // src/shared/scalarArrayItemType.ts
3624
+ function scalarArrayItemType(node) {
3625
+ const element = node.def?.element;
3626
+ if (node.def?.type !== "array" || !element) return void 0;
3627
+ const type = leafTypeOf(unwrapSchemaNode(element).inner);
3628
+ return isScalarLeafType(type) ? type : void 0;
3629
+ }
3630
+
3631
+ // src/shared/describeConfigLeaves.ts
3602
3632
  function enumValues(node) {
3603
3633
  const entries = node.def?.entries;
3604
3634
  if (!entries) return void 0;
@@ -3607,9 +3637,13 @@ function enumValues(node) {
3607
3637
  );
3608
3638
  }
3609
3639
  function toLeaf(key, node, defaultValue) {
3610
- const type = leafType(node);
3640
+ const unionTypes = scalarUnionMembers(node);
3641
+ const type = unionTypes ? "union" : leafTypeOf(node);
3611
3642
  const leaf = { key, type };
3612
3643
  if (type === "enum") leaf.enumValues = enumValues(node);
3644
+ if (unionTypes) leaf.unionTypes = unionTypes;
3645
+ const itemType = scalarArrayItemType(node);
3646
+ if (itemType) leaf.itemType = itemType;
3613
3647
  if (defaultValue !== void 0) leaf.defaultValue = defaultValue;
3614
3648
  return leaf;
3615
3649
  }
@@ -10052,21 +10086,10 @@ function applyConfigSet(key, coerced, global, cwd = process.cwd()) {
10052
10086
  return { ok: true, target: "project" };
10053
10087
  }
10054
10088
 
10055
- // src/commands/config/coerceConfigValue.ts
10056
- var EDITABLE_TYPES = /* @__PURE__ */ new Set(["string", "number", "boolean", "enum"]);
10057
- function coerceConfigValue(key, raw) {
10058
- const leaf = describeConfigLeaves(assistConfigSchema).find(
10059
- (candidate) => candidate.key === key
10060
- );
10061
- if (!leaf) return { ok: false, error: `Unknown config key "${key}"` };
10062
- if (!EDITABLE_TYPES.has(leaf.type)) {
10063
- return {
10064
- ok: false,
10065
- error: `"${key}" is a ${leaf.type} value and is read-only here`
10066
- };
10067
- }
10068
- if (leaf.type === "boolean") return coerceBoolean(key, raw);
10069
- if (leaf.type === "number") return coerceNumber(key, raw);
10089
+ // src/commands/config/coerceConfigScalar.ts
10090
+ function coerceConfigScalar(key, raw, type) {
10091
+ if (type === "boolean") return coerceBoolean(key, raw);
10092
+ if (type === "number") return coerceNumber(key, raw);
10070
10093
  return coerceString(key, raw);
10071
10094
  }
10072
10095
  function coerceBoolean(key, raw) {
@@ -10090,6 +10113,53 @@ function coerceString(key, raw) {
10090
10113
  return { ok: false, error: `${key}: expected a string` };
10091
10114
  }
10092
10115
 
10116
+ // src/commands/config/coerceConfigValue.ts
10117
+ var EDITABLE_TYPES = /* @__PURE__ */ new Set([
10118
+ "string",
10119
+ "number",
10120
+ "boolean",
10121
+ "enum",
10122
+ "union"
10123
+ ]);
10124
+ function coerceConfigValue(key, raw) {
10125
+ const leaf = describeConfigLeaves(assistConfigSchema).find(
10126
+ (candidate) => candidate.key === key
10127
+ );
10128
+ if (!leaf) return { ok: false, error: `Unknown config key "${key}"` };
10129
+ if (leaf.itemType) return coerceScalarList(key, raw, leaf.itemType);
10130
+ if (!EDITABLE_TYPES.has(leaf.type)) {
10131
+ return {
10132
+ ok: false,
10133
+ error: `"${key}" is a ${leaf.type} value and is read-only here`
10134
+ };
10135
+ }
10136
+ if (leaf.type === "union")
10137
+ return coerceUnion(key, raw, leaf.unionTypes ?? []);
10138
+ return coerceConfigScalar(key, raw, leaf.type);
10139
+ }
10140
+ function coerceScalarList(key, raw, itemType) {
10141
+ if (!Array.isArray(raw)) {
10142
+ return {
10143
+ ok: false,
10144
+ error: `${key}: expected a list of ${itemType} values`
10145
+ };
10146
+ }
10147
+ const values = [];
10148
+ for (const item of raw) {
10149
+ const result = coerceConfigScalar(key, item, itemType);
10150
+ if (!result.ok) return result;
10151
+ values.push(result.value);
10152
+ }
10153
+ return { ok: true, value: values };
10154
+ }
10155
+ function coerceUnion(key, raw, types) {
10156
+ for (const type of types) {
10157
+ const result = coerceConfigScalar(key, raw, type);
10158
+ if (result.ok) return result;
10159
+ }
10160
+ return { ok: false, error: `${key}: expected ${types.join(" or ")}` };
10161
+ }
10162
+
10093
10163
  // src/commands/sessions/web/readJsonBody.ts
10094
10164
  async function readJsonBody(req) {
10095
10165
  const chunks = [];
@@ -17168,8 +17238,8 @@ function printRepoAuthorBreakdown(repos2) {
17168
17238
  }
17169
17239
 
17170
17240
  // src/commands/github/printCommitsReport.ts
17171
- function printCommitsReport(report) {
17172
- const { org, since, totalRepos, repos: repos2, committers, repoAuthors } = report;
17241
+ function printCommitsReport(report2) {
17242
+ const { org, since, totalRepos, repos: repos2, committers, repoAuthors } = report2;
17173
17243
  const scope = repos2.length < totalRepos ? ` (top ${repos2.length} of ${totalRepos} repos)` : "";
17174
17244
  console.log(`Top repos by commits in ${org} since ${since}${scope}:
17175
17245
  `);
@@ -17198,7 +17268,7 @@ function commits(org, options2) {
17198
17268
  name: repo.name,
17199
17269
  authors: fetchRepoCommitAuthors(org, repo.name, since)
17200
17270
  }));
17201
- const report = {
17271
+ const report2 = {
17202
17272
  org,
17203
17273
  since: sinceDate,
17204
17274
  totalRepos: allRepos.length,
@@ -17207,11 +17277,11 @@ function commits(org, options2) {
17207
17277
  repoAuthors
17208
17278
  };
17209
17279
  if (options2.json) {
17210
- console.log(JSON.stringify(report, null, 2));
17280
+ console.log(JSON.stringify(report2, null, 2));
17211
17281
  } else if (repos2.length === 0) {
17212
17282
  console.log(`No commits found in ${org} since ${sinceDate}.`);
17213
17283
  } else {
17214
- printCommitsReport(report);
17284
+ printCommitsReport(report2);
17215
17285
  }
17216
17286
  }
17217
17287
 
@@ -25482,6 +25552,243 @@ function registerVoice(program2) {
25482
25552
  configHelp(voiceCommand, voiceConfigHelp);
25483
25553
  }
25484
25554
 
25555
+ // src/commands/watch/describeOutcome.ts
25556
+ var short = (sha) => sha.slice(0, 7);
25557
+ function describeOutcome(outcome) {
25558
+ switch (outcome.kind) {
25559
+ case "moved":
25560
+ return {
25561
+ exitCode: 0,
25562
+ message: `remote moved: ${short(outcome.from)}..${short(outcome.to)} (${outcome.count} commit${outcome.count === 1 ? "" : "s"})`
25563
+ };
25564
+ case "timeout":
25565
+ return {
25566
+ exitCode: 2,
25567
+ message: `no movement on ${outcome.upstream} after ${outcome.timeout}`
25568
+ };
25569
+ case "unavailable":
25570
+ return { exitCode: 1, message: `cannot wait: ${outcome.reason}` };
25571
+ case "interrupted":
25572
+ return { exitCode: 130, message: "interrupted" };
25573
+ }
25574
+ }
25575
+
25576
+ // src/commands/watch/describePull.ts
25577
+ function describePull(result) {
25578
+ if (result.kind === "fast-forwarded") {
25579
+ return {
25580
+ exitCode: 0,
25581
+ message: `pulled --ff-only \u2192 ${result.sha.slice(0, 7)}`
25582
+ };
25583
+ }
25584
+ return {
25585
+ exitCode: 3,
25586
+ message: `pull was not a fast-forward:
25587
+ ${result.reason}`
25588
+ };
25589
+ }
25590
+
25591
+ // src/commands/watch/parseDuration.ts
25592
+ var UNIT_MS = { s: 1e3, m: 6e4, h: 36e5 };
25593
+ function parseDuration(value) {
25594
+ const match = /^(\d+)(s|m|h)$/.exec(value.trim());
25595
+ if (!match) {
25596
+ throw new Error(
25597
+ `Invalid duration "${value}". Use a whole number followed by s, m, or h (e.g. 30s, 60m, 2h).`
25598
+ );
25599
+ }
25600
+ const amount = Number(match[1]);
25601
+ if (amount === 0) {
25602
+ throw new Error(`Invalid duration "${value}". Value must be at least 1.`);
25603
+ }
25604
+ return amount * UNIT_MS[match[2]];
25605
+ }
25606
+
25607
+ // src/commands/watch/gitFailureReason.ts
25608
+ function gitFailureReason(error) {
25609
+ const streams = error;
25610
+ for (const stream of [streams?.stderr, streams?.stdout]) {
25611
+ const text17 = stream == null ? "" : String(stream).trim();
25612
+ if (text17) return text17;
25613
+ }
25614
+ const message2 = error instanceof Error ? error.message : String(error);
25615
+ return message2.trim() || "git failed without reporting a reason";
25616
+ }
25617
+
25618
+ // src/commands/watch/resolveUpstream.ts
25619
+ import { execFileSync as execFileSync9 } from "child_process";
25620
+ function runGit2(args, cwd) {
25621
+ return execFileSync9("git", args, {
25622
+ encoding: "utf8",
25623
+ stdio: ["pipe", "pipe", "pipe"],
25624
+ cwd
25625
+ }).trim();
25626
+ }
25627
+ function resolveUpstream(cwd) {
25628
+ try {
25629
+ runGit2(["rev-parse", "--is-inside-work-tree"], cwd);
25630
+ } catch {
25631
+ throw new Error(
25632
+ "not a git repository \u2014 run assist watch wait from inside a repo"
25633
+ );
25634
+ }
25635
+ let branch2;
25636
+ try {
25637
+ branch2 = runGit2(["symbolic-ref", "--quiet", "--short", "HEAD"], cwd);
25638
+ } catch {
25639
+ throw new Error(
25640
+ "HEAD is detached \u2014 check out a branch before waiting on its upstream"
25641
+ );
25642
+ }
25643
+ try {
25644
+ return {
25645
+ branch: branch2,
25646
+ upstream: runGit2(
25647
+ ["rev-parse", "--abbrev-ref", "--symbolic-full-name", "@{u}"],
25648
+ cwd
25649
+ )
25650
+ };
25651
+ } catch {
25652
+ throw new Error(
25653
+ `branch "${branch2}" has no upstream \u2014 set one with: git push -u origin ${branch2}`
25654
+ );
25655
+ }
25656
+ }
25657
+
25658
+ // src/commands/watch/pullFastForward.ts
25659
+ function pullFastForward(cwd) {
25660
+ try {
25661
+ runGit2(["pull", "--ff-only"], cwd);
25662
+ return { kind: "fast-forwarded", sha: runGit2(["rev-parse", "@"], cwd) };
25663
+ } catch (error) {
25664
+ return { kind: "blocked", reason: gitFailureReason(error) };
25665
+ }
25666
+ }
25667
+
25668
+ // src/commands/watch/fetchQuietly.ts
25669
+ import { execFileSync as execFileSync10 } from "child_process";
25670
+ function fetchQuietly(cwd, timeoutMs) {
25671
+ try {
25672
+ execFileSync10("git", ["fetch", "--quiet"], {
25673
+ stdio: ["pipe", "pipe", "pipe"],
25674
+ cwd,
25675
+ timeout: timeoutMs
25676
+ });
25677
+ } catch {
25678
+ }
25679
+ }
25680
+
25681
+ // src/commands/watch/detectMovement.ts
25682
+ function detectMovement(from, to, count7) {
25683
+ if (from === to) return void 0;
25684
+ if (!Number.isFinite(count7) || count7 <= 0) return void 0;
25685
+ return { from, to, count: count7 };
25686
+ }
25687
+
25688
+ // src/commands/watch/readMovement.ts
25689
+ function readMovement(cwd) {
25690
+ try {
25691
+ const from = runGit2(["rev-parse", "@"], cwd);
25692
+ const to = runGit2(["rev-parse", "@{u}"], cwd);
25693
+ const count7 = Number(runGit2(["rev-list", "--count", "@..@{u}"], cwd));
25694
+ return detectMovement(from, to, count7);
25695
+ } catch {
25696
+ return void 0;
25697
+ }
25698
+ }
25699
+
25700
+ // src/commands/watch/waitForUpstream.ts
25701
+ var MIN_FETCH_TIMEOUT_MS = 6e4;
25702
+ function waitForUpstream(options2) {
25703
+ const { intervalMs, timeoutMs, timeout, cwd, onStart } = options2;
25704
+ let upstream;
25705
+ try {
25706
+ upstream = resolveUpstream(cwd).upstream;
25707
+ } catch (error) {
25708
+ return Promise.resolve({
25709
+ kind: "unavailable",
25710
+ reason: error instanceof Error ? error.message : String(error)
25711
+ });
25712
+ }
25713
+ onStart?.(upstream);
25714
+ const moved = readMovement(cwd);
25715
+ if (moved) {
25716
+ return Promise.resolve({ kind: "moved", upstream, ...moved });
25717
+ }
25718
+ const fetchTimeoutMs = Math.max(intervalMs, MIN_FETCH_TIMEOUT_MS);
25719
+ return new Promise((resolve20) => {
25720
+ let settled = false;
25721
+ const finish = (outcome) => {
25722
+ if (settled) return;
25723
+ settled = true;
25724
+ clearInterval(ticker);
25725
+ clearTimeout(deadline);
25726
+ process.off("SIGINT", onInterrupt);
25727
+ resolve20(outcome);
25728
+ };
25729
+ const onInterrupt = () => finish({ kind: "interrupted" });
25730
+ const ticker = setInterval(() => {
25731
+ fetchQuietly(cwd, fetchTimeoutMs);
25732
+ const found = readMovement(cwd);
25733
+ if (found) finish({ kind: "moved", upstream, ...found });
25734
+ }, intervalMs);
25735
+ const deadline = setTimeout(
25736
+ () => finish({ kind: "timeout", upstream, timeout }),
25737
+ timeoutMs
25738
+ );
25739
+ process.on("SIGINT", onInterrupt);
25740
+ });
25741
+ }
25742
+
25743
+ // src/commands/watch/watchWait.ts
25744
+ function parseOrExit(value) {
25745
+ try {
25746
+ return parseDuration(value);
25747
+ } catch (error) {
25748
+ console.error(error instanceof Error ? error.message : String(error));
25749
+ return process.exit(1);
25750
+ }
25751
+ }
25752
+ function report({ exitCode, message: message2 }) {
25753
+ if (exitCode === 0) console.log(message2);
25754
+ else console.error(message2);
25755
+ }
25756
+ async function watchWait(options2) {
25757
+ const intervalMs = parseOrExit(options2.interval);
25758
+ const timeoutMs = parseOrExit(options2.timeout);
25759
+ const outcome = await waitForUpstream({
25760
+ intervalMs,
25761
+ timeoutMs,
25762
+ timeout: options2.timeout,
25763
+ onStart: (upstream) => console.log(`waiting on ${upstream} \u2026`)
25764
+ });
25765
+ const waitReport = describeOutcome(outcome);
25766
+ report(waitReport);
25767
+ if (outcome.kind === "moved" && options2.pull) {
25768
+ const pullReport = describePull(pullFastForward());
25769
+ report(pullReport);
25770
+ process.exit(pullReport.exitCode);
25771
+ }
25772
+ process.exit(waitReport.exitCode);
25773
+ }
25774
+
25775
+ // src/commands/registerWatch.ts
25776
+ function registerWatch(program2) {
25777
+ const watchCommand = program2.command("watch").description("Wait on upstream movement for the current branch");
25778
+ watchCommand.command("wait").description(
25779
+ "Block until the current branch's upstream gains commits, then exit 0 (2 on timeout, 3 when --pull is not a fast-forward, 1 when waiting is impossible, 130 on interrupt)"
25780
+ ).option("--interval <duration>", "How often to fetch (e.g. 30s, 2m)", "30s").option(
25781
+ "--timeout <duration>",
25782
+ "Give up and exit 2 after this long (e.g. 60m, 2h)",
25783
+ "60m"
25784
+ ).option(
25785
+ "--pull",
25786
+ "On movement, fast-forward with git pull --ff-only; exit 3 with git's reason if it is not a clean fast-forward"
25787
+ ).action(
25788
+ (options2) => watchWait(options2)
25789
+ );
25790
+ }
25791
+
25485
25792
  // src/commands/roam/auth.ts
25486
25793
  import { randomBytes } from "crypto";
25487
25794
  import chalk204 from "chalk";
@@ -25642,7 +25949,7 @@ async function auth() {
25642
25949
  }
25643
25950
 
25644
25951
  // src/commands/roam/postRoamActivity.ts
25645
- import { execFileSync as execFileSync9 } from "child_process";
25952
+ import { execFileSync as execFileSync11 } from "child_process";
25646
25953
  import { readdirSync as readdirSync11, readFileSync as readFileSync47, statSync as statSync9 } from "fs";
25647
25954
  import { join as join66 } from "path";
25648
25955
  function findPortFile(roamDir) {
@@ -25675,7 +25982,7 @@ function postRoamActivity(app, event) {
25675
25982
  }
25676
25983
  const url = `http://127.0.0.1:${port}/api/v1/activity/${app}/${event}?pid=${app === "codex" ? 99998 : 99999}`;
25677
25984
  try {
25678
- execFileSync9("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
25985
+ execFileSync11("curl", ["-sf", "--max-time", "0.2", "-X", "POST", url], {
25679
25986
  stdio: "ignore"
25680
25987
  });
25681
25988
  } catch {
@@ -25896,13 +26203,13 @@ function runPreCommands(pre, cwd) {
25896
26203
  }
25897
26204
 
25898
26205
  // src/commands/run/spawnRunCommand.ts
25899
- import { execFileSync as execFileSync10, spawn as spawn8 } from "child_process";
26206
+ import { execFileSync as execFileSync12, spawn as spawn8 } from "child_process";
25900
26207
  import { existsSync as existsSync58 } from "fs";
25901
26208
  import { dirname as dirname29, join as join67, resolve as resolve15 } from "path";
25902
26209
  function resolveCommand2(command) {
25903
26210
  if (process.platform !== "win32" || command !== "bash") return command;
25904
26211
  try {
25905
- const gitPath = execFileSync10("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
26212
+ const gitPath = execFileSync12("where", ["git"], { encoding: "utf8" }).trim().split("\r\n")[0];
25906
26213
  const gitRoot = resolve15(dirname29(gitPath), "..");
25907
26214
  const gitBash = join67(gitRoot, "bin", "bash.exe");
25908
26215
  if (existsSync58(gitBash)) return gitBash;
@@ -26355,11 +26662,11 @@ function screenshot(processName) {
26355
26662
  }
26356
26663
 
26357
26664
  // src/commands/sessions/daemon/listDaemonPids.ts
26358
- import { execFileSync as execFileSync11 } from "child_process";
26665
+ import { execFileSync as execFileSync13 } from "child_process";
26359
26666
  function listDaemonPids() {
26360
26667
  if (process.platform === "win32") return [];
26361
26668
  try {
26362
- const out = execFileSync11("ps", ["-eo", "pid=,args="], {
26669
+ const out = execFileSync13("ps", ["-eo", "pid=,args="], {
26363
26670
  encoding: "utf8"
26364
26671
  });
26365
26672
  return out.split("\n").filter((line) => line.includes("assist") && / daemon run\b/.test(line)).map((line) => Number.parseInt(line.trim(), 10)).filter((pid) => Number.isInteger(pid));
@@ -30556,7 +30863,7 @@ function summaryPathFor(jsonlPath2) {
30556
30863
  }
30557
30864
 
30558
30865
  // src/commands/sessions/summarise/summariseSession.ts
30559
- import { execFileSync as execFileSync12 } from "child_process";
30866
+ import { execFileSync as execFileSync14 } from "child_process";
30560
30867
 
30561
30868
  // src/commands/sessions/summarise/extractFirstUserMessage.ts
30562
30869
  function extractFirstUserMessage(filePath) {
@@ -30580,7 +30887,7 @@ function summariseSession(jsonlPath2) {
30580
30887
  }
30581
30888
  const prompt = buildPrompt3(firstMessage, backlogIds);
30582
30889
  try {
30583
- const output = execFileSync12("claude", ["-p", "--model", "haiku", prompt], {
30890
+ const output = execFileSync14("claude", ["-p", "--model", "haiku", prompt], {
30584
30891
  encoding: "utf8",
30585
30892
  timeout: 3e4,
30586
30893
  stdio: ["ignore", "pipe", "ignore"]
@@ -31267,6 +31574,7 @@ registerSeq(program);
31267
31574
  registerSql(program);
31268
31575
  registerTranscript(program);
31269
31576
  registerVoice(program);
31577
+ registerWatch(program);
31270
31578
  registerSessions(program);
31271
31579
  registerDaemon(program);
31272
31580
  registerPrompts(program);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@staff0rd/assist",
3
- "version": "0.454.1",
3
+ "version": "0.456.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "bin": {