@omnidev-ai/cli 0.7.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/dist/index.js +176 -116
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -32,27 +32,30 @@ import { existsSync as existsSync4 } from "node:fs";
32
32
 
33
33
  // ../adapters/src/claude-code/index.ts
34
34
  import { existsSync, mkdirSync } from "node:fs";
35
- import { writeFile } from "node:fs/promises";
35
+ import { readFile, writeFile } from "node:fs/promises";
36
36
  import { join } from "node:path";
37
+ import {
38
+ transformHooksConfig
39
+ } from "@omnidev-ai/core";
37
40
  var claudeCodeAdapter = {
38
41
  id: "claude-code",
39
42
  displayName: "Claude Code",
40
- async init(ctx) {
41
- const claudeMdPath = join(ctx.projectRoot, "CLAUDE.md");
42
- const filesCreated = [];
43
- if (!existsSync(claudeMdPath)) {
44
- await writeFile(claudeMdPath, generateClaudeTemplate(), "utf-8");
45
- filesCreated.push("CLAUDE.md");
46
- }
43
+ async init(_ctx) {
47
44
  return {
48
- filesCreated,
49
- message: filesCreated.length > 0 ? `Created ${filesCreated.join(", ")}` : "CLAUDE.md already exists"
45
+ filesCreated: [],
46
+ message: "Claude Code adapter initialized"
50
47
  };
51
48
  },
52
49
  async sync(bundle, ctx) {
53
50
  const filesWritten = [];
54
51
  const filesDeleted = [];
55
- const skillsDir = join(ctx.projectRoot, ".claude", "skills");
52
+ const claudeMdPath = join(ctx.projectRoot, "CLAUDE.md");
53
+ const claudeMdContent = await generateClaudeMdContent(ctx.projectRoot);
54
+ await writeFile(claudeMdPath, claudeMdContent, "utf-8");
55
+ filesWritten.push("CLAUDE.md");
56
+ const claudeDir = join(ctx.projectRoot, ".claude");
57
+ mkdirSync(claudeDir, { recursive: true });
58
+ const skillsDir = join(claudeDir, "skills");
56
59
  mkdirSync(skillsDir, { recursive: true });
57
60
  for (const skill of bundle.skills) {
58
61
  const skillDir = join(skillsDir, skill.name);
@@ -67,57 +70,93 @@ ${skill.instructions}`;
67
70
  await writeFile(skillPath, content, "utf-8");
68
71
  filesWritten.push(`.claude/skills/${skill.name}/SKILL.md`);
69
72
  }
73
+ if (bundle.hooks) {
74
+ const settingsPath = join(claudeDir, "settings.json");
75
+ const hooksWritten = await writeHooksToSettings(settingsPath, bundle.hooks);
76
+ if (hooksWritten) {
77
+ filesWritten.push(".claude/settings.json");
78
+ }
79
+ }
70
80
  return {
71
81
  filesWritten,
72
82
  filesDeleted
73
83
  };
74
84
  }
75
85
  };
76
- function generateClaudeTemplate() {
77
- return `# Project Instructions
78
-
79
- <!-- Add your project-specific instructions here -->
86
+ async function generateClaudeMdContent(projectRoot) {
87
+ const omniMdPath = join(projectRoot, "OMNI.md");
88
+ let omniMdContent = "";
89
+ if (existsSync(omniMdPath)) {
90
+ omniMdContent = await readFile(omniMdPath, "utf-8");
91
+ }
92
+ let content = omniMdContent;
93
+ content += `
80
94
 
81
95
  ## OmniDev
82
96
 
83
97
  @import .omni/instructions.md
84
98
  `;
99
+ return content;
100
+ }
101
+ async function writeHooksToSettings(settingsPath, hooks) {
102
+ const claudeHooks = transformHooksConfig(hooks, "toClaude");
103
+ let existingSettings = {};
104
+ if (existsSync(settingsPath)) {
105
+ try {
106
+ const content = await readFile(settingsPath, "utf-8");
107
+ existingSettings = JSON.parse(content);
108
+ } catch {
109
+ existingSettings = {};
110
+ }
111
+ }
112
+ const newSettings = {
113
+ ...existingSettings,
114
+ hooks: claudeHooks
115
+ };
116
+ await writeFile(settingsPath, `${JSON.stringify(newSettings, null, 2)}
117
+ `, "utf-8");
118
+ return true;
85
119
  }
86
120
  // ../adapters/src/codex/index.ts
87
121
  import { existsSync as existsSync2 } from "node:fs";
88
- import { writeFile as writeFile2 } from "node:fs/promises";
122
+ import { readFile as readFile2, writeFile as writeFile2 } from "node:fs/promises";
89
123
  import { join as join2 } from "node:path";
90
124
  var codexAdapter = {
91
125
  id: "codex",
92
126
  displayName: "Codex",
93
- async init(ctx) {
94
- const agentsMdPath = join2(ctx.projectRoot, "AGENTS.md");
95
- const filesCreated = [];
96
- if (!existsSync2(agentsMdPath)) {
97
- await writeFile2(agentsMdPath, generateAgentsTemplate(), "utf-8");
98
- filesCreated.push("AGENTS.md");
99
- }
127
+ async init(_ctx) {
100
128
  return {
101
- filesCreated,
102
- message: filesCreated.length > 0 ? `Created ${filesCreated.join(", ")}` : "AGENTS.md already exists"
129
+ filesCreated: [],
130
+ message: "Codex adapter initialized"
103
131
  };
104
132
  },
105
- async sync(_bundle, _ctx) {
133
+ async sync(_bundle, ctx) {
134
+ const filesWritten = [];
135
+ const filesDeleted = [];
136
+ const agentsMdPath = join2(ctx.projectRoot, "AGENTS.md");
137
+ const agentsMdContent = await generateAgentsMdContent(ctx.projectRoot);
138
+ await writeFile2(agentsMdPath, agentsMdContent, "utf-8");
139
+ filesWritten.push("AGENTS.md");
106
140
  return {
107
- filesWritten: [],
108
- filesDeleted: []
141
+ filesWritten,
142
+ filesDeleted
109
143
  };
110
144
  }
111
145
  };
112
- function generateAgentsTemplate() {
113
- return `# Project Instructions
114
-
115
- <!-- Add your project-specific instructions here -->
146
+ async function generateAgentsMdContent(projectRoot) {
147
+ const omniMdPath = join2(projectRoot, "OMNI.md");
148
+ let omniMdContent = "";
149
+ if (existsSync2(omniMdPath)) {
150
+ omniMdContent = await readFile2(omniMdPath, "utf-8");
151
+ }
152
+ let content = omniMdContent;
153
+ content += `
116
154
 
117
155
  ## OmniDev
118
156
 
119
157
  @import .omni/instructions.md
120
158
  `;
159
+ return content;
121
160
  }
122
161
  // ../adapters/src/cursor/index.ts
123
162
  import { mkdirSync as mkdirSync2 } from "node:fs";
@@ -152,7 +191,7 @@ var cursorAdapter = {
152
191
  };
153
192
  // ../adapters/src/opencode/index.ts
154
193
  import { existsSync as existsSync3, mkdirSync as mkdirSync3 } from "node:fs";
155
- import { writeFile as writeFile4 } from "node:fs/promises";
194
+ import { readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
156
195
  import { join as join4 } from "node:path";
157
196
  var opencodeAdapter = {
158
197
  id: "opencode",
@@ -160,33 +199,40 @@ var opencodeAdapter = {
160
199
  async init(ctx) {
161
200
  const opencodeDir = join4(ctx.projectRoot, ".opencode");
162
201
  mkdirSync3(opencodeDir, { recursive: true });
163
- const instructionsPath = join4(opencodeDir, "instructions.md");
164
- const filesCreated = [];
165
- if (!existsSync3(instructionsPath)) {
166
- await writeFile4(instructionsPath, generateOpencodeTemplate(), "utf-8");
167
- filesCreated.push(".opencode/instructions.md");
168
- }
169
202
  return {
170
- filesCreated,
171
- message: filesCreated.length > 0 ? `Created ${filesCreated.join(", ")}` : ".opencode/instructions.md already exists"
203
+ filesCreated: [".opencode/"],
204
+ message: "OpenCode adapter initialized"
172
205
  };
173
206
  },
174
- async sync(_bundle, _ctx) {
207
+ async sync(_bundle, ctx) {
208
+ const filesWritten = [];
209
+ const filesDeleted = [];
210
+ const opencodeDir = join4(ctx.projectRoot, ".opencode");
211
+ mkdirSync3(opencodeDir, { recursive: true });
212
+ const instructionsPath = join4(opencodeDir, "instructions.md");
213
+ const instructionsContent = await generateOpencodeInstructionsContent(ctx.projectRoot);
214
+ await writeFile4(instructionsPath, instructionsContent, "utf-8");
215
+ filesWritten.push(".opencode/instructions.md");
175
216
  return {
176
- filesWritten: [],
177
- filesDeleted: []
217
+ filesWritten,
218
+ filesDeleted
178
219
  };
179
220
  }
180
221
  };
181
- function generateOpencodeTemplate() {
182
- return `# OpenCode Instructions
183
-
184
- <!-- Add your project-specific instructions here -->
222
+ async function generateOpencodeInstructionsContent(projectRoot) {
223
+ const omniMdPath = join4(projectRoot, "OMNI.md");
224
+ let omniMdContent = "";
225
+ if (existsSync3(omniMdPath)) {
226
+ omniMdContent = await readFile3(omniMdPath, "utf-8");
227
+ }
228
+ let content = omniMdContent;
229
+ content += `
185
230
 
186
231
  ## OmniDev
187
232
 
188
233
  @import ../.omni/instructions.md
189
234
  `;
235
+ return content;
190
236
  }
191
237
  // ../adapters/src/registry.ts
192
238
  import { readEnabledProviders } from "@omnidev-ai/core";
@@ -208,24 +254,12 @@ async function getEnabledAdapters() {
208
254
  import {
209
255
  getActiveProfile,
210
256
  loadBaseConfig,
211
- syncAgentConfiguration,
212
- writeConfig
257
+ patchAddCapabilitySource,
258
+ patchAddMcp,
259
+ patchAddToProfile,
260
+ syncAgentConfiguration
213
261
  } from "@omnidev-ai/core";
214
262
  import { buildCommand, buildRouteMap } from "@stricli/core";
215
- function addToActiveProfile(config, activeProfile, capabilityName) {
216
- if (!config.profiles) {
217
- config.profiles = {};
218
- }
219
- if (!config.profiles[activeProfile]) {
220
- config.profiles[activeProfile] = { capabilities: [] };
221
- }
222
- if (!config.profiles[activeProfile].capabilities) {
223
- config.profiles[activeProfile].capabilities = [];
224
- }
225
- if (!config.profiles[activeProfile].capabilities.includes(capabilityName)) {
226
- config.profiles[activeProfile].capabilities.push(capabilityName);
227
- }
228
- }
229
263
  async function runAddCap(flags, name) {
230
264
  try {
231
265
  if (!existsSync4("omni.toml")) {
@@ -241,25 +275,18 @@ async function runAddCap(flags, name) {
241
275
  }
242
276
  const config = await loadBaseConfig();
243
277
  const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
244
- if (!config.capabilities) {
245
- config.capabilities = {};
246
- }
247
- if (!config.capabilities.sources) {
248
- config.capabilities.sources = {};
249
- }
250
- if (config.capabilities.sources[name]) {
278
+ if (config.capabilities?.sources?.[name]) {
251
279
  console.error(`✗ Capability source "${name}" already exists`);
252
280
  console.log(" Use a different name or remove the existing source first");
253
281
  process.exit(1);
254
282
  }
255
283
  const source = `github:${flags.github}`;
256
284
  if (flags.path) {
257
- config.capabilities.sources[name] = { source, path: flags.path };
285
+ await patchAddCapabilitySource(name, { source, path: flags.path });
258
286
  } else {
259
- config.capabilities.sources[name] = source;
287
+ await patchAddCapabilitySource(name, source);
260
288
  }
261
- addToActiveProfile(config, activeProfile, name);
262
- await writeConfig(config);
289
+ await patchAddToProfile(activeProfile, name);
263
290
  console.log(`✓ Added capability source: ${name}`);
264
291
  console.log(` Source: ${source}`);
265
292
  if (flags.path) {
@@ -284,10 +311,7 @@ async function runAddMcp(flags, name) {
284
311
  }
285
312
  const config = await loadBaseConfig();
286
313
  const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
287
- if (!config.mcps) {
288
- config.mcps = {};
289
- }
290
- if (config.mcps[name]) {
314
+ if (config.mcps?.[name]) {
291
315
  console.error(`✗ MCP "${name}" already exists`);
292
316
  console.log(" Use a different name or remove the existing MCP first");
293
317
  process.exit(1);
@@ -341,9 +365,8 @@ async function runAddMcp(flags, name) {
341
365
  }
342
366
  }
343
367
  }
344
- config.mcps[name] = mcpConfig;
345
- addToActiveProfile(config, activeProfile, name);
346
- await writeConfig(config);
368
+ await patchAddMcp(name, mcpConfig);
369
+ await patchAddToProfile(activeProfile, name);
347
370
  console.log(`✓ Added MCP: ${name}`);
348
371
  console.log(` Transport: ${transport}`);
349
372
  if (mcpConfig.url) {
@@ -663,7 +686,7 @@ var capabilityRoutes = buildRouteMap2({
663
686
  // src/commands/doctor.ts
664
687
  import { existsSync as existsSync5 } from "node:fs";
665
688
  import { execFile } from "node:child_process";
666
- import { readFile } from "node:fs/promises";
689
+ import { readFile as readFile4 } from "node:fs/promises";
667
690
  import { promisify } from "node:util";
668
691
  import { buildCommand as buildCommand3 } from "@stricli/core";
669
692
  var doctorCommand = buildCommand3({
@@ -803,7 +826,7 @@ async function checkRootGitignore() {
803
826
  fix: "Run: omnidev init"
804
827
  };
805
828
  }
806
- const content = await readFile(gitignorePath, "utf-8");
829
+ const content = await readFile4(gitignorePath, "utf-8");
807
830
  const lines = content.split(`
808
831
  `).map((line) => line.trim());
809
832
  const hasOmniDir = lines.includes(".omni/");
@@ -844,20 +867,32 @@ async function checkCapabilitiesDir() {
844
867
  }
845
868
 
846
869
  // src/commands/init.ts
870
+ import { exec } from "node:child_process";
847
871
  import { existsSync as existsSync6, mkdirSync as mkdirSync4 } from "node:fs";
848
- import { readFile as readFile2, writeFile as writeFile5 } from "node:fs/promises";
872
+ import { readFile as readFile5, writeFile as writeFile5 } from "node:fs/promises";
873
+ import { promisify as promisify2 } from "node:util";
849
874
  import {
850
- generateInstructionsTemplate,
875
+ generateOmniMdTemplate,
851
876
  loadConfig,
852
877
  setActiveProfile,
853
878
  syncAgentConfiguration as syncAgentConfiguration3,
854
- writeConfig as writeConfig2,
879
+ writeConfig,
855
880
  writeEnabledProviders
856
881
  } from "@omnidev-ai/core";
857
882
  import { buildCommand as buildCommand4 } from "@stricli/core";
858
883
 
859
884
  // src/prompts/provider.ts
860
- import { checkbox } from "@inquirer/prompts";
885
+ import { checkbox, confirm } from "@inquirer/prompts";
886
+ var PROVIDER_GITIGNORE_FILES = {
887
+ claude: ["CLAUDE.md", ".claude/"],
888
+ "claude-code": ["CLAUDE.md", ".claude/"],
889
+ cursor: [".cursor/"],
890
+ codex: ["AGENTS.md", ".codex/"],
891
+ opencode: [".opencode/"]
892
+ };
893
+ function getProviderGitignoreFiles(providers) {
894
+ return providers.flatMap((p) => PROVIDER_GITIGNORE_FILES[p] ?? []);
895
+ }
861
896
  async function promptForProviders() {
862
897
  const answers = await checkbox({
863
898
  message: "Select your AI provider(s):",
@@ -871,8 +906,16 @@ async function promptForProviders() {
871
906
  });
872
907
  return answers;
873
908
  }
909
+ async function promptForGitignoreProviderFiles(selectedProviders) {
910
+ const filesToIgnore = getProviderGitignoreFiles(selectedProviders);
911
+ return confirm({
912
+ message: `Add provider files to .gitignore? (${filesToIgnore.join(", ")})`,
913
+ default: false
914
+ });
915
+ }
874
916
 
875
917
  // src/commands/init.ts
918
+ var execAsync = promisify2(exec);
876
919
  async function runInit(_flags, providerArg) {
877
920
  console.log("Initializing OmniDev...");
878
921
  mkdirSync4(".omni", { recursive: true });
@@ -880,15 +923,31 @@ async function runInit(_flags, providerArg) {
880
923
  mkdirSync4(".omni/state", { recursive: true });
881
924
  await updateRootGitignore();
882
925
  let providerIds;
926
+ const isInteractive = !providerArg;
883
927
  if (providerArg) {
884
928
  providerIds = parseProviderArg(providerArg);
885
929
  } else {
886
930
  providerIds = await promptForProviders();
887
931
  }
932
+ if (isInteractive) {
933
+ const shouldIgnoreProviderFiles = await promptForGitignoreProviderFiles(providerIds);
934
+ if (shouldIgnoreProviderFiles) {
935
+ const filesToIgnore = getProviderGitignoreFiles(providerIds);
936
+ await addProviderFilesToGitignore(filesToIgnore);
937
+ const trackedFiles = await getTrackedProviderFiles(filesToIgnore);
938
+ if (trackedFiles.length > 0) {
939
+ console.log("");
940
+ console.log("⚠️ Some provider files are already tracked in git.");
941
+ console.log(" Run the following to stop tracking them:");
942
+ console.log("");
943
+ console.log(` git rm --cached ${trackedFiles.join(" ")}`);
944
+ console.log("");
945
+ }
946
+ }
947
+ }
888
948
  await writeEnabledProviders(providerIds);
889
949
  if (!existsSync6("omni.toml")) {
890
- await writeConfig2({
891
- project: "my-project",
950
+ await writeConfig({
892
951
  profiles: {
893
952
  default: {
894
953
  capabilities: []
@@ -903,8 +962,8 @@ async function runInit(_flags, providerArg) {
903
962
  });
904
963
  await setActiveProfile("default");
905
964
  }
906
- if (!existsSync6(".omni/instructions.md")) {
907
- await writeFile5(".omni/instructions.md", generateInstructionsTemplate(), "utf-8");
965
+ if (!existsSync6("OMNI.md")) {
966
+ await writeFile5("OMNI.md", generateOmniMdTemplate(), "utf-8");
908
967
  }
909
968
  const config = await loadConfig();
910
969
  const ctx = {
@@ -913,14 +972,9 @@ async function runInit(_flags, providerArg) {
913
972
  };
914
973
  const allAdapters = getAllAdapters();
915
974
  const selectedAdapters = allAdapters.filter((a) => providerIds.includes(a.id));
916
- const filesCreated = [];
917
- const filesExisting = [];
918
975
  for (const adapter of selectedAdapters) {
919
976
  if (adapter.init) {
920
- const result = await adapter.init(ctx);
921
- if (result.filesCreated) {
922
- filesCreated.push(...result.filesCreated);
923
- }
977
+ await adapter.init(ctx);
924
978
  }
925
979
  }
926
980
  const enabledAdapters = await getEnabledAdapters();
@@ -928,22 +982,10 @@ async function runInit(_flags, providerArg) {
928
982
  console.log("");
929
983
  console.log(`✓ OmniDev initialized for ${selectedAdapters.map((a) => a.displayName).join(" and ")}!`);
930
984
  console.log("");
931
- if (filesCreated.length > 0) {
932
- console.log("\uD83D\uDCDD Don't forget to add your project description to:");
933
- console.log(" • .omni/instructions.md");
934
- }
935
- if (filesExisting.length > 0) {
936
- console.log("\uD83D\uDCDD Add this line to your existing file(s):");
937
- for (const file of filesExisting) {
938
- console.log(` • ${file}: @import .omni/instructions.md`);
939
- }
940
- }
941
- console.log("");
942
- console.log("\uD83D\uDCA1 Recommendation:");
943
- console.log(" Add provider-specific files to .gitignore:");
944
- console.log(" CLAUDE.md, .claude/, AGENTS.md, .cursor/, .mcp.json");
985
+ console.log("\uD83D\uDCDD Add your project description and instructions to OMNI.md");
986
+ console.log(" This will be transformed into provider-specific files (CLAUDE.md, AGENTS.md, etc.)");
945
987
  console.log("");
946
- console.log(" Run 'omnidev capability list' to see available capabilities.");
988
+ console.log("\uD83D\uDCA1 Run 'omnidev capability list' to see available capabilities.");
947
989
  }
948
990
  var initCommand = buildCommand4({
949
991
  parameters: {
@@ -985,11 +1027,17 @@ function parseProviderArg(arg) {
985
1027
  return result;
986
1028
  }
987
1029
  async function updateRootGitignore() {
988
- const gitignorePath = ".gitignore";
989
1030
  const entriesToAdd = [".omni/", "omni.local.toml"];
1031
+ await addToGitignore(entriesToAdd, "OmniDev");
1032
+ }
1033
+ async function addProviderFilesToGitignore(entries) {
1034
+ await addToGitignore(entries, "OmniDev Provider Files");
1035
+ }
1036
+ async function addToGitignore(entriesToAdd, sectionHeader) {
1037
+ const gitignorePath = ".gitignore";
990
1038
  let content = "";
991
1039
  if (existsSync6(gitignorePath)) {
992
- content = await readFile2(gitignorePath, "utf-8");
1040
+ content = await readFile5(gitignorePath, "utf-8");
993
1041
  }
994
1042
  const lines = content.split(`
995
1043
  `);
@@ -1000,12 +1048,24 @@ async function updateRootGitignore() {
1000
1048
  const needsNewline = content.length > 0 && !content.endsWith(`
1001
1049
  `);
1002
1050
  const section = `${needsNewline ? `
1003
- ` : ""}# OmniDev
1051
+ ` : ""}# ${sectionHeader}
1004
1052
  ${missingEntries.join(`
1005
1053
  `)}
1006
1054
  `;
1007
1055
  await writeFile5(gitignorePath, content + section, "utf-8");
1008
1056
  }
1057
+ async function getTrackedProviderFiles(files) {
1058
+ const tracked = [];
1059
+ for (const file of files) {
1060
+ try {
1061
+ const { stdout } = await execAsync(`git ls-files "${file}"`);
1062
+ if (stdout.trim()) {
1063
+ tracked.push(file);
1064
+ }
1065
+ } catch {}
1066
+ }
1067
+ return tracked;
1068
+ }
1009
1069
 
1010
1070
  // src/commands/profile.ts
1011
1071
  import { existsSync as existsSync7 } from "node:fs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omnidev-ai/cli",
3
- "version": "0.7.0",
3
+ "version": "0.9.0",
4
4
  "type": "module",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -28,11 +28,11 @@
28
28
  },
29
29
  "dependencies": {
30
30
  "@inquirer/prompts": "^8.1.0",
31
- "@omnidev-ai/core": "0.7.0",
31
+ "@omnidev-ai/core": "0.9.0",
32
32
  "@stricli/core": "^1.2.5"
33
33
  },
34
34
  "devDependencies": {
35
- "@omnidev-ai/adapters": "0.0.1",
35
+ "@omnidev-ai/adapters": "0.0.11",
36
36
  "bunup": "^0.16.20"
37
37
  }
38
38
  }