@fnclaude/cli 1.1.0 → 2.0.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 (100) hide show
  1. package/bin/fnc.js +34 -79
  2. package/package.json +6 -9
  3. package/share/fnclaude/templates/handoff.template.md +11 -0
  4. package/src/argv/classify.ts +48 -0
  5. package/src/argv/expand.ts +51 -0
  6. package/src/argv/intake.ts +52 -0
  7. package/src/argv/magic.ts +103 -0
  8. package/src/argv/parse.ts +213 -0
  9. package/src/argv/preserve-args.ts +333 -0
  10. package/src/argv/sentinel.ts +41 -0
  11. package/src/argv/short-flags.ts +152 -0
  12. package/src/config/load.ts +116 -0
  13. package/src/handoff/awaiter.ts +140 -0
  14. package/src/handoff/clean-env.ts +45 -0
  15. package/src/handoff/kill-and-exec.ts +110 -0
  16. package/src/handoff/spawn-launcher.ts +185 -0
  17. package/src/handoff/summary-file.ts +86 -0
  18. package/src/handoff/trigger.ts +90 -0
  19. package/src/help-version.ts +151 -0
  20. package/src/launch/compose-env.ts +34 -0
  21. package/src/launch/cross-cwd-parse.ts +69 -0
  22. package/src/launch/cross-cwd-relaunch.ts +95 -0
  23. package/src/launch/find-claude.ts +52 -0
  24. package/src/launch/live-permission-reader.ts +133 -0
  25. package/src/launch/ring-buffer.ts +92 -0
  26. package/src/main.ts +580 -437
  27. package/src/mcp/dispatch.ts +240 -0
  28. package/src/mcp/handlers/clipboard-backends.ts +176 -0
  29. package/src/mcp/handlers/clipboard.ts +62 -0
  30. package/src/mcp/handlers/restart.ts +156 -0
  31. package/src/mcp/handlers/spawn.ts +219 -0
  32. package/src/mcp/handlers/switch.ts +272 -0
  33. package/src/mcp/inject-config.ts +59 -0
  34. package/src/mcp/jsonrpc-server.ts +154 -0
  35. package/src/mcp/listener.ts +141 -0
  36. package/src/mcp/parent-dispatch.ts +154 -0
  37. package/src/mcp/socket-path.ts +48 -0
  38. package/src/mcp/wire.ts +181 -0
  39. package/src/name/auto-name.ts +162 -0
  40. package/src/name/llm-prompt.ts +14 -0
  41. package/src/name/sanitize.ts +57 -0
  42. package/src/name/sdk-llm.ts +42 -0
  43. package/src/noop/seed.ts +63 -0
  44. package/src/noop/template-source.ts +62 -0
  45. package/src/path/ensure-cwd.ts +95 -0
  46. package/src/path/resolve.ts +58 -0
  47. package/src/prompts/dir.ts +61 -0
  48. package/src/prompts/load.ts +100 -0
  49. package/src/prompts/select.ts +43 -0
  50. package/src/repo/clone-exec.ts +37 -0
  51. package/src/repo/clone.ts +45 -0
  52. package/src/repo/gh-runner.ts +68 -0
  53. package/src/repo/host-aliases.ts +58 -0
  54. package/src/repo/owner-lookup.ts +71 -0
  55. package/src/repo/ref.ts +146 -0
  56. package/src/repo/repo-settings.ts +99 -0
  57. package/src/repo/resolve-input.ts +179 -0
  58. package/src/repo/template.ts +92 -0
  59. package/src/warnings/buffer.ts +39 -0
  60. package/src/worktree/auto-tmux.ts +45 -0
  61. package/src/worktree/git-list.ts +73 -0
  62. package/src/worktree/intercept.ts +150 -0
  63. package/bin/preflight.js +0 -66
  64. package/prompts/agent-pitfall.md +0 -1
  65. package/prompts/noop-router.md +0 -186
  66. package/prompts/project-switch.md +0 -64
  67. package/prompts/restart.md +0 -50
  68. package/prompts/spawn.md +0 -62
  69. package/src/argParser.ts +0 -367
  70. package/src/args/preserve.ts +0 -338
  71. package/src/args.ts +0 -239
  72. package/src/argv.ts +0 -203
  73. package/src/autoname.ts +0 -273
  74. package/src/clipboard.ts +0 -149
  75. package/src/config.ts +0 -369
  76. package/src/errors.ts +0 -13
  77. package/src/handoff.ts +0 -108
  78. package/src/help.ts +0 -139
  79. package/src/hostAliases.ts +0 -139
  80. package/src/index.ts +0 -120
  81. package/src/mcp/client.ts +0 -645
  82. package/src/mcp/protocol.ts +0 -445
  83. package/src/mcp/socketListener.ts +0 -540
  84. package/src/noop.ts +0 -106
  85. package/src/passthrough.ts +0 -36
  86. package/src/paths.ts +0 -55
  87. package/src/prompts.ts +0 -279
  88. package/src/pty/unix.ts +0 -429
  89. package/src/pty/windows.ts +0 -125
  90. package/src/pty.ts +0 -380
  91. package/src/repoRef.ts +0 -158
  92. package/src/repoSettings.ts +0 -144
  93. package/src/resolver.ts +0 -519
  94. package/src/sanitize.ts +0 -120
  95. package/src/sessionState.ts +0 -220
  96. package/src/silentRelaunch.ts +0 -178
  97. package/src/spawn.ts +0 -163
  98. package/src/template.ts +0 -44
  99. package/src/warnings.ts +0 -34
  100. package/src/worktree.ts +0 -201
