@oh-my-pi/pi-coding-agent 3.15.0 → 3.20.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/CHANGELOG.md +61 -1
- package/docs/extensions.md +1055 -0
- package/docs/rpc.md +69 -13
- package/docs/session-tree-plan.md +1 -1
- package/examples/extensions/README.md +141 -0
- package/examples/extensions/api-demo.ts +87 -0
- package/examples/extensions/chalk-logger.ts +26 -0
- package/examples/extensions/hello.ts +33 -0
- package/examples/extensions/pirate.ts +44 -0
- package/examples/extensions/plan-mode.ts +551 -0
- package/examples/extensions/subagent/agents/reviewer.md +35 -0
- package/examples/extensions/todo.ts +299 -0
- package/examples/extensions/tools.ts +145 -0
- package/examples/extensions/with-deps/index.ts +36 -0
- package/examples/extensions/with-deps/package-lock.json +31 -0
- package/examples/extensions/with-deps/package.json +16 -0
- package/examples/sdk/02-custom-model.ts +3 -3
- package/examples/sdk/05-tools.ts +7 -3
- package/examples/sdk/06-extensions.ts +81 -0
- package/examples/sdk/06-hooks.ts +14 -13
- package/examples/sdk/08-prompt-templates.ts +42 -0
- package/examples/sdk/08-slash-commands.ts +17 -12
- package/examples/sdk/09-api-keys-and-oauth.ts +2 -2
- package/examples/sdk/12-full-control.ts +6 -6
- package/package.json +11 -7
- package/src/capability/extension-module.ts +34 -0
- package/src/cli/args.ts +22 -7
- package/src/cli/file-processor.ts +38 -67
- package/src/cli/list-models.ts +1 -1
- package/src/config.ts +25 -14
- package/src/core/agent-session.ts +505 -242
- package/src/core/auth-storage.ts +33 -21
- package/src/core/compaction/branch-summarization.ts +4 -4
- package/src/core/compaction/compaction.ts +3 -3
- package/src/core/custom-commands/bundled/wt/index.ts +430 -0
- package/src/core/custom-commands/loader.ts +9 -0
- package/src/core/custom-tools/wrapper.ts +5 -0
- package/src/core/event-bus.ts +59 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/extensions/index.ts +100 -0
- package/src/core/extensions/loader.ts +501 -0
- package/src/core/extensions/runner.ts +477 -0
- package/src/core/extensions/types.ts +712 -0
- package/src/core/extensions/wrapper.ts +147 -0
- package/src/core/hooks/types.ts +2 -2
- package/src/core/index.ts +10 -21
- package/src/core/keybindings.ts +199 -0
- package/src/core/messages.ts +26 -7
- package/src/core/model-registry.ts +123 -46
- package/src/core/model-resolver.ts +7 -5
- package/src/core/prompt-templates.ts +242 -0
- package/src/core/sdk.ts +378 -295
- package/src/core/session-manager.ts +72 -58
- package/src/core/settings-manager.ts +118 -22
- package/src/core/system-prompt.ts +24 -1
- package/src/core/terminal-notify.ts +37 -0
- package/src/core/tools/context.ts +4 -4
- package/src/core/tools/exa/mcp-client.ts +5 -4
- package/src/core/tools/exa/render.ts +176 -131
- package/src/core/tools/gemini-image.ts +361 -0
- package/src/core/tools/git.ts +216 -0
- package/src/core/tools/index.ts +28 -15
- package/src/core/tools/lsp/config.ts +5 -4
- package/src/core/tools/lsp/index.ts +17 -12
- package/src/core/tools/lsp/render.ts +39 -47
- package/src/core/tools/read.ts +66 -29
- package/src/core/tools/render-utils.ts +268 -0
- package/src/core/tools/renderers.ts +243 -225
- package/src/core/tools/task/discovery.ts +2 -2
- package/src/core/tools/task/executor.ts +66 -58
- package/src/core/tools/task/index.ts +29 -10
- package/src/core/tools/task/model-resolver.ts +8 -13
- package/src/core/tools/task/omp-command.ts +24 -0
- package/src/core/tools/task/render.ts +35 -60
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/web-fetch.ts +29 -28
- package/src/core/tools/web-search/index.ts +6 -5
- package/src/core/tools/web-search/providers/exa.ts +6 -5
- package/src/core/tools/web-search/render.ts +66 -111
- package/src/core/voice-controller.ts +135 -0
- package/src/core/voice-supervisor.ts +1003 -0
- package/src/core/voice.ts +308 -0
- package/src/discovery/builtin.ts +75 -1
- package/src/discovery/claude.ts +47 -1
- package/src/discovery/codex.ts +54 -2
- package/src/discovery/gemini.ts +55 -2
- package/src/discovery/helpers.ts +100 -1
- package/src/discovery/index.ts +2 -0
- package/src/index.ts +14 -9
- package/src/lib/worktree/collapse.ts +179 -0
- package/src/lib/worktree/constants.ts +14 -0
- package/src/lib/worktree/errors.ts +23 -0
- package/src/lib/worktree/git.ts +110 -0
- package/src/lib/worktree/index.ts +23 -0
- package/src/lib/worktree/operations.ts +216 -0
- package/src/lib/worktree/session.ts +114 -0
- package/src/lib/worktree/stats.ts +67 -0
- package/src/main.ts +61 -37
- package/src/migrations.ts +37 -7
- package/src/modes/interactive/components/bash-execution.ts +6 -4
- package/src/modes/interactive/components/custom-editor.ts +55 -0
- package/src/modes/interactive/components/custom-message.ts +95 -0
- package/src/modes/interactive/components/extensions/extension-list.ts +5 -0
- package/src/modes/interactive/components/extensions/inspector-panel.ts +18 -12
- package/src/modes/interactive/components/extensions/state-manager.ts +12 -0
- package/src/modes/interactive/components/extensions/types.ts +1 -0
- package/src/modes/interactive/components/footer.ts +324 -0
- package/src/modes/interactive/components/hook-editor.ts +1 -0
- package/src/modes/interactive/components/hook-selector.ts +3 -3
- package/src/modes/interactive/components/model-selector.ts +7 -6
- package/src/modes/interactive/components/oauth-selector.ts +3 -3
- package/src/modes/interactive/components/settings-defs.ts +55 -6
- package/src/modes/interactive/components/status-line/separators.ts +4 -4
- package/src/modes/interactive/components/status-line.ts +45 -35
- package/src/modes/interactive/components/tool-execution.ts +95 -23
- package/src/modes/interactive/interactive-mode.ts +644 -113
- package/src/modes/interactive/theme/defaults/alabaster.json +99 -0
- package/src/modes/interactive/theme/defaults/amethyst.json +103 -0
- package/src/modes/interactive/theme/defaults/anthracite.json +100 -0
- package/src/modes/interactive/theme/defaults/basalt.json +90 -0
- package/src/modes/interactive/theme/defaults/birch.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-abyss.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-aurora.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-cavern.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-copper.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-cosmos.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-eclipse.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-ember.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-equinox.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-lavender.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-lunar.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-midnight.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-nebula.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-rainforest.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-reef.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-sakura.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-slate.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-solstice.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-starfall.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-swamp.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-taiga.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-terminal.json +94 -0
- package/src/modes/interactive/theme/defaults/dark-tundra.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-twilight.json +97 -0
- package/src/modes/interactive/theme/defaults/dark-volcanic.json +97 -0
- package/src/modes/interactive/theme/defaults/graphite.json +99 -0
- package/src/modes/interactive/theme/defaults/index.ts +128 -0
- package/src/modes/interactive/theme/defaults/light-aurora-day.json +97 -0
- package/src/modes/interactive/theme/defaults/light-canyon.json +97 -0
- package/src/modes/interactive/theme/defaults/light-cirrus.json +96 -0
- package/src/modes/interactive/theme/defaults/light-coral.json +94 -0
- package/src/modes/interactive/theme/defaults/light-dawn.json +96 -0
- package/src/modes/interactive/theme/defaults/light-dunes.json +97 -0
- package/src/modes/interactive/theme/defaults/light-eucalyptus.json +94 -0
- package/src/modes/interactive/theme/defaults/light-frost.json +94 -0
- package/src/modes/interactive/theme/defaults/light-glacier.json +97 -0
- package/src/modes/interactive/theme/defaults/light-haze.json +96 -0
- package/src/modes/interactive/theme/defaults/light-honeycomb.json +94 -0
- package/src/modes/interactive/theme/defaults/light-lagoon.json +97 -0
- package/src/modes/interactive/theme/defaults/light-lavender.json +94 -0
- package/src/modes/interactive/theme/defaults/light-meadow.json +97 -0
- package/src/modes/interactive/theme/defaults/light-mint.json +94 -0
- package/src/modes/interactive/theme/defaults/light-opal.json +97 -0
- package/src/modes/interactive/theme/defaults/light-orchard.json +97 -0
- package/src/modes/interactive/theme/defaults/light-paper.json +94 -0
- package/src/modes/interactive/theme/defaults/light-prism.json +96 -0
- package/src/modes/interactive/theme/defaults/light-sand.json +94 -0
- package/src/modes/interactive/theme/defaults/light-savanna.json +97 -0
- package/src/modes/interactive/theme/defaults/light-soleil.json +96 -0
- package/src/modes/interactive/theme/defaults/light-wetland.json +97 -0
- package/src/modes/interactive/theme/defaults/light-zenith.json +95 -0
- package/src/modes/interactive/theme/defaults/limestone.json +100 -0
- package/src/modes/interactive/theme/defaults/mahogany.json +104 -0
- package/src/modes/interactive/theme/defaults/marble.json +99 -0
- package/src/modes/interactive/theme/defaults/obsidian.json +90 -0
- package/src/modes/interactive/theme/defaults/onyx.json +90 -0
- package/src/modes/interactive/theme/defaults/pearl.json +99 -0
- package/src/modes/interactive/theme/defaults/porcelain.json +90 -0
- package/src/modes/interactive/theme/defaults/quartz.json +102 -0
- package/src/modes/interactive/theme/defaults/sandstone.json +101 -0
- package/src/modes/interactive/theme/defaults/titanium.json +89 -0
- package/src/modes/print-mode.ts +14 -72
- package/src/modes/rpc/rpc-client.ts +23 -9
- package/src/modes/rpc/rpc-mode.ts +137 -125
- package/src/modes/rpc/rpc-types.ts +46 -24
- package/src/prompts/task.md +1 -0
- package/src/prompts/tools/gemini-image.md +4 -0
- package/src/prompts/tools/git.md +9 -0
- package/src/prompts/voice-summary.md +12 -0
- package/src/utils/image-convert.ts +26 -0
- package/src/utils/image-resize.ts +215 -0
- package/src/utils/shell-snapshot.ts +22 -20
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { WORKTREE_BASE } from "./constants";
|
|
4
|
+
import { WorktreeError, WorktreeErrorCode } from "./errors";
|
|
5
|
+
import { getRepoName, getRepoRoot, git } from "./git";
|
|
6
|
+
|
|
7
|
+
export interface Worktree {
|
|
8
|
+
path: string;
|
|
9
|
+
branch: string | null;
|
|
10
|
+
head: string;
|
|
11
|
+
isMain: boolean;
|
|
12
|
+
isDetached: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
type WorktreePartial = Partial<Worktree> & { isDetached?: boolean };
|
|
16
|
+
|
|
17
|
+
function finalizeWorktree(entry: WorktreePartial, repoRoot: string): Worktree {
|
|
18
|
+
const wtPath = entry.path?.trim();
|
|
19
|
+
if (!wtPath) {
|
|
20
|
+
throw new Error("Invalid worktree entry");
|
|
21
|
+
}
|
|
22
|
+
const branch = entry.isDetached ? null : (entry.branch ?? null);
|
|
23
|
+
const isDetached = entry.isDetached ?? branch === null;
|
|
24
|
+
return {
|
|
25
|
+
path: wtPath,
|
|
26
|
+
branch,
|
|
27
|
+
head: entry.head ?? "",
|
|
28
|
+
isMain: path.resolve(wtPath) === path.resolve(repoRoot),
|
|
29
|
+
isDetached,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function parseWorktreeList(output: string, repoRoot: string): Worktree[] {
|
|
34
|
+
const worktrees: Worktree[] = [];
|
|
35
|
+
let current: WorktreePartial = {};
|
|
36
|
+
|
|
37
|
+
for (const line of output.split("\n")) {
|
|
38
|
+
if (line.startsWith("worktree ")) {
|
|
39
|
+
if (current.path) {
|
|
40
|
+
worktrees.push(finalizeWorktree(current, repoRoot));
|
|
41
|
+
}
|
|
42
|
+
current = { path: line.slice(9) };
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (line.startsWith("HEAD ")) {
|
|
47
|
+
current.head = line.slice(5);
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (line.startsWith("branch ")) {
|
|
52
|
+
const raw = line.slice(7);
|
|
53
|
+
current.branch = raw.startsWith("refs/heads/") ? raw.slice("refs/heads/".length) : raw;
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
if (line === "detached") {
|
|
58
|
+
current.isDetached = true;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (current.path) {
|
|
63
|
+
worktrees.push(finalizeWorktree(current, repoRoot));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return worktrees;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Create a new worktree.
|
|
71
|
+
*/
|
|
72
|
+
export async function create(branch: string, options?: { base?: string; path?: string }): Promise<Worktree> {
|
|
73
|
+
const repoRoot = await getRepoRoot();
|
|
74
|
+
const repoName = await getRepoName();
|
|
75
|
+
const targetPath = options?.path ?? path.join(WORKTREE_BASE, repoName, branch);
|
|
76
|
+
const resolvedTarget = path.resolve(targetPath);
|
|
77
|
+
|
|
78
|
+
const existing = await list();
|
|
79
|
+
const conflict = existing.find((wt) => wt.branch === branch || path.resolve(wt.path) === resolvedTarget);
|
|
80
|
+
if (conflict) {
|
|
81
|
+
throw new WorktreeError(`Worktree already exists: ${conflict.path}`, WorktreeErrorCode.WORKTREE_EXISTS);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
await mkdir(path.dirname(resolvedTarget), { recursive: true });
|
|
85
|
+
|
|
86
|
+
const branchExists = (await git(["rev-parse", "--verify", `refs/heads/${branch}`], repoRoot)).code === 0;
|
|
87
|
+
|
|
88
|
+
const args = branchExists
|
|
89
|
+
? ["worktree", "add", resolvedTarget, branch]
|
|
90
|
+
: ["worktree", "add", "-b", branch, resolvedTarget, options?.base ?? "HEAD"];
|
|
91
|
+
|
|
92
|
+
const result = await git(args, repoRoot);
|
|
93
|
+
if (result.code !== 0) {
|
|
94
|
+
const stderr = result.stderr.trim();
|
|
95
|
+
if (stderr.includes("already exists") || stderr.includes("already checked out")) {
|
|
96
|
+
throw new WorktreeError(stderr || "Worktree already exists", WorktreeErrorCode.WORKTREE_EXISTS);
|
|
97
|
+
}
|
|
98
|
+
throw new Error(stderr || "Failed to create worktree");
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const updated = await list();
|
|
102
|
+
const created = updated.find((wt) => path.resolve(wt.path) === resolvedTarget);
|
|
103
|
+
if (!created) {
|
|
104
|
+
throw new Error("Worktree created but not found in list");
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return created;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* List all worktrees for current repository.
|
|
112
|
+
*/
|
|
113
|
+
export async function list(): Promise<Worktree[]> {
|
|
114
|
+
const repoRoot = await getRepoRoot();
|
|
115
|
+
const result = await git(["worktree", "list", "--porcelain"], repoRoot);
|
|
116
|
+
if (result.code !== 0) {
|
|
117
|
+
throw new Error(result.stderr.trim() || "Failed to list worktrees");
|
|
118
|
+
}
|
|
119
|
+
return parseWorktreeList(result.stdout, repoRoot);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Find a worktree by pattern.
|
|
124
|
+
*/
|
|
125
|
+
export async function find(pattern: string): Promise<Worktree> {
|
|
126
|
+
const worktrees = await list();
|
|
127
|
+
|
|
128
|
+
const exactBranch = worktrees.filter((wt) => wt.branch === pattern);
|
|
129
|
+
if (exactBranch.length === 1) return exactBranch[0];
|
|
130
|
+
if (exactBranch.length > 1) {
|
|
131
|
+
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const exactDir = worktrees.filter((wt) => path.basename(wt.path) === pattern);
|
|
135
|
+
if (exactDir.length === 1) return exactDir[0];
|
|
136
|
+
if (exactDir.length > 1) {
|
|
137
|
+
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const partialBranch = worktrees.filter((wt) => wt.branch?.includes(pattern));
|
|
141
|
+
if (partialBranch.length === 1) return partialBranch[0];
|
|
142
|
+
if (partialBranch.length > 1) {
|
|
143
|
+
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const partialPath = worktrees.filter((wt) => wt.path.includes(pattern));
|
|
147
|
+
if (partialPath.length === 1) return partialPath[0];
|
|
148
|
+
if (partialPath.length > 1) {
|
|
149
|
+
throw new WorktreeError(`Ambiguous worktree: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
throw new WorktreeError(`Worktree not found: ${pattern}`, WorktreeErrorCode.WORKTREE_NOT_FOUND);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Remove a worktree.
|
|
157
|
+
*/
|
|
158
|
+
export async function remove(nameOrPath: string, options?: { force?: boolean }): Promise<void> {
|
|
159
|
+
const wt = await find(nameOrPath);
|
|
160
|
+
if (wt.isMain) {
|
|
161
|
+
throw new WorktreeError("Cannot remove main worktree", WorktreeErrorCode.CANNOT_MODIFY_MAIN);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const repoRoot = await getRepoRoot();
|
|
165
|
+
const args = ["worktree", "remove", wt.path];
|
|
166
|
+
if (options?.force) args.push("--force");
|
|
167
|
+
|
|
168
|
+
const result = await git(args, repoRoot);
|
|
169
|
+
if (result.code !== 0) {
|
|
170
|
+
throw new Error(result.stderr.trim() || "Failed to remove worktree");
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* Remove worktrees for branches that no longer exist.
|
|
176
|
+
*/
|
|
177
|
+
export async function prune(): Promise<number> {
|
|
178
|
+
const repoRoot = await getRepoRoot();
|
|
179
|
+
const worktrees = await list();
|
|
180
|
+
let removed = 0;
|
|
181
|
+
|
|
182
|
+
for (const wt of worktrees) {
|
|
183
|
+
if (wt.isMain || !wt.branch) continue;
|
|
184
|
+
const existsResult = await git(["rev-parse", "--verify", `refs/heads/${wt.branch}`], repoRoot);
|
|
185
|
+
if (existsResult.code === 0) continue;
|
|
186
|
+
|
|
187
|
+
const result = await git(["worktree", "remove", wt.path], repoRoot);
|
|
188
|
+
if (result.code !== 0) {
|
|
189
|
+
throw new Error(result.stderr.trim() || `Failed to remove worktree: ${wt.path}`);
|
|
190
|
+
}
|
|
191
|
+
removed += 1;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return removed;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get the worktree containing the given path.
|
|
199
|
+
* Returns null if path is not in any worktree.
|
|
200
|
+
*/
|
|
201
|
+
export async function which(targetPath?: string): Promise<Worktree | null> {
|
|
202
|
+
const worktrees = await list();
|
|
203
|
+
const resolved = path.resolve(targetPath ?? process.cwd());
|
|
204
|
+
|
|
205
|
+
let best: Worktree | null = null;
|
|
206
|
+
for (const wt of worktrees) {
|
|
207
|
+
const wtPath = path.resolve(wt.path);
|
|
208
|
+
if (resolved === wtPath || resolved.startsWith(wtPath + path.sep)) {
|
|
209
|
+
if (!best || wtPath.length > best.path.length) {
|
|
210
|
+
best = wt;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return best;
|
|
216
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { mkdir } from "node:fs/promises";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import { nanoid } from "nanoid";
|
|
4
|
+
import { getRepoRoot, git } from "./git";
|
|
5
|
+
|
|
6
|
+
export interface WorktreeSession {
|
|
7
|
+
id: string;
|
|
8
|
+
branch: string;
|
|
9
|
+
path: string;
|
|
10
|
+
scope?: string[];
|
|
11
|
+
agentId?: string;
|
|
12
|
+
task?: string;
|
|
13
|
+
status: SessionStatus;
|
|
14
|
+
createdAt: number;
|
|
15
|
+
completedAt?: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export type SessionStatus = "creating" | "active" | "completed" | "merging" | "merged" | "failed" | "abandoned";
|
|
19
|
+
|
|
20
|
+
async function getSessionsFile(): Promise<string> {
|
|
21
|
+
const repoRoot = await getRepoRoot();
|
|
22
|
+
const result = await git(["rev-parse", "--git-common-dir"], repoRoot);
|
|
23
|
+
let gitDir = result.code === 0 ? result.stdout.trim() : "";
|
|
24
|
+
if (!gitDir) {
|
|
25
|
+
gitDir = path.join(repoRoot, ".git");
|
|
26
|
+
}
|
|
27
|
+
if (!path.isAbsolute(gitDir)) {
|
|
28
|
+
// Resolve relative git dir from repo root to keep sessions in the common dir.
|
|
29
|
+
gitDir = path.resolve(repoRoot, gitDir);
|
|
30
|
+
}
|
|
31
|
+
await mkdir(gitDir, { recursive: true });
|
|
32
|
+
return path.join(gitDir, "worktree-sessions.json");
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function loadSessions(): Promise<WorktreeSession[]> {
|
|
36
|
+
const filePath = await getSessionsFile();
|
|
37
|
+
const file = Bun.file(filePath);
|
|
38
|
+
if (!(await file.exists())) return [];
|
|
39
|
+
try {
|
|
40
|
+
const data = await file.json();
|
|
41
|
+
if (Array.isArray(data)) {
|
|
42
|
+
return data as WorktreeSession[];
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function saveSessions(sessions: WorktreeSession[]): Promise<void> {
|
|
51
|
+
const filePath = await getSessionsFile();
|
|
52
|
+
await Bun.write(filePath, JSON.stringify(sessions, null, 2));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export async function createSession(params: {
|
|
56
|
+
branch: string;
|
|
57
|
+
path: string;
|
|
58
|
+
scope?: string[];
|
|
59
|
+
task?: string;
|
|
60
|
+
}): Promise<WorktreeSession> {
|
|
61
|
+
const sessions = await loadSessions();
|
|
62
|
+
const session: WorktreeSession = {
|
|
63
|
+
id: nanoid(10),
|
|
64
|
+
branch: params.branch,
|
|
65
|
+
path: params.path,
|
|
66
|
+
scope: params.scope,
|
|
67
|
+
task: params.task,
|
|
68
|
+
status: "creating",
|
|
69
|
+
createdAt: Date.now(),
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
sessions.push(session);
|
|
73
|
+
await saveSessions(sessions);
|
|
74
|
+
return session;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function updateSession(id: string, updates: Partial<WorktreeSession>): Promise<void> {
|
|
78
|
+
const sessions = await loadSessions();
|
|
79
|
+
const idx = sessions.findIndex((s) => s.id === id);
|
|
80
|
+
if (idx === -1) return;
|
|
81
|
+
const current = sessions[idx];
|
|
82
|
+
sessions[idx] = { ...current, ...updates, id: current.id };
|
|
83
|
+
await saveSessions(sessions);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export async function getSession(id: string): Promise<WorktreeSession | null> {
|
|
87
|
+
const sessions = await loadSessions();
|
|
88
|
+
return sessions.find((s) => s.id === id) ?? null;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function listSessions(): Promise<WorktreeSession[]> {
|
|
92
|
+
return loadSessions();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export async function cleanupSessions(): Promise<number> {
|
|
96
|
+
const sessions = await loadSessions();
|
|
97
|
+
let removed = 0;
|
|
98
|
+
|
|
99
|
+
const remaining: WorktreeSession[] = [];
|
|
100
|
+
for (const session of sessions) {
|
|
101
|
+
const exists = await Bun.file(session.path).exists();
|
|
102
|
+
if (!exists) {
|
|
103
|
+
removed += 1;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
remaining.push(session);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (removed > 0) {
|
|
110
|
+
await saveSessions(remaining);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return removed;
|
|
114
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { git } from "./git";
|
|
2
|
+
|
|
3
|
+
export interface WorktreeStats {
|
|
4
|
+
additions: number;
|
|
5
|
+
deletions: number;
|
|
6
|
+
untracked: number;
|
|
7
|
+
modified: number;
|
|
8
|
+
staged: number;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Get diff statistics for a worktree.
|
|
13
|
+
*/
|
|
14
|
+
export async function getStats(worktreePath: string): Promise<WorktreeStats> {
|
|
15
|
+
const diffResult = await git(["diff", "HEAD", "--shortstat"], worktreePath);
|
|
16
|
+
|
|
17
|
+
let additions = 0;
|
|
18
|
+
let deletions = 0;
|
|
19
|
+
|
|
20
|
+
const statsLine = diffResult.stdout.trim();
|
|
21
|
+
if (statsLine) {
|
|
22
|
+
const insertMatch = statsLine.match(/(\d+) insertion/);
|
|
23
|
+
const deleteMatch = statsLine.match(/(\d+) deletion/);
|
|
24
|
+
if (insertMatch) additions = parseInt(insertMatch[1], 10);
|
|
25
|
+
if (deleteMatch) deletions = parseInt(deleteMatch[1], 10);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const untrackedResult = await git(["ls-files", "--others", "--exclude-standard"], worktreePath);
|
|
29
|
+
const untracked = untrackedResult.stdout.trim() ? untrackedResult.stdout.trim().split("\n").length : 0;
|
|
30
|
+
|
|
31
|
+
const statusResult = await git(["status", "--porcelain"], worktreePath);
|
|
32
|
+
let modified = 0;
|
|
33
|
+
let staged = 0;
|
|
34
|
+
|
|
35
|
+
for (const line of statusResult.stdout.split("\n")) {
|
|
36
|
+
if (!line) continue;
|
|
37
|
+
const index = line[0];
|
|
38
|
+
const worktree = line[1];
|
|
39
|
+
if (index !== " " && index !== "?") staged += 1;
|
|
40
|
+
if (worktree !== " " && worktree !== "?") modified += 1;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return { additions, deletions, untracked, modified, staged };
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Format stats for display.
|
|
48
|
+
* Returns "clean" or "+N -M ?U" format.
|
|
49
|
+
*/
|
|
50
|
+
export function formatStats(stats: WorktreeStats): string {
|
|
51
|
+
if (
|
|
52
|
+
stats.additions === 0 &&
|
|
53
|
+
stats.deletions === 0 &&
|
|
54
|
+
stats.untracked === 0 &&
|
|
55
|
+
stats.modified === 0 &&
|
|
56
|
+
stats.staged === 0
|
|
57
|
+
) {
|
|
58
|
+
return "clean";
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const parts: string[] = [];
|
|
62
|
+
if (stats.additions > 0) parts.push(`+${stats.additions}`);
|
|
63
|
+
if (stats.deletions > 0) parts.push(`-${stats.deletions}`);
|
|
64
|
+
if (stats.untracked > 0) parts.push(`?${stats.untracked}`);
|
|
65
|
+
|
|
66
|
+
return parts.join(" ") || "clean";
|
|
67
|
+
}
|
package/src/main.ts
CHANGED
|
@@ -15,9 +15,8 @@ import { selectSession } from "./cli/session-picker";
|
|
|
15
15
|
import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
|
|
16
16
|
import { findConfigFile, getModelsPath, VERSION } from "./config";
|
|
17
17
|
import type { AgentSession } from "./core/agent-session";
|
|
18
|
-
import type { LoadedCustomTool } from "./core/custom-tools/index";
|
|
19
18
|
import { exportFromFile } from "./core/export-html/index";
|
|
20
|
-
import type {
|
|
19
|
+
import type { ExtensionUIContext } from "./core/index";
|
|
21
20
|
import type { ModelRegistry } from "./core/model-registry";
|
|
22
21
|
import { parseModelPattern, resolveModelScope, type ScopedModel } from "./core/model-resolver";
|
|
23
22
|
import { type CreateAgentSessionOptions, createAgentSession, discoverAuthStorage, discoverModels } from "./core/sdk";
|
|
@@ -26,7 +25,7 @@ import { SettingsManager } from "./core/settings-manager";
|
|
|
26
25
|
import { resolvePromptInput } from "./core/system-prompt";
|
|
27
26
|
import { printTimings, time } from "./core/timings";
|
|
28
27
|
import { allTools } from "./core/tools/index";
|
|
29
|
-
import { runMigrations } from "./migrations";
|
|
28
|
+
import { runMigrations, showDeprecationWarnings } from "./migrations";
|
|
30
29
|
import { InteractiveMode, installTerminalCrashHandlers, runPrintMode, runRpcMode } from "./modes/index";
|
|
31
30
|
import { initTheme, stopThemeWatcher } from "./modes/interactive/theme/theme";
|
|
32
31
|
import { getChangelogPath, getNewEntries, parseChangelog } from "./utils/changelog";
|
|
@@ -59,22 +58,13 @@ async function runInteractiveMode(
|
|
|
59
58
|
migratedProviders: string[],
|
|
60
59
|
versionCheckPromise: Promise<string | undefined>,
|
|
61
60
|
initialMessages: string[],
|
|
62
|
-
|
|
63
|
-
setToolUIContext: (uiContext: HookUIContext, hasUI: boolean) => void,
|
|
61
|
+
setExtensionUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void,
|
|
64
62
|
lspServers: Array<{ name: string; status: "ready" | "error"; fileTypes: string[] }> | undefined,
|
|
65
63
|
initialMessage?: string,
|
|
66
64
|
initialImages?: ImageContent[],
|
|
67
65
|
fdPath: string | undefined = undefined,
|
|
68
66
|
): Promise<void> {
|
|
69
|
-
const mode = new InteractiveMode(
|
|
70
|
-
session,
|
|
71
|
-
version,
|
|
72
|
-
changelogMarkdown,
|
|
73
|
-
customTools,
|
|
74
|
-
setToolUIContext,
|
|
75
|
-
lspServers,
|
|
76
|
-
fdPath,
|
|
77
|
-
);
|
|
67
|
+
const mode = new InteractiveMode(session, version, changelogMarkdown, setExtensionUIContext, lspServers, fdPath);
|
|
78
68
|
|
|
79
69
|
await mode.init();
|
|
80
70
|
|
|
@@ -127,7 +117,10 @@ async function runInteractiveMode(
|
|
|
127
117
|
}
|
|
128
118
|
}
|
|
129
119
|
|
|
130
|
-
async function prepareInitialMessage(
|
|
120
|
+
async function prepareInitialMessage(
|
|
121
|
+
parsed: Args,
|
|
122
|
+
autoResizeImages: boolean,
|
|
123
|
+
): Promise<{
|
|
131
124
|
initialMessage?: string;
|
|
132
125
|
initialImages?: ImageContent[];
|
|
133
126
|
}> {
|
|
@@ -135,7 +128,7 @@ async function prepareInitialMessage(parsed: Args): Promise<{
|
|
|
135
128
|
return {};
|
|
136
129
|
}
|
|
137
130
|
|
|
138
|
-
const { text, images } = await processFileArguments(parsed.fileArgs);
|
|
131
|
+
const { text, images } = await processFileArguments(parsed.fileArgs, { autoResizeImages });
|
|
139
132
|
|
|
140
133
|
let initialMessage: string;
|
|
141
134
|
if (parsed.messages.length > 0) {
|
|
@@ -215,6 +208,7 @@ async function buildSessionOptions(
|
|
|
215
208
|
scopedModels: ScopedModel[],
|
|
216
209
|
sessionManager: SessionManager | undefined,
|
|
217
210
|
modelRegistry: ModelRegistry,
|
|
211
|
+
settingsManager: SettingsManager,
|
|
218
212
|
): Promise<CreateAgentSessionOptions> {
|
|
219
213
|
const options: CreateAgentSessionOptions = {};
|
|
220
214
|
|
|
@@ -229,7 +223,7 @@ async function buildSessionOptions(
|
|
|
229
223
|
|
|
230
224
|
// Model from CLI (--model) - uses same fuzzy matching as --models
|
|
231
225
|
if (parsed.model) {
|
|
232
|
-
const available =
|
|
226
|
+
const available = modelRegistry.getAvailable();
|
|
233
227
|
const { model, warning } = parseModelPattern(parsed.model, available);
|
|
234
228
|
if (warning) {
|
|
235
229
|
console.warn(chalk.yellow(`Warning: ${warning}`));
|
|
@@ -276,16 +270,20 @@ async function buildSessionOptions(
|
|
|
276
270
|
// Skills
|
|
277
271
|
if (parsed.noSkills) {
|
|
278
272
|
options.skills = [];
|
|
273
|
+
} else if (parsed.skills && parsed.skills.length > 0) {
|
|
274
|
+
// Override includeSkills in settingsManager for this session
|
|
275
|
+
settingsManager.applyOverrides({
|
|
276
|
+
skills: {
|
|
277
|
+
...settingsManager.getSkillsSettings(),
|
|
278
|
+
includeSkills: parsed.skills,
|
|
279
|
+
},
|
|
280
|
+
});
|
|
279
281
|
}
|
|
280
282
|
|
|
281
|
-
// Additional
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
// Additional custom tool paths from CLI
|
|
287
|
-
if (parsed.customTools && parsed.customTools.length > 0) {
|
|
288
|
-
options.additionalCustomToolPaths = parsed.customTools;
|
|
283
|
+
// Additional extension paths from CLI
|
|
284
|
+
const cliExtensionPaths = [...(parsed.extensions ?? []), ...(parsed.hooks ?? [])];
|
|
285
|
+
if (cliExtensionPaths.length > 0) {
|
|
286
|
+
options.additionalExtensionPaths = cliExtensionPaths;
|
|
289
287
|
}
|
|
290
288
|
|
|
291
289
|
return options;
|
|
@@ -320,12 +318,12 @@ export async function main(args: string[]) {
|
|
|
320
318
|
return;
|
|
321
319
|
}
|
|
322
320
|
|
|
323
|
-
// Run migrations
|
|
324
|
-
const { migratedAuthProviders: migratedProviders } = runMigrations();
|
|
321
|
+
// Run migrations (pass cwd for project-local migrations)
|
|
322
|
+
const { migratedAuthProviders: migratedProviders, deprecationWarnings } = await runMigrations(process.cwd());
|
|
325
323
|
|
|
326
324
|
// Create AuthStorage and ModelRegistry upfront
|
|
327
|
-
const authStorage = discoverAuthStorage();
|
|
328
|
-
const modelRegistry = discoverModels(authStorage);
|
|
325
|
+
const authStorage = await discoverAuthStorage();
|
|
326
|
+
const modelRegistry = await discoverModels(authStorage);
|
|
329
327
|
time("discoverModels");
|
|
330
328
|
|
|
331
329
|
const parsed = parseArgs(args);
|
|
@@ -366,14 +364,13 @@ export async function main(args: string[]) {
|
|
|
366
364
|
}
|
|
367
365
|
|
|
368
366
|
const cwd = process.cwd();
|
|
369
|
-
const
|
|
367
|
+
const settingsManager = SettingsManager.create(cwd);
|
|
368
|
+
time("SettingsManager.create");
|
|
369
|
+
const { initialMessage, initialImages } = await prepareInitialMessage(parsed, settingsManager.getImageAutoResize());
|
|
370
370
|
time("prepareInitialMessage");
|
|
371
371
|
const isInteractive = !parsed.print && parsed.mode === undefined;
|
|
372
372
|
const mode = parsed.mode || "text";
|
|
373
373
|
|
|
374
|
-
const settingsManager = SettingsManager.create(cwd);
|
|
375
|
-
time("SettingsManager.create");
|
|
376
|
-
|
|
377
374
|
// Initialize discovery system with settings for provider persistence
|
|
378
375
|
const { initializeWithSettings } = await import("./discovery");
|
|
379
376
|
initializeWithSettings(settingsManager);
|
|
@@ -392,6 +389,11 @@ export async function main(args: string[]) {
|
|
|
392
389
|
initTheme(settingsManager.getTheme(), isInteractive, settingsManager.getSymbolPreset());
|
|
393
390
|
time("initTheme");
|
|
394
391
|
|
|
392
|
+
// Show deprecation warnings in interactive mode
|
|
393
|
+
if (isInteractive && deprecationWarnings.length > 0) {
|
|
394
|
+
await showDeprecationWarnings(deprecationWarnings);
|
|
395
|
+
}
|
|
396
|
+
|
|
395
397
|
let scopedModels: ScopedModel[] = [];
|
|
396
398
|
const modelPatterns = parsed.models ?? settingsManager.getEnabledModels();
|
|
397
399
|
if (modelPatterns && modelPatterns.length > 0) {
|
|
@@ -420,9 +422,16 @@ export async function main(args: string[]) {
|
|
|
420
422
|
sessionManager = await SessionManager.open(selectedPath);
|
|
421
423
|
}
|
|
422
424
|
|
|
423
|
-
const sessionOptions = await buildSessionOptions(
|
|
425
|
+
const sessionOptions = await buildSessionOptions(
|
|
426
|
+
parsed,
|
|
427
|
+
scopedModels,
|
|
428
|
+
sessionManager,
|
|
429
|
+
modelRegistry,
|
|
430
|
+
settingsManager,
|
|
431
|
+
);
|
|
424
432
|
sessionOptions.authStorage = authStorage;
|
|
425
433
|
sessionOptions.modelRegistry = modelRegistry;
|
|
434
|
+
sessionOptions.settingsManager = settingsManager;
|
|
426
435
|
sessionOptions.hasUI = isInteractive;
|
|
427
436
|
|
|
428
437
|
// Handle CLI --api-key as runtime override (not persisted)
|
|
@@ -435,9 +444,25 @@ export async function main(args: string[]) {
|
|
|
435
444
|
}
|
|
436
445
|
|
|
437
446
|
time("buildSessionOptions");
|
|
438
|
-
const { session,
|
|
447
|
+
const { session, extensionsResult, modelFallbackMessage, lspServers } = await createAgentSession(sessionOptions);
|
|
439
448
|
time("createAgentSession");
|
|
440
449
|
|
|
450
|
+
// Re-parse CLI args with extension flags and apply values
|
|
451
|
+
if (session.extensionRunner) {
|
|
452
|
+
const extFlags = session.extensionRunner.getFlags();
|
|
453
|
+
if (extFlags.size > 0) {
|
|
454
|
+
const flagDefs = new Map<string, { type: "boolean" | "string" }>();
|
|
455
|
+
for (const [name, flag] of extFlags) {
|
|
456
|
+
flagDefs.set(name, { type: flag.type });
|
|
457
|
+
}
|
|
458
|
+
const reparsed = parseArgs(args, flagDefs);
|
|
459
|
+
for (const [name, value] of reparsed.unknownFlags) {
|
|
460
|
+
session.extensionRunner.setFlagValue(name, value);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
time("applyExtensionFlags");
|
|
465
|
+
|
|
441
466
|
if (!isInteractive && !session.model) {
|
|
442
467
|
console.error(chalk.red("No models available."));
|
|
443
468
|
console.error(chalk.yellow("\nSet an API key environment variable:"));
|
|
@@ -489,8 +514,7 @@ export async function main(args: string[]) {
|
|
|
489
514
|
migratedProviders,
|
|
490
515
|
versionCheckPromise,
|
|
491
516
|
parsed.messages,
|
|
492
|
-
|
|
493
|
-
customToolsResult.setUIContext,
|
|
517
|
+
extensionsResult.setUIContext,
|
|
494
518
|
lspServers,
|
|
495
519
|
initialMessage,
|
|
496
520
|
initialImages,
|
package/src/migrations.ts
CHANGED
|
@@ -4,22 +4,28 @@
|
|
|
4
4
|
|
|
5
5
|
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
6
6
|
import { dirname, join } from "node:path";
|
|
7
|
+
import chalk from "chalk";
|
|
7
8
|
import { getAgentDir } from "./config";
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Migrate PI_* environment variables to OMP_* equivalents.
|
|
11
12
|
* If PI_XX is set and OMP_XX is not, set OMP_XX to PI_XX's value.
|
|
12
13
|
* This provides backwards compatibility for users with existing PI_* env vars.
|
|
14
|
+
*
|
|
15
|
+
* @returns Array of PI_* env var names that were migrated
|
|
13
16
|
*/
|
|
14
|
-
export function migrateEnvVars():
|
|
17
|
+
export function migrateEnvVars(): string[] {
|
|
18
|
+
const migrated: string[] = [];
|
|
15
19
|
for (const [key, value] of Object.entries(process.env)) {
|
|
16
20
|
if (key.startsWith("PI_") && value !== undefined) {
|
|
17
21
|
const ompKey = `OMP_${key.slice(3)}`; // PI_FOO -> OMP_FOO
|
|
18
22
|
if (process.env[ompKey] === undefined) {
|
|
19
23
|
process.env[ompKey] = value;
|
|
24
|
+
migrated.push(key);
|
|
20
25
|
}
|
|
21
26
|
}
|
|
22
27
|
}
|
|
28
|
+
return migrated;
|
|
23
29
|
}
|
|
24
30
|
|
|
25
31
|
/**
|
|
@@ -122,9 +128,7 @@ export function migrateSessionsFromAgentRoot(): void {
|
|
|
122
128
|
const correctDir = join(agentDir, "sessions", safePath);
|
|
123
129
|
|
|
124
130
|
// Create directory if needed
|
|
125
|
-
|
|
126
|
-
mkdirSync(correctDir, { recursive: true });
|
|
127
|
-
}
|
|
131
|
+
mkdirSync(correctDir, { recursive: true });
|
|
128
132
|
|
|
129
133
|
// Move the file
|
|
130
134
|
const fileName = file.split("/").pop() || file.split("\\").pop();
|
|
@@ -142,15 +146,41 @@ export function migrateSessionsFromAgentRoot(): void {
|
|
|
142
146
|
/**
|
|
143
147
|
* Run all migrations. Called once on startup.
|
|
144
148
|
*
|
|
149
|
+
* @param _cwd - Current working directory (reserved for future project-local migrations)
|
|
145
150
|
* @returns Object with migration results
|
|
146
151
|
*/
|
|
147
|
-
export function runMigrations(
|
|
152
|
+
export async function runMigrations(_cwd: string): Promise<{
|
|
153
|
+
migratedAuthProviders: string[];
|
|
154
|
+
deprecationWarnings: string[];
|
|
155
|
+
}> {
|
|
148
156
|
// First: migrate env vars (before anything else reads them)
|
|
149
|
-
migrateEnvVars();
|
|
157
|
+
const migratedEnvVars = migrateEnvVars();
|
|
150
158
|
|
|
151
159
|
// Then: run data migrations
|
|
152
160
|
const migratedAuthProviders = migrateAuthToAuthJson();
|
|
153
161
|
migrateSessionsFromAgentRoot();
|
|
154
162
|
|
|
155
|
-
|
|
163
|
+
// Collect deprecation warnings
|
|
164
|
+
const deprecationWarnings: string[] = [];
|
|
165
|
+
if (migratedEnvVars.length > 0) {
|
|
166
|
+
for (const envVar of migratedEnvVars) {
|
|
167
|
+
const ompVar = `OMP_${envVar.slice(3)}`;
|
|
168
|
+
deprecationWarnings.push(`${envVar} is deprecated. Use ${ompVar} instead.`);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return { migratedAuthProviders, deprecationWarnings };
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Display deprecation warnings to the user in interactive mode.
|
|
177
|
+
*
|
|
178
|
+
* @param warnings - Array of deprecation warning messages
|
|
179
|
+
*/
|
|
180
|
+
export async function showDeprecationWarnings(warnings: string[]): Promise<void> {
|
|
181
|
+
console.log(chalk.yellow("\n⚠ Deprecation Warnings:"));
|
|
182
|
+
for (const warning of warnings) {
|
|
183
|
+
console.log(chalk.yellow(` • ${warning}`));
|
|
184
|
+
}
|
|
185
|
+
console.log();
|
|
156
186
|
}
|