@fnclaude/cli 1.1.1 → 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.
- package/bin/fnc.js +34 -79
- package/package.json +6 -9
- package/share/fnclaude/templates/handoff.template.md +11 -0
- package/src/argv/classify.ts +48 -0
- package/src/argv/expand.ts +51 -0
- package/src/argv/intake.ts +52 -0
- package/src/argv/magic.ts +103 -0
- package/src/argv/parse.ts +213 -0
- package/src/argv/preserve-args.ts +333 -0
- package/src/argv/sentinel.ts +41 -0
- package/src/argv/short-flags.ts +152 -0
- package/src/config/load.ts +116 -0
- package/src/handoff/awaiter.ts +140 -0
- package/src/handoff/clean-env.ts +45 -0
- package/src/handoff/kill-and-exec.ts +110 -0
- package/src/handoff/spawn-launcher.ts +185 -0
- package/src/handoff/summary-file.ts +86 -0
- package/src/handoff/trigger.ts +90 -0
- package/src/help-version.ts +151 -0
- package/src/launch/compose-env.ts +34 -0
- package/src/launch/cross-cwd-parse.ts +69 -0
- package/src/launch/cross-cwd-relaunch.ts +95 -0
- package/src/launch/find-claude.ts +52 -0
- package/src/launch/live-permission-reader.ts +133 -0
- package/src/launch/ring-buffer.ts +92 -0
- package/src/main.ts +580 -437
- package/src/mcp/dispatch.ts +240 -0
- package/src/mcp/handlers/clipboard-backends.ts +176 -0
- package/src/mcp/handlers/clipboard.ts +62 -0
- package/src/mcp/handlers/restart.ts +156 -0
- package/src/mcp/handlers/spawn.ts +219 -0
- package/src/mcp/handlers/switch.ts +272 -0
- package/src/mcp/inject-config.ts +59 -0
- package/src/mcp/jsonrpc-server.ts +154 -0
- package/src/mcp/listener.ts +141 -0
- package/src/mcp/parent-dispatch.ts +154 -0
- package/src/mcp/socket-path.ts +48 -0
- package/src/mcp/wire.ts +181 -0
- package/src/name/auto-name.ts +162 -0
- package/src/name/llm-prompt.ts +14 -0
- package/src/name/sanitize.ts +57 -0
- package/src/name/sdk-llm.ts +42 -0
- package/src/noop/seed.ts +63 -0
- package/src/noop/template-source.ts +62 -0
- package/src/path/ensure-cwd.ts +95 -0
- package/src/path/resolve.ts +58 -0
- package/src/prompts/dir.ts +61 -0
- package/src/prompts/load.ts +100 -0
- package/src/prompts/select.ts +43 -0
- package/src/repo/clone-exec.ts +37 -0
- package/src/repo/clone.ts +45 -0
- package/src/repo/gh-runner.ts +68 -0
- package/src/repo/host-aliases.ts +58 -0
- package/src/repo/owner-lookup.ts +71 -0
- package/src/repo/ref.ts +146 -0
- package/src/repo/repo-settings.ts +99 -0
- package/src/repo/resolve-input.ts +179 -0
- package/src/repo/template.ts +92 -0
- package/src/warnings/buffer.ts +39 -0
- package/src/worktree/auto-tmux.ts +45 -0
- package/src/worktree/git-list.ts +73 -0
- package/src/worktree/intercept.ts +150 -0
- package/bin/preflight.js +0 -66
- package/prompts/agent-pitfall.md +0 -1
- package/prompts/noop-router.md +0 -186
- package/prompts/project-switch.md +0 -64
- package/prompts/restart.md +0 -50
- package/prompts/spawn.md +0 -62
- package/src/argParser.ts +0 -367
- package/src/args/preserve.ts +0 -338
- package/src/args.ts +0 -239
- package/src/argv.ts +0 -219
- package/src/autoname.ts +0 -273
- package/src/clipboard.ts +0 -149
- package/src/config.ts +0 -369
- package/src/errors.ts +0 -13
- package/src/handoff.ts +0 -108
- package/src/help.ts +0 -139
- package/src/hostAliases.ts +0 -139
- package/src/index.ts +0 -120
- package/src/mcp/client.ts +0 -645
- package/src/mcp/protocol.ts +0 -445
- package/src/mcp/socketListener.ts +0 -540
- package/src/noop.ts +0 -106
- package/src/passthrough.ts +0 -36
- package/src/paths.ts +0 -55
- package/src/prompts.ts +0 -279
- package/src/pty/unix.ts +0 -429
- package/src/pty/windows.ts +0 -125
- package/src/pty.ts +0 -380
- package/src/repoRef.ts +0 -158
- package/src/repoSettings.ts +0 -144
- package/src/resolver.ts +0 -519
- package/src/sanitize.ts +0 -120
- package/src/sessionState.ts +0 -220
- package/src/silentRelaunch.ts +0 -178
- package/src/spawn.ts +0 -163
- package/src/template.ts +0 -44
- package/src/warnings.ts +0 -34
- package/src/worktree.ts +0 -201
package/src/worktree.ts
DELETED
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
// Port of the worktree-intercept block of src/main.go (Go reference, lines
|
|
2
|
-
// 623–743). Mirrors gitRunner / worktreeInfo / listWorktrees / findWorktree /
|
|
3
|
-
// applyWorktreeIntercept.
|
|
4
|
-
//
|
|
5
|
-
// Design notes:
|
|
6
|
-
// - The Go reference uses a package-level mutable `gitRunner` var that
|
|
7
|
-
// tests swap out. TS port uses explicit dependency injection — callers
|
|
8
|
-
// pass a GitRunner in, with `defaultGitRunner` exported for production
|
|
9
|
-
// use. Both shapes give tests deterministic control without an env or
|
|
10
|
-
// module-state assumption.
|
|
11
|
-
// - applyWorktreeIntercept is a pure stage transition: takes a
|
|
12
|
-
// `ResolvedArgs`, returns an `InterceptedArgs` that carries the new
|
|
13
|
-
// `worktreeMatched` invariant. The cwd / passthrough overrides flow
|
|
14
|
-
// through `withIntercepted`; nothing is mutated in place.
|
|
15
|
-
|
|
16
|
-
import { isAbsolute, join } from 'node:path';
|
|
17
|
-
import {
|
|
18
|
-
withIntercepted,
|
|
19
|
-
type InterceptedArgs,
|
|
20
|
-
type ResolvedArgs,
|
|
21
|
-
} from './args.js';
|
|
22
|
-
import { nameInPassthrough } from './passthrough.js';
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* GitRunner is a thin wrapper around `git -C <dir> <args...>`. Returns the
|
|
26
|
-
* raw stdout string on success; throws on any git error. The thrown-error
|
|
27
|
-
* path is treated identically to "no match possible" by callers — they
|
|
28
|
-
* never inspect the error itself, so any thrown value is fine.
|
|
29
|
-
*/
|
|
30
|
-
export type GitRunner = (dir: string, ...args: string[]) => string;
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Production GitRunner. Spawns git synchronously via Bun.spawnSync — same
|
|
34
|
-
* mechanism the rest of the codebase uses for its child-process work
|
|
35
|
-
* (autoname, resolver, clipboard, spawn). Node's `execFileSync` here was
|
|
36
|
-
* the lone holdout; switching unifies the spawn layer.
|
|
37
|
-
*
|
|
38
|
-
* Behaviour preserved from the prior `execFileSync` version:
|
|
39
|
-
* - On a successful run (exit 0), returns the UTF-8-decoded stdout.
|
|
40
|
-
* - On non-zero exit, throws an Error with the git stderr verbatim —
|
|
41
|
-
* callers (listWorktrees) catch any thrown value and treat it as
|
|
42
|
-
* "no match possible", so the exact shape of the error doesn't matter
|
|
43
|
-
* beyond being throwable.
|
|
44
|
-
* - If `git` isn't on PATH, Bun.spawnSync throws ENOENT itself (same
|
|
45
|
-
* posture as execFileSync did).
|
|
46
|
-
*/
|
|
47
|
-
export const defaultGitRunner: GitRunner = (dir, ...args) => {
|
|
48
|
-
const proc = Bun.spawnSync(['git', '-C', dir, ...args], {
|
|
49
|
-
stdout: 'pipe',
|
|
50
|
-
stderr: 'pipe',
|
|
51
|
-
});
|
|
52
|
-
if (proc.exitCode !== 0) {
|
|
53
|
-
const stderr = proc.stderr?.toString('utf8') ?? '';
|
|
54
|
-
throw new Error(`git -C ${dir} ${args.join(' ')} exited ${proc.exitCode}: ${stderr.trim()}`);
|
|
55
|
-
}
|
|
56
|
-
return proc.stdout?.toString('utf8') ?? '';
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* WorktreeInfo is one entry from `git worktree list --porcelain`.
|
|
61
|
-
*/
|
|
62
|
-
export interface WorktreeInfo {
|
|
63
|
-
/** Absolute filesystem path of the worktree. */
|
|
64
|
-
path: string;
|
|
65
|
-
/**
|
|
66
|
-
* Bare branch name (e.g. "feat-x" or "worktree-feat-x"); undefined if the
|
|
67
|
-
* worktree is detached.
|
|
68
|
-
*/
|
|
69
|
-
branch: string | undefined;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* listWorktrees runs `git worktree list --porcelain` in dir and parses each
|
|
74
|
-
* blank-line-separated block into a WorktreeInfo. Returns [] on any git
|
|
75
|
-
* error (not-a-repo, git unavailable, etc.) — callers treat [] as "no
|
|
76
|
-
* match possible".
|
|
77
|
-
*/
|
|
78
|
-
export function listWorktrees(dir: string, runner: GitRunner = defaultGitRunner): WorktreeInfo[] {
|
|
79
|
-
let out: string;
|
|
80
|
-
try {
|
|
81
|
-
out = runner(dir, 'worktree', 'list', '--porcelain');
|
|
82
|
-
} catch {
|
|
83
|
-
return [];
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const result: WorktreeInfo[] = [];
|
|
87
|
-
for (const block of out.split('\n\n')) {
|
|
88
|
-
let path = '';
|
|
89
|
-
let branch: string | undefined;
|
|
90
|
-
for (const line of block.split('\n')) {
|
|
91
|
-
if (line.startsWith('worktree ')) {
|
|
92
|
-
path = line.slice('worktree '.length).trim();
|
|
93
|
-
} else if (line.startsWith('branch refs/heads/')) {
|
|
94
|
-
branch = line.slice('branch refs/heads/'.length).trim();
|
|
95
|
-
}
|
|
96
|
-
}
|
|
97
|
-
if (path !== '') {
|
|
98
|
-
result.push({ path, branch });
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
return result;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* findWorktree picks the WorktreeInfo matching `query`, trying three
|
|
106
|
-
* strategies in order. Branch name is checked first since the branch is
|
|
107
|
-
* the semantically stable identifier — its path can be anywhere the
|
|
108
|
-
* creator chose, but its branch is the same string the user typed at
|
|
109
|
-
* creation time.
|
|
110
|
-
*
|
|
111
|
-
* 1. Branch name == query (any worktree, any convention)
|
|
112
|
-
* 2. Branch with `worktree-` prefix stripped == query (matches Claude's
|
|
113
|
-
* default `worktree-<name>` branches)
|
|
114
|
-
* 3. Basename of the path == query (last-resort fallback for worktrees
|
|
115
|
-
* whose branch was renamed or whose creator skipped the convention)
|
|
116
|
-
*
|
|
117
|
-
* Returns undefined when no entry matches. Undefined `query` short-circuits
|
|
118
|
-
* to undefined so that detached worktrees (branch=undefined) can't be
|
|
119
|
-
* matched by accident.
|
|
120
|
-
*/
|
|
121
|
-
export function findWorktree(
|
|
122
|
-
worktrees: readonly WorktreeInfo[],
|
|
123
|
-
query: string | undefined,
|
|
124
|
-
): WorktreeInfo | undefined {
|
|
125
|
-
if (query === undefined) return undefined;
|
|
126
|
-
|
|
127
|
-
for (const wt of worktrees) {
|
|
128
|
-
if (wt.branch === query) return wt;
|
|
129
|
-
}
|
|
130
|
-
for (const wt of worktrees) {
|
|
131
|
-
if (wt.branch !== undefined && stripWorktreePrefix(wt.branch) === query) return wt;
|
|
132
|
-
}
|
|
133
|
-
for (const wt of worktrees) {
|
|
134
|
-
if (basename(wt.path) === query) return wt;
|
|
135
|
-
}
|
|
136
|
-
return undefined;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function stripWorktreePrefix(branch: string): string {
|
|
140
|
-
return branch.startsWith('worktree-') ? branch.slice('worktree-'.length) : branch;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
function basename(p: string): string {
|
|
144
|
-
// `path.basename` collapses trailing slashes the way we want here; the
|
|
145
|
-
// Go reference uses filepath.Base which has the same shape.
|
|
146
|
-
const idx = p.lastIndexOf('/');
|
|
147
|
-
return idx === -1 ? p : p.slice(idx + 1);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/**
|
|
151
|
-
* applyWorktreeIntercept applies the -w / --worktree intercept logic.
|
|
152
|
-
*
|
|
153
|
-
* Pure function: takes a `ResolvedArgs`, returns a new `InterceptedArgs`.
|
|
154
|
-
* No input is mutated. The four cases:
|
|
155
|
-
*
|
|
156
|
-
* 1. worktreeSet=false → carry through with worktreeMatched=false.
|
|
157
|
-
* 2. Bare -w (worktreeArg=undefined) → append --worktree to passthrough,
|
|
158
|
-
* worktreeMatched=false.
|
|
159
|
-
* 3. Existing worktree matched → swap cwd to the worktree path, set
|
|
160
|
-
* worktreeMatched=true, suppress --worktree.
|
|
161
|
-
* 4. Otherwise → append --worktree <name>, plus --name <name> when
|
|
162
|
-
* --name isn't already set; worktreeMatched=false.
|
|
163
|
-
*
|
|
164
|
-
* `shellCWD` is the process working directory at fnclaude startup, used
|
|
165
|
-
* to resolve a relative `cwd` to an absolute path before querying git.
|
|
166
|
-
*/
|
|
167
|
-
export function applyWorktreeIntercept(
|
|
168
|
-
a: ResolvedArgs,
|
|
169
|
-
shellCWD: string,
|
|
170
|
-
runner: GitRunner = defaultGitRunner,
|
|
171
|
-
): InterceptedArgs {
|
|
172
|
-
if (!a.worktreeSet) {
|
|
173
|
-
return withIntercepted(a, { worktreeMatched: false });
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
// Bare -w with no name: push --worktree back through unchanged.
|
|
177
|
-
if (a.worktreeArg === undefined) {
|
|
178
|
-
return withIntercepted(a, {
|
|
179
|
-
passthrough: [...a.passthrough, '--worktree'],
|
|
180
|
-
worktreeMatched: false,
|
|
181
|
-
});
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Resolve absolute cwd for git queries.
|
|
185
|
-
const dir = isAbsolute(a.cwd) ? a.cwd : join(shellCWD, a.cwd);
|
|
186
|
-
|
|
187
|
-
// List worktrees in the project repo, then match the user's query against
|
|
188
|
-
// branch / stripped-branch / basename.
|
|
189
|
-
const hit = findWorktree(listWorktrees(dir, runner), a.worktreeArg);
|
|
190
|
-
if (hit) {
|
|
191
|
-
// Existing worktree matched: swap cwd, suppress -w.
|
|
192
|
-
return withIntercepted(a, { cwd: hit.path, worktreeMatched: true });
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
// No match (or not a repo): pass --worktree through and attach --name.
|
|
196
|
-
const withWt = [...a.passthrough, '--worktree', a.worktreeArg];
|
|
197
|
-
const passthrough = nameInPassthrough(withWt)
|
|
198
|
-
? withWt
|
|
199
|
-
: [...withWt, '--name', a.worktreeArg];
|
|
200
|
-
return withIntercepted(a, { passthrough, worktreeMatched: false });
|
|
201
|
-
}
|