package/src/args.ts DELETED
@@ -1,239 +0,0 @@
1
- /**
2
- * Stage-typed argv pipeline.
3
- *
4
- * fnclaude's argv flows through five stages — parse, resolve, intercept,
5
- * auto-name, sanitize — before being handed to buildArgv. Earlier the
6
- * intermediate state was a single mutable `Args` bag passed through every
7
- * step, each free to rewrite any field. The pipeline's *ordering* was a
8
- * runtime invariant only: nothing stopped `buildArgv` from being called
9
- * before `applyWorktreeIntercept`, and `worktreeMatched` started life as a
10
- * placeholder boolean on the parsed shape, even though it has no meaningful
11
- * value until intercept has run.
12
- *
13
- * The new shape uses distinct types per stage. Each function takes its
14
- * predecessor's output type as input and returns the next stage's type.
15
- * The shapes are structurally readonly and brand-discriminated, so:
16
- *
17
- * - `parseArgs` returns `ParsedArgs`, which has NO `worktreeMatched`
18
- * field at all — the invariant "the intercept hasn't run yet" is
19
- * encoded in the type, not a sentinel value.
20
- * - `applyWorktreeIntercept` returns `InterceptedArgs`, which has
21
- * `worktreeMatched` materialized. Passing a `ParsedArgs` to `buildArgv`
22
- * is a compile error because `InterceptedArgs` is what `buildArgv`
23
- * accepts.
24
- * - Stages are immutable; each function returns a new value. The
25
- * pipeline composes by value, not by aliased reference.
26
- *
27
- * The brand fields (`__stage`) only exist in the type system — they're
28
- * never assigned at runtime. The brand functions (`brandParsed` etc.) are
29
- * named casts that document the boundary at which the stage transition
30
- * happens.
31
- */
32
-
33
- // ── Base shape ─────────────────────────────────────────────────────────────
34
-
35
- /**
36
- * Fields every stage carries. All readonly: stage transitions produce new
37
- * objects, never mutate.
38
- */
39
- export interface BaseArgs {
40
- /**
41
- * CWD is the directory claude will be launched in (first positional, or
42
- * the noop fallback when no positionals are given). The interpretation
43
- * narrows as stages progress — `ParsedArgs.cwd` is whatever the user
44
- * typed; later stages have it resolved to an absolute path and/or
45
- * swapped to a matched-worktree path.
46
- */
47
- readonly cwd: string;
48
-
49
- /**
50
- * ExtraDirs collects all -A / --also values in order. Positional 2 is the
51
- * worktree slot; -A is the only way to supply extra dirs.
52
- */
53
- readonly extraDirs: readonly string[];
54
-
55
- /**
56
- * Passthrough is everything else, preserved in order, to be forwarded to
57
- * claude verbatim. Short flags are already translated to their long
58
- * forms. Later stages may *extend* this slice (e.g. the intercept pushes
59
- * `--worktree <name>`, auto-name prepends `--name <name>`).
60
- */
61
- readonly passthrough: readonly string[];
62
-
63
- /**
64
- * NoTmux is true when the user passed --no-tmux (eaten by fnclaude; not
65
- * forwarded to claude).
66
- */
67
- readonly noTmux: boolean;
68
-
69
- /**
70
- * WorktreeSet is true when the user passed -w / --worktree, OR supplied
71
- * a 2nd positional after magic + subcommand consumption, OR the Resolve
72
- * step picked up a `+workspace` suffix from a repo reference.
73
- */
74
- readonly worktreeSet: boolean;
75
-
76
- /**
77
- * WorktreeArg is the name/value given with -w / --worktree (or the 2nd
78
- * positional / +workspace suffix), or undefined if the flag was bare.
79
- */
80
- readonly worktreeArg: string | undefined;
81
-
82
- /**
83
- * UsedNoopFallback is true when CWD was filled by the noop fallback (no
84
- * positional path given). Caller uses this to gate seed-noop behavior —
85
- * explicit paths don't get auto-seeded.
86
- */
87
- readonly usedNoopFallback: boolean;
88
- }
89
-
90
- // ── Brand machinery ────────────────────────────────────────────────────────
91
-
92
- /**
93
- * `Branded<T, K>` tags `T` with a phantom string literal `K` so two
94
- * otherwise-structurally-identical types become assignment-incompatible.
95
- * The `__stage` property exists only in the type — runtime values never
96
- * carry it.
97
- */
98
- type Branded<T, K extends string> = T & { readonly __stage: K };
99
-
100
- // ── Stage 1: parsed ────────────────────────────────────────────────────────
101
-
102
- /**
103
- * Output of `parseArgs`. The argv has been split into structural fields,
104
- * but no I/O has run yet — `cwd` is still the user-typed string, the
105
- * worktree intercept hasn't queried git, and no autoname has been generated.
106
- *
107
- * Has NO `worktreeMatched` field. That value only becomes meaningful after
108
- * the intercept stage, and encoding its absence in the type means a stale
109
- * `worktreeMatched: false` can't accidentally be read by a downstream step.
110
- */
111
- export type ParsedArgs = Branded<BaseArgs, 'parsed'>;
112
-
113
- // ── Stage 2: resolved ──────────────────────────────────────────────────────
114
-
115
- /**
116
- * Output of the Resolve / tilde-expand step. `cwd` is an absolute path
117
- * (when a path or repo ref was resolved) or the noop fallback. The Resolve
118
- * step may also have promoted a `+workspace` suffix into `worktreeSet` +
119
- * `worktreeArg`.
120
- *
121
- * Still no `worktreeMatched` — that's the next stage.
122
- */
123
- export type ResolvedArgs = Branded<BaseArgs, 'resolved'>;
124
-
125
- // ── Stage 3: intercepted ───────────────────────────────────────────────────
126
-
127
- /**
128
- * Output of `applyWorktreeIntercept`. `worktreeMatched` is now meaningful:
129
- * true iff an existing worktree of the project repo matched
130
- * `worktreeArg` (and `cwd` was swapped to that worktree's path). Downstream
131
- * consumers (`buildArgv`'s auto-tmux gate, primarily) treat matched=true
132
- * as "no new worktree being created this run" and avoid injecting flags
133
- * that only make sense when claude is about to spin up a fresh worktree.
134
- *
135
- * `passthrough` may have been extended with `--worktree`, `--worktree <name>`,
136
- * or `--name <name>` depending on whether the intercept matched.
137
- */
138
- export interface InterceptedFields {
139
- /**
140
- * True iff -w / --worktree was resolved against an existing worktree of
141
- * the project repo (and cwd was swapped to that worktree).
142
- */
143
- readonly worktreeMatched: boolean;
144
- }
145
-
146
- export type InterceptedArgs = Branded<BaseArgs & InterceptedFields, 'intercepted'>;
147
-
148
- // ── Brand constructors ─────────────────────────────────────────────────────
149
- //
150
- // Each brand function is a named cast — it doesn't validate anything,
151
- // it just documents *where* the stage transition happens in the pipeline.
152
- // The asserted shape carries all the invariants the stage promises.
153
-
154
- /**
155
- * Stamp a `BaseArgs`-shaped value as `ParsedArgs`. Only `parseArgs` should
156
- * call this.
157
- */
158
- export function brandParsed(a: BaseArgs): ParsedArgs {
159
- return a as ParsedArgs;
160
- }
161
-
162
- /**
163
- * Stamp a `BaseArgs`-shaped value as `ResolvedArgs`. Called after the
164
- * Resolve / tilde-expand step (or to short-circuit when the input is
165
- * already absolute and doesn't need resolution).
166
- */
167
- export function brandResolved(a: BaseArgs): ResolvedArgs {
168
- return a as ResolvedArgs;
169
- }
170
-
171
- /**
172
- * Stamp a `BaseArgs & InterceptedFields`-shaped value as `InterceptedArgs`.
173
- * Only `applyWorktreeIntercept` should call this.
174
- */
175
- export function brandIntercepted(a: BaseArgs & InterceptedFields): InterceptedArgs {
176
- return a as InterceptedArgs;
177
- }
178
-
179
- // ── Stage transitions (replace one or more fields, restamp the brand) ──────
180
-
181
- /**
182
- * Return a new `ResolvedArgs` with the given field overrides. Used by the
183
- * Resolve step to swap `cwd` (and possibly `worktreeSet` / `worktreeArg`)
184
- * without mutating the parsed value.
185
- */
186
- export function withResolved(
187
- a: ParsedArgs | ResolvedArgs,
188
- overrides: Partial<BaseArgs>,
189
- ): ResolvedArgs {
190
- return brandResolved({ ...(a as BaseArgs), ...overrides });
191
- }
192
-
193
- /**
194
- * Return a new `InterceptedArgs` from a `ResolvedArgs` plus the
195
- * intercept's outputs. `InterceptedFields` (currently just `worktreeMatched`)
196
- * is mandatory in the overrides so the brand can't be applied without it.
197
- */
198
- export function withIntercepted(
199
- a: ResolvedArgs,
200
- overrides: Partial<BaseArgs> & InterceptedFields,
201
- ): InterceptedArgs {
202
- return brandIntercepted({ ...(a as BaseArgs), ...overrides });
203
- }
204
-
205
- /**
206
- * Return a new `InterceptedArgs` with `passthrough` (or other fields)
207
- * replaced. Used by the auto-name and sanitize steps — both operate on the
208
- * passthrough slice and produce a new slice; the rest of the args carries
209
- * through unchanged.
210
- *
211
- * Returns `InterceptedArgs` (not a separate "named" / "sanitized" type)
212
- * because no new invariants are established at those steps — only the
213
- * passthrough slice changes shape, and that's already an in-stage edit
214
- * the intercept itself does.
215
- */
216
- export function withPassthroughUpdate(
217
- a: InterceptedArgs,
218
- overrides: Partial<BaseArgs>,
219
- ): InterceptedArgs {
220
- // Carry worktreeMatched through; only overrides win.
221
- return brandIntercepted({
222
- ...(a as BaseArgs & InterceptedFields),
223
- ...overrides,
224
- });
225
- }
226
-
227
- // ── Back-compat re-export ──────────────────────────────────────────────────
228
-
229
- /**
230
- * `Args` was the single mutable bag the pipeline threaded through before
231
- * the stage-typed refactor. Retained as an alias for `InterceptedArgs` so
232
- * external consumers (the published `index.ts` surface, test helpers in
233
- * downstream tooling) keep working while the refactor lands. New code
234
- * should use the stage-specific types directly.
235
- *
236
- * @deprecated Use `ParsedArgs` / `ResolvedArgs` / `InterceptedArgs` per
237
- * the position in the pipeline.
238
- */
239
- export type Args = InterceptedArgs;
package/src/argv.ts DELETED
@@ -1,203 +0,0 @@
1
- // Port of buildArgv + the helpers it composes (src/main.go, lines 745–858 in
2
- // the Go reference). Constructs the final argv slice that fnclaude execs
3
- // claude with.
4
- //
5
- // Layered behaviours, in order:
6
- // 1. Per-extra-dir flag injection: --add-dir <dir>, --mcp-config <dir>/.mcp.json
7
- // when present, --settings <dir>/.claude/settings.json when present and
8
- // --setting-sources isn't already in passthrough.
9
- // 2. Self-MCP injection: an inline --mcp-config <json> pointing at the
10
- // current fnclaude binary so the spawned claude can call our
11
- // fnc_restart / fnc_switch_project / fnc_copy_to_clipboard tools.
12
- // Gated on interactive sessions only.
13
- // 3. Auto-tmux injection: --tmux when auto.tmux="worktree", a new
14
- // worktree is being created (worktreeSet && !worktreeMatched), and
15
- // neither --tmux nor --no-tmux is already in play.
16
- // 4. System-prompt fragment injection: --append-system-prompt <merged-text>
17
- // composed by selectFragments + withAppendedSystemPrompts.
18
-
19
- import { existsSync } from 'node:fs';
20
- import { isAbsolute, join } from 'node:path';
21
- import type { InterceptedArgs } from './args.js';
22
- import {
23
- nameInPassthrough as _nameInPassthrough,
24
- settingSourcesInPassthrough,
25
- tokenInPassthrough,
26
- } from './passthrough.js';
27
- import type { Config } from './config.js';
28
- import { resolveSelfPath } from './paths.js';
29
- import { isInteractiveSession, selectFragments, type PromptSet } from './prompts.js';
30
-
31
- // Re-export the passthrough inspection helpers from their canonical home so
32
- // callers can reach them via "./argv.js" — mirrors the Go reference where
33
- // they sit next to buildArgv. The single-source-of-truth implementation
34
- // stays in passthrough.ts.
35
- export { settingSourcesInPassthrough, tokenInPassthrough };
36
-
37
- // ── MCP self-injection ─────────────────────────────────────────────────────
38
-
39
- /**
40
- * mcpConfigEntry mirrors the Go struct of the same name; one server entry
41
- * inside the --mcp-config JSON object.
42
- */
43
- interface McpConfigEntry {
44
- command: string;
45
- args: string[];
46
- }
47
-
48
- /**
49
- * buildFnclaudeMCPConfigJSON returns the inline JSON string to pass as
50
- * --mcp-config so claude launches `fnclaude mcp` (or `fnclaude mcp --noop`)
51
- * as its MCP server subprocess. Returns null on any error — same defensive
52
- * pattern as Go's findPromptsDir symlink-resolution fallback: log nothing,
53
- * let the session launch without the MCP server rather than failing.
54
- *
55
- * Path resolution: prefer argv[1] (the CLI script) over execPath (the bun
56
- * interpreter) so the spawned `fnclaude mcp` runs the same CLI logic as
57
- * the launching process. Symlinks are followed (so a ~/.local/bin/fnc-dev
58
- * → repo/bin/fnclaude symlink resolves to the real binary path).
59
- */
60
- export function buildFnclaudeMCPConfigJSON(noop: boolean): string | null {
61
- const exe = resolveSelfPath();
62
-
63
- const args = ['mcp'];
64
- if (noop) args.push('--noop');
65
-
66
- const cfg: { mcpServers: Record<string, McpConfigEntry> } = {
67
- mcpServers: {
68
- fnclaude: { command: exe, args },
69
- },
70
- };
71
-
72
- try {
73
- return JSON.stringify(cfg);
74
- } catch {
75
- return null;
76
- }
77
- }
78
-
79
- // ── system-prompt fragment merge ───────────────────────────────────────────
80
-
81
- /**
82
- * withAppendedSystemPrompts returns a copy of `passthrough` with the given
83
- * `fragments` merged into a single --append-system-prompt value. Fragments
84
- * are joined with a blank-line separator. If `passthrough` already contains
85
- * a --append-system-prompt (either space-form or =form), the fragments are
86
- * appended to that existing value. Empty fragments are dropped. Returns
87
- * `passthrough` unchanged when no non-empty fragments remain.
88
- *
89
- * Never mutates the input slice.
90
- */
91
- export function withAppendedSystemPrompts(
92
- passthrough: readonly string[],
93
- fragments: readonly string[],
94
- ): string[] {
95
- const clean = fragments.filter((f) => f !== '');
96
- if (clean.length === 0) return [...passthrough];
97
-
98
- const joined = clean.join('\n\n');
99
- for (let i = 0; i < passthrough.length; i++) {
100
- const t = passthrough[i] as string;
101
- if (t === '--append-system-prompt' && i + 1 < passthrough.length) {
102
- const out = [...passthrough];
103
- out[i + 1] = `${passthrough[i + 1] as string}\n\n${joined}`;
104
- return out;
105
- }
106
- if (t.startsWith('--append-system-prompt=')) {
107
- const existing = t.slice('--append-system-prompt='.length);
108
- const out = [...passthrough];
109
- out[i] = `--append-system-prompt=${existing}\n\n${joined}`;
110
- return out;
111
- }
112
- }
113
- return [...passthrough, '--append-system-prompt', joined];
114
- }
115
-
116
- // ── buildArgv ──────────────────────────────────────────────────────────────
117
-
118
- /**
119
- * buildArgv constructs the argv slice to exec claude with, given the
120
- * fnclaude args at their final pipeline stage (`InterceptedArgs` — the
121
- * intercept must have run so `worktreeMatched` is meaningful), the user's
122
- * shell cwd (used to resolve relative extra-dir paths), the loaded config,
123
- * and the set of prompt fragments loaded from the install dir.
124
- *
125
- * Accepting `InterceptedArgs` makes the ordering invariant a compile-time
126
- * check: passing a `ParsedArgs` or `ResolvedArgs` is a type error,
127
- * preventing the auto-tmux gate from reading a stale `worktreeMatched`
128
- * value the parse stage couldn't know.
129
- *
130
- * `shellCWD` is the process working directory at fnclaude startup —
131
- * normally `process.cwd()`. It's threaded through (rather than reached for
132
- * directly) so tests can pin it without `chdir`-ing.
133
- */
134
- export function buildArgv(
135
- a: InterceptedArgs,
136
- shellCWD: string,
137
- cfg: Config,
138
- prompts: PromptSet,
139
- ): string[] {
140
- const suppressSettings = settingSourcesInPassthrough(a.passthrough);
141
-
142
- const argv: string[] = ['claude'];
143
-
144
- // 1. Inject --add-dir (+ optional --mcp-config / --settings) per extra dir.
145
- for (const raw of a.extraDirs) {
146
- const d = isAbsolute(raw) ? raw : join(shellCWD, raw);
147
- argv.push('--add-dir', d);
148
-
149
- const mcpConfig = join(d, '.mcp.json');
150
- if (existsSync(mcpConfig)) {
151
- argv.push('--mcp-config', mcpConfig);
152
- }
153
-
154
- if (!suppressSettings) {
155
- const settings = join(d, '.claude', 'settings.json');
156
- if (existsSync(settings)) {
157
- argv.push('--settings', settings);
158
- }
159
- }
160
- }
161
-
162
- // 2. Inject the fnclaude self-MCP config (interactive sessions only).
163
- if (isInteractiveSession(a.passthrough)) {
164
- const configJSON = buildFnclaudeMCPConfigJSON(a.usedNoopFallback);
165
- if (configJSON !== null) {
166
- argv.push('--mcp-config', configJSON);
167
- }
168
- }
169
-
170
- // 3. Auto-inject --tmux per auto.tmux config.
171
- //
172
- // claude requires --worktree to be present when --tmux is used. The only
173
- // auto mode compatible with that constraint is "worktree", which fires
174
- // when the user is already creating a new worktree themselves:
175
- //
176
- // "worktree" — inject --tmux when the user passed -w / --worktree for
177
- // a NEW worktree (worktreeSet && !worktreeMatched).
178
- // --worktree is already in passthrough; claude's
179
- // constraint is satisfied without fnclaude having to
180
- // generate worktrees itself.
181
- // "never" — no-op.
182
- //
183
- // fnclaude never auto-creates worktrees — that's always user-initiated.
184
- if (
185
- cfg.auto.tmux === 'worktree' &&
186
- !tokenInPassthrough(a.passthrough, '--tmux') &&
187
- !a.noTmux &&
188
- a.worktreeSet &&
189
- !a.worktreeMatched
190
- ) {
191
- argv.push('--tmux');
192
- }
193
-
194
- // 4. System-prompt fragments.
195
- const fragments = selectFragments(prompts, a.passthrough, a.usedNoopFallback);
196
- argv.push(...withAppendedSystemPrompts(a.passthrough, fragments));
197
-
198
- return argv;
199
- }
200
-
201
- // Re-export so callers that already import nameInPassthrough from argv.ts
202
- // (parity with the Go file layout) don't have to reach into passthrough.
203
- export const nameInPassthrough = _nameInPassthrough;