byterover-cli 3.8.3 → 3.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/agent/infra/llm/providers/google.js +1 -1
  2. package/dist/oclif/commands/vc/diff.d.ts +12 -0
  3. package/dist/oclif/commands/vc/diff.js +40 -0
  4. package/dist/oclif/commands/vc/remote/remove.d.ts +9 -0
  5. package/dist/oclif/commands/vc/remote/remove.js +23 -0
  6. package/dist/server/core/domain/entities/brv-config.d.ts +4 -0
  7. package/dist/server/core/domain/entities/brv-config.js +12 -0
  8. package/dist/server/core/domain/entities/provider-registry.js +1 -1
  9. package/dist/server/core/interfaces/services/i-git-service.d.ts +55 -4
  10. package/dist/server/infra/context-tree/summary-frontmatter.js +2 -2
  11. package/dist/server/infra/dream/operations/consolidate.js +5 -4
  12. package/dist/server/infra/dream/operations/synthesize.js +1 -1
  13. package/dist/server/infra/git/isomorphic-git-service.d.ts +24 -1
  14. package/dist/server/infra/git/isomorphic-git-service.js +207 -7
  15. package/dist/server/infra/transport/handlers/config-handler.js +1 -0
  16. package/dist/server/infra/transport/handlers/locations-handler.d.ts +1 -0
  17. package/dist/server/infra/transport/handlers/locations-handler.js +25 -1
  18. package/dist/server/infra/transport/handlers/reveal-command.d.ts +9 -0
  19. package/dist/server/infra/transport/handlers/reveal-command.js +7 -0
  20. package/dist/server/infra/transport/handlers/vc-handler.d.ts +11 -0
  21. package/dist/server/infra/transport/handlers/vc-handler.js +143 -9
  22. package/dist/server/infra/webui/webui-middleware.js +6 -1
  23. package/dist/shared/transport/events/config-events.d.ts +1 -0
  24. package/dist/shared/transport/events/index.d.ts +1 -0
  25. package/dist/shared/transport/events/locations-events.d.ts +7 -0
  26. package/dist/shared/transport/events/locations-events.js +1 -0
  27. package/dist/shared/transport/events/vc-events.d.ts +56 -5
  28. package/dist/shared/transport/events/vc-events.js +7 -0
  29. package/dist/tui/features/commands/definitions/vc-diff.d.ts +2 -0
  30. package/dist/tui/features/commands/definitions/vc-diff.js +23 -0
  31. package/dist/tui/features/commands/definitions/vc-remote.js +16 -7
  32. package/dist/tui/features/commands/definitions/vc.js +2 -0
  33. package/dist/tui/features/vc/diff/api/execute-vc-diff.d.ts +8 -0
  34. package/dist/tui/features/vc/diff/api/execute-vc-diff.js +13 -0
  35. package/dist/tui/features/vc/diff/components/vc-diff-flow.d.ts +8 -0
  36. package/dist/tui/features/vc/diff/components/vc-diff-flow.js +31 -0
  37. package/dist/tui/features/vc/diff/utils/format-diff.d.ts +2 -0
  38. package/dist/tui/features/vc/diff/utils/format-diff.js +83 -0
  39. package/dist/tui/features/vc/diff/utils/parse-mode.d.ts +2 -0
  40. package/dist/tui/features/vc/diff/utils/parse-mode.js +16 -0
  41. package/dist/tui/features/vc/remote/components/vc-remote-flow.js +23 -8
  42. package/dist/webui/assets/index-CvcqpMYn.css +1 -0
  43. package/dist/webui/assets/index-thSZZahh.js +130 -0
  44. package/dist/webui/index.html +3 -3
  45. package/dist/webui/sw.js +1 -1
  46. package/oclif.manifest.json +639 -566
  47. package/package.json +3 -1
  48. package/dist/webui/assets/index-DFMY2d5W.css +0 -1
  49. package/dist/webui/assets/index-Dkyf6c5F.js +0 -130
@@ -155,8 +155,9 @@ export interface IVcLogResponse {
155
155
  }>;
156
156
  currentBranch?: string;
157
157
  }
158
- export type VcRemoteSubcommand = 'add' | 'set-url' | 'show';
158
+ export type VcRemoteSubcommand = 'add' | 'remove' | 'set-url' | 'show';
159
159
  export declare const VC_REMOTE_SUBCOMMANDS: readonly string[];
