@omnidev-ai/cli 0.3.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +1094 -0
- package/package.json +35 -36
- package/src/commands/AGENTS.md +0 -41
- package/src/commands/capability.test.ts +0 -453
- package/src/commands/capability.ts +0 -166
- package/src/commands/doctor.test.ts +0 -196
- package/src/commands/doctor.ts +0 -181
- package/src/commands/init.test.ts +0 -289
- package/src/commands/init.ts +0 -188
- package/src/commands/profile.test.ts +0 -339
- package/src/commands/profile.ts +0 -153
- package/src/commands/provider.test.ts +0 -128
- package/src/commands/provider.ts +0 -139
- package/src/commands/sync.ts +0 -49
- package/src/index.ts +0 -55
- package/src/lib/debug.ts +0 -4
- package/src/lib/dynamic-app.ts +0 -180
- package/src/prompts/provider.ts +0 -17
|
@@ -1,289 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
3
|
-
import { setupTestDir } from "@omnidev-ai/core/test-utils";
|
|
4
|
-
import { runInit } from "./init";
|
|
5
|
-
|
|
6
|
-
describe("init command", () => {
|
|
7
|
-
setupTestDir("init-test-", { chdir: true });
|
|
8
|
-
|
|
9
|
-
test("creates .omni/ directory", async () => {
|
|
10
|
-
await runInit({}, "claude-code");
|
|
11
|
-
|
|
12
|
-
expect(existsSync(".omni")).toBe(true);
|
|
13
|
-
expect(existsSync(".omni/capabilities")).toBe(true);
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
test("creates omni.toml with default config", async () => {
|
|
17
|
-
await runInit({}, "claude-code");
|
|
18
|
-
|
|
19
|
-
expect(existsSync("omni.toml")).toBe(true);
|
|
20
|
-
|
|
21
|
-
const content = readFileSync("omni.toml", "utf-8");
|
|
22
|
-
expect(content).toContain('project = "my-project"');
|
|
23
|
-
// active_profile is stored in state file, not config.toml
|
|
24
|
-
expect(content).not.toContain("active_profile");
|
|
25
|
-
// profiles should be in config.toml
|
|
26
|
-
expect(content).toContain("[profiles.default]");
|
|
27
|
-
expect(content).toContain("[profiles.planning]");
|
|
28
|
-
expect(content).toContain("[profiles.coding]");
|
|
29
|
-
// providers are stored in local state, not config.toml
|
|
30
|
-
expect(content).not.toContain("[providers]");
|
|
31
|
-
// should have documentation comments
|
|
32
|
-
expect(content).toContain("# OmniDev Configuration");
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
test("creates active profile in state file", async () => {
|
|
36
|
-
await runInit({}, "claude-code");
|
|
37
|
-
|
|
38
|
-
expect(existsSync(".omni/state/active-profile")).toBe(true);
|
|
39
|
-
|
|
40
|
-
const content = readFileSync(".omni/state/active-profile", "utf-8");
|
|
41
|
-
expect(content).toBe("default");
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("stores enabled providers in local state file", async () => {
|
|
45
|
-
await runInit({}, "claude-code");
|
|
46
|
-
|
|
47
|
-
expect(existsSync(".omni/state/providers.json")).toBe(true);
|
|
48
|
-
|
|
49
|
-
const content = readFileSync(".omni/state/providers.json", "utf-8");
|
|
50
|
-
const state = JSON.parse(content);
|
|
51
|
-
expect(state.enabled).toContain("claude-code");
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
test("does not create separate capabilities.toml file", async () => {
|
|
55
|
-
await runInit({}, "claude-code");
|
|
56
|
-
|
|
57
|
-
// All config is unified in config.toml
|
|
58
|
-
expect(existsSync(".omni/capabilities.toml")).toBe(false);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
test("does not create separate profiles.toml file", async () => {
|
|
62
|
-
await runInit({}, "claude-code");
|
|
63
|
-
|
|
64
|
-
// Profiles are in config.toml
|
|
65
|
-
expect(existsSync(".omni/profiles.toml")).toBe(false);
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("creates .omni/ directory with subdirectories", async () => {
|
|
69
|
-
await runInit({}, "claude-code");
|
|
70
|
-
|
|
71
|
-
expect(existsSync(".omni")).toBe(true);
|
|
72
|
-
expect(existsSync(".omni/state")).toBe(true);
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
test("does not create separate provider.toml file", async () => {
|
|
76
|
-
await runInit({}, "claude-code");
|
|
77
|
-
|
|
78
|
-
// Provider state is in .omni/state/providers.json
|
|
79
|
-
expect(existsSync(".omni/provider.toml")).toBe(false);
|
|
80
|
-
|
|
81
|
-
// Verify provider is in state file instead
|
|
82
|
-
const content = readFileSync(".omni/state/providers.json", "utf-8");
|
|
83
|
-
const state = JSON.parse(content);
|
|
84
|
-
expect(state.enabled).toContain("claude-code");
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
test("creates AGENTS.md for Codex provider", async () => {
|
|
88
|
-
await runInit({}, "codex");
|
|
89
|
-
|
|
90
|
-
expect(existsSync("AGENTS.md")).toBe(true);
|
|
91
|
-
|
|
92
|
-
const content = readFileSync("AGENTS.md", "utf-8");
|
|
93
|
-
expect(content).toContain("# Project Instructions");
|
|
94
|
-
expect(content).toContain("@import .omni/instructions.md");
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
test("creates .omni/instructions.md", async () => {
|
|
98
|
-
await runInit({}, "codex");
|
|
99
|
-
|
|
100
|
-
expect(existsSync(".omni/instructions.md")).toBe(true);
|
|
101
|
-
|
|
102
|
-
const content = readFileSync(".omni/instructions.md", "utf-8");
|
|
103
|
-
expect(content).toContain("# OmniDev Instructions");
|
|
104
|
-
expect(content).toContain("## Project Description");
|
|
105
|
-
expect(content).toContain("<!-- TODO: Add 2-3 sentences describing your project -->");
|
|
106
|
-
expect(content).toContain("## Capabilities");
|
|
107
|
-
expect(content).toContain("No capabilities enabled yet");
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
test("does not create AGENTS.md for Claude Code provider", async () => {
|
|
111
|
-
await runInit({}, "claude-code");
|
|
112
|
-
|
|
113
|
-
expect(existsSync("AGENTS.md")).toBe(false);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
test("creates CLAUDE.md for Claude Code provider", async () => {
|
|
117
|
-
await runInit({}, "claude-code");
|
|
118
|
-
|
|
119
|
-
expect(existsSync("CLAUDE.md")).toBe(true);
|
|
120
|
-
|
|
121
|
-
const content = readFileSync("CLAUDE.md", "utf-8");
|
|
122
|
-
expect(content).toContain("# Project Instructions");
|
|
123
|
-
expect(content).toContain("@import .omni/instructions.md");
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
test("does not create CLAUDE.md for Codex provider", async () => {
|
|
127
|
-
await runInit({}, "codex");
|
|
128
|
-
|
|
129
|
-
expect(existsSync("CLAUDE.md")).toBe(false);
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
test("creates both CLAUDE.md and .cursor/rules/ for 'both' providers", async () => {
|
|
133
|
-
// "both" maps to claude-code and cursor
|
|
134
|
-
await runInit({}, "both");
|
|
135
|
-
|
|
136
|
-
expect(existsSync("CLAUDE.md")).toBe(true);
|
|
137
|
-
expect(existsSync(".cursor/rules")).toBe(true);
|
|
138
|
-
|
|
139
|
-
const claudeContent = readFileSync("CLAUDE.md", "utf-8");
|
|
140
|
-
expect(claudeContent).toContain("# Project Instructions");
|
|
141
|
-
expect(claudeContent).toContain("@import .omni/instructions.md");
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
test("does not modify existing CLAUDE.md", async () => {
|
|
145
|
-
const existingContent = "# My Existing Config\n\nExisting content here.\n";
|
|
146
|
-
await Bun.write("CLAUDE.md", existingContent);
|
|
147
|
-
|
|
148
|
-
await runInit({}, "claude-code");
|
|
149
|
-
|
|
150
|
-
const content = readFileSync("CLAUDE.md", "utf-8");
|
|
151
|
-
expect(content).toBe(existingContent);
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
test("does not modify existing AGENTS.md", async () => {
|
|
155
|
-
const existingContent = "# My Existing Agents\n\nExisting content here.\n";
|
|
156
|
-
await Bun.write("AGENTS.md", existingContent);
|
|
157
|
-
|
|
158
|
-
await runInit({}, "codex");
|
|
159
|
-
|
|
160
|
-
const content = readFileSync("AGENTS.md", "utf-8");
|
|
161
|
-
expect(content).toBe(existingContent);
|
|
162
|
-
});
|
|
163
|
-
|
|
164
|
-
test("does not create .omni/.gitignore", async () => {
|
|
165
|
-
await runInit({}, "claude-code");
|
|
166
|
-
|
|
167
|
-
// The whole .omni/ directory is gitignored, no need for internal .gitignore
|
|
168
|
-
expect(existsSync(".omni/.gitignore")).toBe(false);
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
test("updates root .gitignore with omnidev entries", async () => {
|
|
172
|
-
// Create a root .gitignore with custom content
|
|
173
|
-
await Bun.write(".gitignore", "node_modules/\n*.log\n");
|
|
174
|
-
|
|
175
|
-
await runInit({}, "claude-code");
|
|
176
|
-
|
|
177
|
-
const content = readFileSync(".gitignore", "utf-8");
|
|
178
|
-
expect(content).toContain("node_modules/");
|
|
179
|
-
expect(content).toContain("*.log");
|
|
180
|
-
expect(content).toContain("# OmniDev");
|
|
181
|
-
expect(content).toContain(".omni/");
|
|
182
|
-
expect(content).toContain("omni.local.toml");
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
test("creates root .gitignore if it doesn't exist", async () => {
|
|
186
|
-
await runInit({}, "claude-code");
|
|
187
|
-
|
|
188
|
-
expect(existsSync(".gitignore")).toBe(true);
|
|
189
|
-
|
|
190
|
-
const content = readFileSync(".gitignore", "utf-8");
|
|
191
|
-
expect(content).toContain("# OmniDev");
|
|
192
|
-
expect(content).toContain(".omni/");
|
|
193
|
-
expect(content).toContain("omni.local.toml");
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
test("does not duplicate gitignore entries on multiple runs", async () => {
|
|
197
|
-
await runInit({}, "claude-code");
|
|
198
|
-
await runInit({}, "claude-code");
|
|
199
|
-
|
|
200
|
-
const content = readFileSync(".gitignore", "utf-8");
|
|
201
|
-
// Should only have one occurrence of each entry
|
|
202
|
-
expect(content.match(/\.omni\//g)?.length).toBe(1);
|
|
203
|
-
expect(content.match(/omni\.local\.toml/g)?.length).toBe(1);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
test("is idempotent - safe to run multiple times", async () => {
|
|
207
|
-
await runInit({}, "claude-code");
|
|
208
|
-
await runInit({}, "claude-code");
|
|
209
|
-
await runInit({}, "claude-code");
|
|
210
|
-
|
|
211
|
-
expect(existsSync("omni.toml")).toBe(true);
|
|
212
|
-
expect(existsSync(".omni")).toBe(true);
|
|
213
|
-
expect(existsSync("CLAUDE.md")).toBe(true);
|
|
214
|
-
|
|
215
|
-
// Should not create AGENTS.md for Claude Code
|
|
216
|
-
expect(existsSync("AGENTS.md")).toBe(false);
|
|
217
|
-
});
|
|
218
|
-
|
|
219
|
-
test("does not overwrite existing config.toml", async () => {
|
|
220
|
-
const customConfig = 'project = "custom"\n';
|
|
221
|
-
mkdirSync(".omni", { recursive: true });
|
|
222
|
-
await Bun.write("omni.toml", customConfig);
|
|
223
|
-
|
|
224
|
-
await runInit({}, "claude-code");
|
|
225
|
-
|
|
226
|
-
const content = readFileSync("omni.toml", "utf-8");
|
|
227
|
-
expect(content).toBe(customConfig);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("does not overwrite existing AGENTS.md", async () => {
|
|
231
|
-
const customAgents = "# Custom agents\n";
|
|
232
|
-
await Bun.write("AGENTS.md", customAgents);
|
|
233
|
-
|
|
234
|
-
await runInit({}, "codex");
|
|
235
|
-
|
|
236
|
-
const content = readFileSync("AGENTS.md", "utf-8");
|
|
237
|
-
expect(content).toBe(customAgents);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
test("creates all directories even if some already exist", async () => {
|
|
241
|
-
mkdirSync(".omni", { recursive: true });
|
|
242
|
-
|
|
243
|
-
await runInit({}, "claude-code");
|
|
244
|
-
|
|
245
|
-
expect(existsSync(".omni/capabilities")).toBe(true);
|
|
246
|
-
expect(existsSync(".omni")).toBe(true);
|
|
247
|
-
expect(existsSync(".omni/state")).toBe(true);
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
test("accepts provider via positional parameter", async () => {
|
|
251
|
-
await runInit({}, "codex");
|
|
252
|
-
|
|
253
|
-
expect(existsSync(".omni/provider.toml")).toBe(false);
|
|
254
|
-
|
|
255
|
-
const content = readFileSync(".omni/state/providers.json", "utf-8");
|
|
256
|
-
const state = JSON.parse(content);
|
|
257
|
-
expect(state.enabled).toContain("codex");
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
test("accepts 'both' as provider parameter", async () => {
|
|
261
|
-
await runInit({}, "both");
|
|
262
|
-
|
|
263
|
-
expect(existsSync(".omni/provider.toml")).toBe(false);
|
|
264
|
-
|
|
265
|
-
const content = readFileSync(".omni/state/providers.json", "utf-8");
|
|
266
|
-
const state = JSON.parse(content);
|
|
267
|
-
// "both" maps to claude-code and cursor
|
|
268
|
-
expect(state.enabled).toContain("claude-code");
|
|
269
|
-
expect(state.enabled).toContain("cursor");
|
|
270
|
-
});
|
|
271
|
-
|
|
272
|
-
test("supports legacy 'claude' name mapping to claude-code", async () => {
|
|
273
|
-
await runInit({}, "claude");
|
|
274
|
-
|
|
275
|
-
const content = readFileSync(".omni/state/providers.json", "utf-8");
|
|
276
|
-
const state = JSON.parse(content);
|
|
277
|
-
expect(state.enabled).toContain("claude-code");
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
test("supports comma-separated providers", async () => {
|
|
281
|
-
await runInit({}, "claude-code,codex,cursor");
|
|
282
|
-
|
|
283
|
-
const content = readFileSync(".omni/state/providers.json", "utf-8");
|
|
284
|
-
const state = JSON.parse(content);
|
|
285
|
-
expect(state.enabled).toContain("claude-code");
|
|
286
|
-
expect(state.enabled).toContain("codex");
|
|
287
|
-
expect(state.enabled).toContain("cursor");
|
|
288
|
-
});
|
|
289
|
-
});
|
package/src/commands/init.ts
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
import { existsSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { getAllAdapters, getEnabledAdapters } from "@omnidev-ai/adapters";
|
|
3
|
-
import type { ProviderId, ProviderContext } from "@omnidev-ai/core";
|
|
4
|
-
import {
|
|
5
|
-
generateInstructionsTemplate,
|
|
6
|
-
loadConfig,
|
|
7
|
-
setActiveProfile,
|
|
8
|
-
syncAgentConfiguration,
|
|
9
|
-
writeConfig,
|
|
10
|
-
writeEnabledProviders,
|
|
11
|
-
} from "@omnidev-ai/core";
|
|
12
|
-
import { buildCommand } from "@stricli/core";
|
|
13
|
-
import { promptForProviders } from "../prompts/provider.js";
|
|
14
|
-
|
|
15
|
-
export async function runInit(_flags: Record<string, never>, providerArg?: string) {
|
|
16
|
-
console.log("Initializing OmniDev...");
|
|
17
|
-
|
|
18
|
-
// Create .omni/ directory structure
|
|
19
|
-
mkdirSync(".omni", { recursive: true });
|
|
20
|
-
mkdirSync(".omni/capabilities", { recursive: true });
|
|
21
|
-
mkdirSync(".omni/state", { recursive: true });
|
|
22
|
-
|
|
23
|
-
// Update root .gitignore to ignore .omni/ and omni.local.toml
|
|
24
|
-
await updateRootGitignore();
|
|
25
|
-
|
|
26
|
-
// Get provider selection
|
|
27
|
-
let providerIds: ProviderId[];
|
|
28
|
-
if (providerArg) {
|
|
29
|
-
providerIds = parseProviderArg(providerArg);
|
|
30
|
-
} else {
|
|
31
|
-
providerIds = await promptForProviders();
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
// Save enabled providers to local state (not omni.toml)
|
|
35
|
-
await writeEnabledProviders(providerIds);
|
|
36
|
-
|
|
37
|
-
// Create omni.toml at project root (without provider config - that's in state)
|
|
38
|
-
if (!existsSync("omni.toml")) {
|
|
39
|
-
await writeConfig({
|
|
40
|
-
project: "my-project",
|
|
41
|
-
profiles: {
|
|
42
|
-
default: {
|
|
43
|
-
capabilities: [],
|
|
44
|
-
},
|
|
45
|
-
planning: {
|
|
46
|
-
capabilities: [],
|
|
47
|
-
},
|
|
48
|
-
coding: {
|
|
49
|
-
capabilities: [],
|
|
50
|
-
},
|
|
51
|
-
},
|
|
52
|
-
});
|
|
53
|
-
// Set active profile in state file
|
|
54
|
-
await setActiveProfile("default");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
// Create .omni/instructions.md
|
|
58
|
-
if (!existsSync(".omni/instructions.md")) {
|
|
59
|
-
await Bun.write(".omni/instructions.md", generateInstructionsTemplate());
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Load config and create provider context
|
|
63
|
-
const config = await loadConfig();
|
|
64
|
-
const ctx: ProviderContext = {
|
|
65
|
-
projectRoot: process.cwd(),
|
|
66
|
-
config,
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
// Initialize enabled adapters (create their root files)
|
|
70
|
-
const allAdapters = getAllAdapters();
|
|
71
|
-
const selectedAdapters = allAdapters.filter((a) => providerIds.includes(a.id));
|
|
72
|
-
const filesCreated: string[] = [];
|
|
73
|
-
const filesExisting: string[] = [];
|
|
74
|
-
|
|
75
|
-
for (const adapter of selectedAdapters) {
|
|
76
|
-
if (adapter.init) {
|
|
77
|
-
const result = await adapter.init(ctx);
|
|
78
|
-
if (result.filesCreated) {
|
|
79
|
-
filesCreated.push(...result.filesCreated);
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Run initial sync with enabled adapters (silent - no need to show details)
|
|
85
|
-
const enabledAdapters = await getEnabledAdapters();
|
|
86
|
-
await syncAgentConfiguration({ silent: true, adapters: enabledAdapters });
|
|
87
|
-
|
|
88
|
-
// Output success message
|
|
89
|
-
console.log("");
|
|
90
|
-
console.log(
|
|
91
|
-
`✓ OmniDev initialized for ${selectedAdapters.map((a) => a.displayName).join(" and ")}!`,
|
|
92
|
-
);
|
|
93
|
-
console.log("");
|
|
94
|
-
|
|
95
|
-
// Show appropriate message based on file status
|
|
96
|
-
if (filesCreated.length > 0) {
|
|
97
|
-
console.log("📝 Don't forget to add your project description to:");
|
|
98
|
-
console.log(" • .omni/instructions.md");
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
if (filesExisting.length > 0) {
|
|
102
|
-
console.log("📝 Add this line to your existing file(s):");
|
|
103
|
-
for (const file of filesExisting) {
|
|
104
|
-
console.log(` • ${file}: @import .omni/instructions.md`);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
console.log("");
|
|
109
|
-
console.log("💡 Recommendation:");
|
|
110
|
-
console.log(" Add provider-specific files to .gitignore:");
|
|
111
|
-
console.log(" CLAUDE.md, .claude/, AGENTS.md, .cursor/, .mcp.json");
|
|
112
|
-
console.log("");
|
|
113
|
-
console.log(" Run 'omnidev capability list' to see available capabilities.");
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
export const initCommand = buildCommand({
|
|
117
|
-
parameters: {
|
|
118
|
-
flags: {},
|
|
119
|
-
positional: {
|
|
120
|
-
kind: "tuple" as const,
|
|
121
|
-
parameters: [
|
|
122
|
-
{
|
|
123
|
-
brief: "AI provider(s): claude-code, cursor, codex, opencode, or comma-separated",
|
|
124
|
-
parse: String,
|
|
125
|
-
optional: true,
|
|
126
|
-
},
|
|
127
|
-
],
|
|
128
|
-
},
|
|
129
|
-
},
|
|
130
|
-
docs: {
|
|
131
|
-
brief: "Initialize OmniDev in the current project",
|
|
132
|
-
},
|
|
133
|
-
func: runInit,
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
function parseProviderArg(arg: string): ProviderId[] {
|
|
137
|
-
const allAdapters = getAllAdapters();
|
|
138
|
-
const validIds = new Set(allAdapters.map((a) => a.id));
|
|
139
|
-
|
|
140
|
-
// Handle legacy "both" argument
|
|
141
|
-
if (arg.toLowerCase() === "both") {
|
|
142
|
-
return ["claude-code", "cursor"];
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Handle comma-separated list
|
|
146
|
-
const parts = arg.split(",").map((p) => p.trim().toLowerCase());
|
|
147
|
-
const result: ProviderId[] = [];
|
|
148
|
-
|
|
149
|
-
for (const part of parts) {
|
|
150
|
-
// Map legacy names
|
|
151
|
-
let id = part;
|
|
152
|
-
if (id === "claude") {
|
|
153
|
-
id = "claude-code";
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
if (!validIds.has(id)) {
|
|
157
|
-
throw new Error(`Invalid provider: ${part}. Valid providers: ${[...validIds].join(", ")}`);
|
|
158
|
-
}
|
|
159
|
-
result.push(id as ProviderId);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
return result;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async function updateRootGitignore(): Promise<void> {
|
|
166
|
-
const gitignorePath = ".gitignore";
|
|
167
|
-
const entriesToAdd = [".omni/", "omni.local.toml"];
|
|
168
|
-
|
|
169
|
-
let content = "";
|
|
170
|
-
if (existsSync(gitignorePath)) {
|
|
171
|
-
content = await Bun.file(gitignorePath).text();
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const lines = content.split("\n");
|
|
175
|
-
const missingEntries = entriesToAdd.filter(
|
|
176
|
-
(entry) => !lines.some((line) => line.trim() === entry),
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
if (missingEntries.length === 0) {
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Add a newline before our section if the file doesn't end with one
|
|
184
|
-
const needsNewline = content.length > 0 && !content.endsWith("\n");
|
|
185
|
-
const section = `${needsNewline ? "\n" : ""}# OmniDev\n${missingEntries.join("\n")}\n`;
|
|
186
|
-
|
|
187
|
-
await Bun.write(gitignorePath, content + section);
|
|
188
|
-
}
|