160
+ export declare const VC_REMOTE_SUBCOMMAND_REQUIRES_URL: Record<VcRemoteSubcommand, boolean>;
160
161
  export declare function isVcRemoteSubcommand(value: string): value is VcRemoteSubcommand;
161
162
  export interface IVcRemoteRequest {
162
163
  subcommand: VcRemoteSubcommand;
@@ -280,15 +281,65 @@ export interface IVcDiffResponse {
280
281
  path: string;
281
282
  }
282
283
  /**
283
- * Batched diff — returns diffs for multiple paths on the same side in one round-trip.
284
- * Preserves the order of the input `paths`.
284
+ * Batched diff — returns diffs for multiple files in one round-trip.
285
+ *
286
+ * Discriminated union of two mutually exclusive request shapes:
287
+ * - WebUI: `{paths, side}` — caller supplies the paths, server returns raw content pairs.
288
+ * - CLI/TUI: `{mode}` — server auto-discovers changed files, returns full `IVcDiffFile`
289
+ * entries (status + oids). Binary files are filtered out.
290
+ *
291
+ * The union form guarantees callers can't accidentally mix the two shapes (type error).
285
292
  */
286
- export interface IVcDiffsRequest {
293
+ export type IVcDiffsRequest = {
294
+ mode: VcDiffMode;
295
+ } | {
287
296
  paths: string[];
288
297
  side: VcDiffSide;
298
+ };
299
+ /**
300
+ * Diff modes for `brv vc diff` / `/vc diff`. Mirrors the four diff modes from `git diff`:
301
+ * - unstaged → STAGE → WORKDIR (tracked files only; matches `git diff` no args)
302
+ * - staged → HEAD → STAGE (matches `git diff --staged`)
303
+ * - ref-vs-worktree → <commit|branch> → WORKDIR (matches `git diff <ref>`)
304
+ * - range → <ref1> → <ref2> (matches `git diff <ref1>..<ref2>`)
305
+ */
306
+ export type VcDiffMode = {
307
+ from: string;
308
+ kind: 'range';
309
+ to: string;
310
+ } | {
311
+ kind: 'ref-vs-worktree';
312
+ ref: string;
313
+ } | {
314
+ kind: 'staged';
315
+ } | {
316
+ kind: 'unstaged';
317
+ };
318
+ export type VcDiffFileStatus = 'added' | 'deleted' | 'modified';
319
+ /**
320
+ * Per-file diff entry. Extends `IVcDiffResponse` with status + oid.
321
+ * Binary files (NUL byte on either side) are filtered out of the response upstream,
322
+ * so consumers only ever see text content here.
323
+ *
324
+ * Legacy (WebUI) consumers read `oldContent`/`newContent`/`path` only; the extra
325
+ * fields are forward-compatible extras they ignore.
326
+ */
327
+ export interface IVcDiffFile {
328
+ /** True when either side is binary (contains a NUL byte). Content fields are empty. */
329
+ binary?: boolean;
330
+ newContent: string;
331
+ /** 7-char short oid; omitted for deleted files. */
332
+ newOid?: string;
333
+ oldContent: string;
334
+ /** 7-char short oid; omitted for added files. */
335
+ oldOid?: string;
336
+ path: string;
337
+ status: VcDiffFileStatus;
289
338
  }
290
339
  export interface IVcDiffsResponse {
291
- diffs: IVcDiffResponse[];
340
+ diffs: IVcDiffFile[];
341
+ /** Echoed when the request used `mode`; absent for legacy calls. */
342
+ mode?: VcDiffMode;
292
343
  }
293
344
  /**
294
345
  * Discards unstaged changes in the working tree.
@@ -62,9 +62,16 @@ export function isVcConfigKey(key) {
62
62
  }
63
63
  export const VC_REMOTE_SUBCOMMANDS = [
64
64
  'add',
65
+ 'remove',
65
66
  'set-url',
66
67
  'show',
67
68
  ];
69
+ export const VC_REMOTE_SUBCOMMAND_REQUIRES_URL = {
70
+ add: true,
71
+ remove: false,
72
+ 'set-url': true,
73
+ show: false,
74
+ };
68
75
  export function isVcRemoteSubcommand(value) {
69
76
  return VC_REMOTE_SUBCOMMANDS.includes(value);
70
77
  }
@@ -0,0 +1,2 @@
1
+ import type { SlashCommand } from '../../../types/commands.js';
2
+ export declare const vcDiffSubCommand: SlashCommand;
@@ -0,0 +1,23 @@
1
+ import React from 'react';
2
+ import { VcDiffFlow } from '../../vc/diff/components/vc-diff-flow.js';
3
+ import { parseMode } from '../../vc/diff/utils/parse-mode.js';
4
+ import { Args, Flags, parseReplArgs, toCommandFlags } from '../utils/arg-parser.js';
5
+ const vcDiffArgs = {
6
+ ref: Args.string({ description: 'commit, branch, or <ref1>..<ref2> range' }),
7
+ };
8
+ const vcDiffFlags = {
9
+ staged: Flags.boolean({ default: false, description: 'Show staged changes (HEAD vs index)' }),
10
+ };
11
+ export const vcDiffSubCommand = {
12
+ async action(_context, args) {
13
+ const parsed = await parseReplArgs(args, { args: vcDiffArgs, flags: vcDiffFlags, strict: false });
14
+ const mode = parseMode(parsed.args.ref, parsed.flags.staged ?? false);
15
+ return {
16
+ render: ({ onCancel, onComplete }) => React.createElement(VcDiffFlow, { mode, onCancel, onComplete }),
17
+ };
18
+ },
19
+ args: [{ description: 'commit, branch, or <ref1>..<ref2> range', name: 'ref' }],
20
+ description: 'Show changes between commits, the index, or the working tree',
21
+ flags: toCommandFlags(vcDiffFlags),
22
+ name: 'diff',
23
+ };
@@ -1,11 +1,11 @@
1
1
  import React from 'react';
2
- import { isVcRemoteSubcommand } from '../../../../shared/transport/events/vc-events.js';
2
+ import { isVcRemoteSubcommand, VC_REMOTE_SUBCOMMAND_REQUIRES_URL } from '../../../../shared/transport/events/vc-events.js';
3
3
  import { getGitRemoteBaseUrl } from '../../../lib/environment.js';
4
4
  import { VcRemoteFlow } from '../../vc/remote/components/vc-remote-flow.js';
5
5
  import { Args, parseReplArgs } from '../utils/arg-parser.js';
6
6
  /* eslint-disable perfectionist/sort-objects -- positional order matters: subcommand, name, url */
7
7
  const vcRemoteArgs = {
8
- subcommand: Args.string({ description: 'Subcommand: add | set-url (omit to show current remote)' }),
8
+ subcommand: Args.string({ description: 'Subcommand: add | set-url | remove (omit to show current remote)' }),
9
9
  name: Args.string({ description: 'Remote name (e.g. origin)' }),
10
10
  url: Args.string({ description: `Remote URL (e.g. ${getGitRemoteBaseUrl()}/<team>/<space>.git)` }),
11
11
  };
@@ -21,15 +21,24 @@ export const vcRemoteSubCommand = {
21
21
  }
22
22
  if (!isVcRemoteSubcommand(rawSubcommand)) {
23
23
  const errorMsg = {
24
- content: `Unknown subcommand '${rawSubcommand}'. Usage: /vc remote [add|set-url] <name> <url>`,
24
+ content: `Unknown subcommand '${rawSubcommand}'. Usage: /vc remote [add|set-url|remove] <name> [url]`,
25
25
  messageType: 'error',
26
26
  type: 'message',
27
27
  };
28
28
  return errorMsg;
29
29
  }
30
- if (!name || !url) {
30
+ if (rawSubcommand === 'show') {
31
+ return {
32
+ render: ({ onCancel, onComplete }) => React.createElement(VcRemoteFlow, { onCancel, onComplete, subcommand: 'show' }),
33
+ };
34
+ }
35
+ const requiresUrl = VC_REMOTE_SUBCOMMAND_REQUIRES_URL[rawSubcommand];
36
+ if (!name || (requiresUrl && !url)) {
37
+ const usage = requiresUrl
38
+ ? `Usage: /vc remote ${rawSubcommand} <name> <url>`
39
+ : `Usage: /vc remote ${rawSubcommand} <name>`;
31
40
  const errorMsg = {
32
- content: `Usage: /vc remote ${rawSubcommand} <name> <url>`,
41
+ content: usage,
33
42
  messageType: 'error',
34
43
  type: 'message',
35
44
  };
@@ -48,9 +57,9 @@ export const vcRemoteSubCommand = {
48
57
  };
49
58
  },
50
59
  args: [
51
- { description: 'Subcommand: add | set-url (omit to show current remote)', name: 'subcommand' },
60
+ { description: 'Subcommand: add | set-url | remove (omit to show current remote)', name: 'subcommand' },
52
61
  { description: 'Remote name (e.g. origin)', name: 'name' },
53
- { description: 'Remote URL', name: 'url' },
62
+ { description: 'Remote URL (required for add | set-url)', name: 'url' },
54
63
  ],
55
64
  description: 'Manage remote origin for ByteRover version control',
56
65
  name: 'remote',
@@ -4,6 +4,7 @@ import { vcCheckoutSubCommand } from './vc-checkout.js';
4
4
  import { vcCloneSubCommand } from './vc-clone.js';
5
5
  import { vcCommitSubCommand } from './vc-commit.js';
6
6
  import { vcConfigSubCommand } from './vc-config.js';
7
+ import { vcDiffSubCommand } from './vc-diff.js';
7
8
  import { vcFetchSubCommand } from './vc-fetch.js';
8
9
  import { vcInitSubCommand } from './vc-init.js';
9
10
  import { vcLogSubCommand } from './vc-log.js';
@@ -26,6 +27,7 @@ export const vcCommand = {
26
27
  vcPullSubCommand,
27
28
  vcPushSubCommand,
28
29
  vcStatusSubCommand,
30
+ vcDiffSubCommand,
29
31
  vcLogSubCommand,
30
32
  vcMergeSubCommand,
31
33
  vcBranchSubCommand,
@@ -0,0 +1,8 @@
1
+ import type { MutationConfig } from '../../../../lib/react-query.js';
2
+ import { type IVcDiffsRequest, type IVcDiffsResponse } from '../../../../../shared/transport/events/vc-events.js';
3
+ export declare const executeVcDiff: (request: IVcDiffsRequest) => Promise<IVcDiffsResponse>;
4
+ type UseExecuteVcDiffOptions = {
5
+ mutationConfig?: MutationConfig<typeof executeVcDiff>;
6
+ };
7
+ export declare const useExecuteVcDiff: ({ mutationConfig }?: UseExecuteVcDiffOptions) => import("@tanstack/react-query").UseMutationResult<IVcDiffsResponse, Error, IVcDiffsRequest, unknown>;
8
+ export {};
@@ -0,0 +1,13 @@
1
+ import { useMutation } from '@tanstack/react-query';
2
+ import { VcEvents, } from '../../../../../shared/transport/events/vc-events.js';
3
+ import { useTransportStore } from '../../../../stores/transport-store.js';
4
+ export const executeVcDiff = (request) => {
5
+ const { apiClient } = useTransportStore.getState();
6
+ if (!apiClient)
7
+ return Promise.reject(new Error('Not connected'));
8
+ return apiClient.request(VcEvents.DIFFS, request);
9
+ };
10
+ export const useExecuteVcDiff = ({ mutationConfig } = {}) => useMutation({
11
+ ...mutationConfig,
12
+ mutationFn: executeVcDiff,
13
+ });
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { VcDiffMode } from '../../../../../shared/transport/events/vc-events.js';
3
+ import type { CustomDialogCallbacks } from '../../../../types/commands.js';
4
+ type VcDiffFlowProps = CustomDialogCallbacks & {
5
+ mode: VcDiffMode;
6
+ };
7
+ export declare function VcDiffFlow({ mode, onCancel, onComplete }: VcDiffFlowProps): React.ReactNode;
8
+ export {};
@@ -0,0 +1,31 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Text, useInput } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ import React, { useEffect } from 'react';
5
+ import { formatTransportError } from '../../../../utils/error-messages.js';
6
+ import { useExecuteVcDiff } from '../api/execute-vc-diff.js';
7
+ import { formatDiff } from '../utils/format-diff.js';
8
+ export function VcDiffFlow({ mode, onCancel, onComplete }) {
9
+ const diffMutation = useExecuteVcDiff();
10
+ useInput((_, key) => {
11
+ if (key.escape && !diffMutation.isPending) {
12
+ onCancel();
13
+ }
14
+ });
15
+ const fired = React.useRef(false);
16
+ useEffect(() => {
17
+ if (fired.current)
18
+ return;
19
+ fired.current = true;
20
+ diffMutation.mutate({ mode }, {
21
+ onError(error) {
22
+ onComplete(`Failed to compute diff: ${formatTransportError(error)}`);
23
+ },
24
+ onSuccess(result) {
25
+ const text = formatDiff(result);
26
+ onComplete(text.length === 0 ? 'No changes.' : text.replace(/\n$/, ''));
27
+ },
28
+ });
29
+ }, []);
30
+ return (_jsxs(Text, { children: [_jsx(Spinner, { type: "dots" }), " Computing diff..."] }));
31
+ }
@@ -0,0 +1,2 @@
1
+ import type { IVcDiffsResponse } from '../../../../../shared/transport/events/vc-events.js';
2
+ export declare function formatDiff(response: IVcDiffsResponse): string;
@@ -0,0 +1,83 @@
1
+ import chalk from 'chalk';
2
+ import { structuredPatch } from 'diff';
3
+ export function formatDiff(response) {
4
+ if (response.diffs.length === 0)
5
+ return '';
6
+ return response.diffs.map((file) => formatFile(file)).join('');
7
+ }
8
+ function formatFile(f) {
9
+ const aPath = f.status === 'added' ? '/dev/null' : `a/${f.path}`;
10
+ const bPath = f.status === 'deleted' ? '/dev/null' : `b/${f.path}`;
11
+ const lines = [chalk.bold(`diff --git a/${f.path} b/${f.path}`)];
12
+ if (f.status === 'added') {
13
+ lines.push(chalk.bold('new file mode 100644'));
14
+ if (f.newOid)
15
+ lines.push(chalk.bold(`index 0000000..${f.newOid}`));
16
+ }
17
+ else if (f.status === 'deleted') {
18
+ lines.push(chalk.bold('deleted file mode 100644'));
19
+ if (f.oldOid)
20
+ lines.push(chalk.bold(`index ${f.oldOid}..0000000`));
21
+ }
22
+ else if (f.oldOid && f.newOid) {
23
+ lines.push(chalk.bold(`index ${f.oldOid}..${f.newOid} 100644`));
24
+ }
25
+ if (f.binary) {
26
+ lines.push(`Binary files ${aPath} and ${bPath} differ`);
27
+ return lines.join('\n') + '\n';
28
+ }
29
+ const patch = structuredPatch(f.path, f.path, f.oldContent, f.newContent, '', '', { context: 3 });
30
+ // Skip `---`/`+++` when there are no hunks (e.g. empty file added/deleted).
31
+ // Matches `git diff` which omits these lines when there's nothing to show.
32
+ if (patch.hunks.length === 0) {
33
+ return lines.join('\n') + '\n';
34
+ }
35
+ // Append a tab after paths containing spaces/tabs so the unified-diff parser
36
+ // can unambiguously locate the path end. Matches `git diff` behavior.
37
+ const aHeader = hasWhitespace(f.path) && f.status !== 'added' ? `${aPath}\t` : aPath;
38
+ const bHeader = hasWhitespace(f.path) && f.status !== 'deleted' ? `${bPath}\t` : bPath;
39
+ lines.push(chalk.bold(`--- ${aHeader}`), chalk.bold(`+++ ${bHeader}`));
40
+ const oldLines = f.oldContent.split('\n');
41
+ for (const hunk of patch.hunks) {
42
+ const header = `@@ -${formatHunkRange(hunk.oldStart, hunk.oldLines)} +${formatHunkRange(hunk.newStart, hunk.newLines)} @@`;
43
+ const context = findHunkContext(oldLines, hunk.oldStart);
44
+ lines.push(chalk.cyan(context ? `${header} ${context}` : header));
45
+ for (const line of hunk.lines) {
46
+ if (line.startsWith('+'))
47
+ lines.push(chalk.green(line));
48
+ else if (line.startsWith('-'))
49
+ lines.push(chalk.red(line));
50
+ else
51
+ lines.push(line);
52
+ }
53
+ }
54
+ return lines.join('\n') + '\n';
55
+ }
56
+ function hasWhitespace(path) {
57
+ return /\s/.test(path);
58
+ }
59
+ /**
60
+ * Approximates git's trailing hunk-context line. Scans backwards from the line before
61
+ * the hunk for the closest non-empty line that starts with an identifier-ish character
62
+ * (matches git's default xfuncname for plain text: `^[A-Za-z_$]`).
63
+ */
64
+ function findHunkContext(oldLines, oldStart) {
65
+ const startIdx = oldStart - 2; // oldStart is 1-based line number of hunk's first line
66
+ for (let i = startIdx; i >= 0; i--) {
67
+ const line = oldLines[i];
68
+ if (line && /^[A-Z_a-z]/.test(line))
69
+ return line;
70
+ }
71
+ return undefined;
72
+ }
73
+ // Matches `git diff` hunk-range formatting:
74
+ // - count === 1: omit `,1`
75
+ // - count === 0: emit `<start-1>,0` (git convention — start is the line BEFORE the insertion point)
76
+ // - otherwise: `<start>,<count>`
77
+ function formatHunkRange(start, count) {
78
+ if (count === 0)
79
+ return `${start - 1},0`;
80
+ if (count === 1)
81
+ return `${start}`;
82
+ return `${start},${count}`;
83
+ }
@@ -0,0 +1,2 @@
1
+ import type { VcDiffMode } from '../../../../../shared/transport/events/vc-events.js';
2
+ export declare function parseMode(arg: string | undefined, staged: boolean): VcDiffMode;
@@ -0,0 +1,16 @@
1
+ export function parseMode(arg, staged) {
2
+ if (staged && arg !== undefined) {
3
+ throw new Error('--staged cannot be combined with a ref argument');
4
+ }
5
+ if (staged)
6
+ return { kind: 'staged' };
7
+ if (arg === undefined)
8
+ return { kind: 'unstaged' };
9
+ if (arg.includes('...')) {
10
+ throw new Error("three-dot syntax is not supported; use 'a..b' for two-dot range");
11
+ }
12
+ const rangeMatch = /^(.+?)\.\.(.+)$/.exec(arg);
13
+ if (rangeMatch)
14
+ return { from: rangeMatch[1], kind: 'range', to: rangeMatch[2] };
15
+ return { kind: 'ref-vs-worktree', ref: arg };
16
+ }
@@ -6,6 +6,7 @@ import { formatTransportError } from '../../../../utils/error-messages.js';
6
6
  import { useExecuteVcRemote } from '../api/execute-vc-remote.js';
7
7
  const LABELS = {
8
8
  add: 'Adding remote...',
9
+ remove: 'Removing remote...',
9
10
  'set-url': 'Updating remote...',
10
11
  show: 'Fetching remote...',
11
12
  };
@@ -26,14 +27,28 @@ export function VcRemoteFlow({ onCancel, onComplete, subcommand, url }) {
26
27
  onComplete(`Failed: ${formatTransportError(error)}`);
27
28
  },
28
29
  onSuccess(result) {
29
- if (result.action === 'show') {
30
- onComplete(result.url ? `origin: ${result.url}` : 'No remote configured.');
31
- }
32
- else if (result.action === 'add') {
33
- onComplete(`Remote 'origin' set to ${result.url}.`);
34
- }
35
- else {
36
- onComplete(`Remote 'origin' updated to ${result.url}.`);
30
+ switch (result.action) {
31
+ case 'add': {
32
+ onComplete(`Remote 'origin' set to ${result.url}.`);
33
+ break;
34
+ }
35
+ case 'remove': {
36
+ onComplete(`Remote 'origin' removed.`);
37
+ break;
38
+ }
39
+ case 'set-url': {
40
+ onComplete(`Remote 'origin' updated to ${result.url}.`);
41
+ break;
42
+ }
43
+ case 'show': {
44
+ onComplete(result.url ? `origin: ${result.url}` : 'No remote configured.');
45
+ break;
46
+ }
47
+ default: {
48
+ const exhaustive = result.action;
49
+ onComplete(`Unknown action: ${String(exhaustive)}`);
50
+ break;
51
+ }
37
52
  }
38
53
  },
39
54
  });