@omnidev-ai/cli 0.8.0 → 0.10.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 +419 -177
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -24,118 +24,213 @@ import { run } from "@stricli/core";
|
|
|
24
24
|
// src/lib/dynamic-app.ts
|
|
25
25
|
import { existsSync as existsSync8 } from "node:fs";
|
|
26
26
|
import { createRequire as createRequire2 } from "node:module";
|
|
27
|
-
import { join as
|
|
27
|
+
import { join as join9 } from "node:path";
|
|
28
28
|
import { buildApplication, buildRouteMap as buildRouteMap5 } from "@stricli/core";
|
|
29
29
|
|
|
30
30
|
// src/commands/add.ts
|
|
31
|
-
import { existsSync as
|
|
31
|
+
import { existsSync as existsSync3 } from "node:fs";
|
|
32
|
+
import { basename, resolve } from "node:path";
|
|
32
33
|
|
|
33
|
-
// ../adapters/src/
|
|
34
|
-
import {
|
|
35
|
-
import { readFile, writeFile } from "node:fs/promises";
|
|
34
|
+
// ../adapters/src/writers/cursor-rules.ts
|
|
35
|
+
import { mkdir, writeFile } from "node:fs/promises";
|
|
36
36
|
import { join } from "node:path";
|
|
37
|
-
var
|
|
38
|
-
id: "
|
|
39
|
-
|
|
40
|
-
|
|
37
|
+
var CursorRulesWriter = {
|
|
38
|
+
id: "cursor-rules",
|
|
39
|
+
async write(bundle, ctx) {
|
|
40
|
+
const rulesDir = join(ctx.projectRoot, ctx.outputPath);
|
|
41
|
+
await mkdir(rulesDir, { recursive: true });
|
|
42
|
+
const filesWritten = [];
|
|
43
|
+
for (const rule of bundle.rules) {
|
|
44
|
+
const rulePath = join(rulesDir, `omnidev-${rule.name}.mdc`);
|
|
45
|
+
await writeFile(rulePath, rule.content, "utf-8");
|
|
46
|
+
filesWritten.push(join(ctx.outputPath, `omnidev-${rule.name}.mdc`));
|
|
47
|
+
}
|
|
41
48
|
return {
|
|
42
|
-
|
|
43
|
-
message: "Claude Code adapter initialized"
|
|
49
|
+
filesWritten
|
|
44
50
|
};
|
|
45
|
-
}
|
|
46
|
-
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// ../adapters/src/writers/hooks.ts
|
|
54
|
+
import { existsSync } from "node:fs";
|
|
55
|
+
import { mkdir as mkdir2, readFile, writeFile as writeFile2 } from "node:fs/promises";
|
|
56
|
+
import { dirname, join as join2 } from "node:path";
|
|
57
|
+
import { transformHooksConfig } from "@omnidev-ai/core";
|
|
58
|
+
var HooksWriter = {
|
|
59
|
+
id: "hooks",
|
|
60
|
+
async write(bundle, ctx) {
|
|
61
|
+
if (!bundle.hooks) {
|
|
62
|
+
return { filesWritten: [] };
|
|
63
|
+
}
|
|
64
|
+
const settingsPath = join2(ctx.projectRoot, ctx.outputPath);
|
|
65
|
+
const parentDir = dirname(settingsPath);
|
|
66
|
+
await mkdir2(parentDir, { recursive: true });
|
|
67
|
+
const claudeHooks = transformHooksConfig(bundle.hooks, "toClaude");
|
|
68
|
+
let existingSettings = {};
|
|
69
|
+
if (existsSync(settingsPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const content = await readFile(settingsPath, "utf-8");
|
|
72
|
+
existingSettings = JSON.parse(content);
|
|
73
|
+
} catch {
|
|
74
|
+
existingSettings = {};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
const newSettings = {
|
|
78
|
+
...existingSettings,
|
|
79
|
+
hooks: claudeHooks
|
|
80
|
+
};
|
|
81
|
+
await writeFile2(settingsPath, `${JSON.stringify(newSettings, null, 2)}
|
|
82
|
+
`, "utf-8");
|
|
83
|
+
return {
|
|
84
|
+
filesWritten: [ctx.outputPath]
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
// ../adapters/src/writers/instructions-md.ts
|
|
89
|
+
import { existsSync as existsSync2 } from "node:fs";
|
|
90
|
+
import { mkdir as mkdir3, readFile as readFile2, writeFile as writeFile3 } from "node:fs/promises";
|
|
91
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
92
|
+
var InstructionsMdWriter = {
|
|
93
|
+
id: "instructions-md",
|
|
94
|
+
async write(bundle, ctx) {
|
|
95
|
+
const outputFullPath = join3(ctx.projectRoot, ctx.outputPath);
|
|
96
|
+
const parentDir = dirname2(outputFullPath);
|
|
97
|
+
if (parentDir !== ctx.projectRoot) {
|
|
98
|
+
await mkdir3(parentDir, { recursive: true });
|
|
99
|
+
}
|
|
100
|
+
const omniMdPath = join3(ctx.projectRoot, "OMNI.md");
|
|
101
|
+
let omniMdContent = "";
|
|
102
|
+
if (existsSync2(omniMdPath)) {
|
|
103
|
+
omniMdContent = await readFile2(omniMdPath, "utf-8");
|
|
104
|
+
}
|
|
105
|
+
let content = omniMdContent;
|
|
106
|
+
content += `
|
|
107
|
+
|
|
108
|
+
## OmniDev
|
|
109
|
+
|
|
110
|
+
${bundle.instructionsContent}
|
|
111
|
+
`;
|
|
112
|
+
await writeFile3(outputFullPath, content, "utf-8");
|
|
113
|
+
return {
|
|
114
|
+
filesWritten: [ctx.outputPath]
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
// ../adapters/src/writers/skills.ts
|
|
119
|
+
import { mkdir as mkdir4, writeFile as writeFile4 } from "node:fs/promises";
|
|
120
|
+
import { join as join4 } from "node:path";
|
|
121
|
+
var SkillsWriter = {
|
|
122
|
+
id: "skills",
|
|
123
|
+
async write(bundle, ctx) {
|
|
124
|
+
const skillsDir = join4(ctx.projectRoot, ctx.outputPath);
|
|
125
|
+
await mkdir4(skillsDir, { recursive: true });
|
|
47
126
|
const filesWritten = [];
|
|
48
|
-
const filesDeleted = [];
|
|
49
|
-
const claudeMdPath = join(ctx.projectRoot, "CLAUDE.md");
|
|
50
|
-
const claudeMdContent = await generateClaudeMdContent(ctx.projectRoot);
|
|
51
|
-
await writeFile(claudeMdPath, claudeMdContent, "utf-8");
|
|
52
|
-
filesWritten.push("CLAUDE.md");
|
|
53
|
-
const skillsDir = join(ctx.projectRoot, ".claude", "skills");
|
|
54
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
55
127
|
for (const skill of bundle.skills) {
|
|
56
|
-
const skillDir =
|
|
57
|
-
|
|
58
|
-
const skillPath =
|
|
128
|
+
const skillDir = join4(skillsDir, skill.name);
|
|
129
|
+
await mkdir4(skillDir, { recursive: true });
|
|
130
|
+
const skillPath = join4(skillDir, "SKILL.md");
|
|
59
131
|
const content = `---
|
|
60
132
|
name: ${skill.name}
|
|
61
133
|
description: "${skill.description}"
|
|
62
134
|
---
|
|
63
135
|
|
|
64
136
|
${skill.instructions}`;
|
|
65
|
-
await
|
|
66
|
-
filesWritten.push(
|
|
137
|
+
await writeFile4(skillPath, content, "utf-8");
|
|
138
|
+
filesWritten.push(join4(ctx.outputPath, skill.name, "SKILL.md"));
|
|
67
139
|
}
|
|
68
140
|
return {
|
|
69
|
-
filesWritten
|
|
70
|
-
filesDeleted
|
|
141
|
+
filesWritten
|
|
71
142
|
};
|
|
72
143
|
}
|
|
73
144
|
};
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
145
|
+
// ../adapters/src/writers/executor.ts
|
|
146
|
+
async function executeWriters(writerConfigs, bundle, projectRoot) {
|
|
147
|
+
const seen = new Set;
|
|
148
|
+
const uniqueConfigs = [];
|
|
149
|
+
let deduplicatedCount = 0;
|
|
150
|
+
for (const config of writerConfigs) {
|
|
151
|
+
const key = `${config.writer.id}:${config.outputPath}`;
|
|
152
|
+
if (seen.has(key)) {
|
|
153
|
+
deduplicatedCount++;
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
seen.add(key);
|
|
157
|
+
uniqueConfigs.push(config);
|
|
79
158
|
}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
159
|
+
const allFilesWritten = [];
|
|
160
|
+
for (const config of uniqueConfigs) {
|
|
161
|
+
const result = await config.writer.write(bundle, {
|
|
162
|
+
outputPath: config.outputPath,
|
|
163
|
+
projectRoot
|
|
164
|
+
});
|
|
165
|
+
allFilesWritten.push(...result.filesWritten);
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
filesWritten: allFilesWritten,
|
|
169
|
+
deduplicatedCount
|
|
170
|
+
};
|
|
88
171
|
}
|
|
172
|
+
// ../adapters/src/claude-code/index.ts
|
|
173
|
+
var claudeCodeAdapter = {
|
|
174
|
+
id: "claude-code",
|
|
175
|
+
displayName: "Claude Code",
|
|
176
|
+
writers: [
|
|
177
|
+
{ writer: InstructionsMdWriter, outputPath: "CLAUDE.md" },
|
|
178
|
+
{ writer: SkillsWriter, outputPath: ".claude/skills/" },
|
|
179
|
+
{ writer: HooksWriter, outputPath: ".claude/settings.json" }
|
|
180
|
+
],
|
|
181
|
+
async init(_ctx) {
|
|
182
|
+
return {
|
|
183
|
+
filesCreated: [],
|
|
184
|
+
message: "Claude Code adapter initialized"
|
|
185
|
+
};
|
|
186
|
+
},
|
|
187
|
+
async sync(bundle, ctx) {
|
|
188
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
189
|
+
return {
|
|
190
|
+
filesWritten: result.filesWritten,
|
|
191
|
+
filesDeleted: []
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
};
|
|
89
195
|
// ../adapters/src/codex/index.ts
|
|
90
|
-
import {
|
|
91
|
-
import {
|
|
92
|
-
import { join as join2 } from "node:path";
|
|
196
|
+
import { mkdirSync } from "node:fs";
|
|
197
|
+
import { join as join5 } from "node:path";
|
|
93
198
|
var codexAdapter = {
|
|
94
199
|
id: "codex",
|
|
95
200
|
displayName: "Codex",
|
|
96
|
-
|
|
201
|
+
writers: [
|
|
202
|
+
{ writer: InstructionsMdWriter, outputPath: "AGENTS.md" },
|
|
203
|
+
{ writer: SkillsWriter, outputPath: ".codex/skills/" }
|
|
204
|
+
],
|
|
205
|
+
async init(ctx) {
|
|
206
|
+
const codexDir = join5(ctx.projectRoot, ".codex");
|
|
207
|
+
mkdirSync(codexDir, { recursive: true });
|
|
97
208
|
return {
|
|
98
|
-
filesCreated: [],
|
|
209
|
+
filesCreated: [".codex/"],
|
|
99
210
|
message: "Codex adapter initialized"
|
|
100
211
|
};
|
|
101
212
|
},
|
|
102
|
-
async sync(
|
|
103
|
-
const
|
|
104
|
-
const filesDeleted = [];
|
|
105
|
-
const agentsMdPath = join2(ctx.projectRoot, "AGENTS.md");
|
|
106
|
-
const agentsMdContent = await generateAgentsMdContent(ctx.projectRoot);
|
|
107
|
-
await writeFile2(agentsMdPath, agentsMdContent, "utf-8");
|
|
108
|
-
filesWritten.push("AGENTS.md");
|
|
213
|
+
async sync(bundle, ctx) {
|
|
214
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
109
215
|
return {
|
|
110
|
-
filesWritten,
|
|
111
|
-
filesDeleted
|
|
216
|
+
filesWritten: result.filesWritten,
|
|
217
|
+
filesDeleted: []
|
|
112
218
|
};
|
|
113
219
|
}
|
|
114
220
|
};
|
|
115
|
-
async function generateAgentsMdContent(projectRoot) {
|
|
116
|
-
const omniMdPath = join2(projectRoot, "OMNI.md");
|
|
117
|
-
let omniMdContent = "";
|
|
118
|
-
if (existsSync2(omniMdPath)) {
|
|
119
|
-
omniMdContent = await readFile2(omniMdPath, "utf-8");
|
|
120
|
-
}
|
|
121
|
-
let content = omniMdContent;
|
|
122
|
-
content += `
|
|
123
|
-
|
|
124
|
-
## OmniDev
|
|
125
|
-
|
|
126
|
-
@import .omni/instructions.md
|
|
127
|
-
`;
|
|
128
|
-
return content;
|
|
129
|
-
}
|
|
130
221
|
// ../adapters/src/cursor/index.ts
|
|
131
222
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
132
|
-
import {
|
|
133
|
-
import { join as join3 } from "node:path";
|
|
223
|
+
import { join as join6 } from "node:path";
|
|
134
224
|
var cursorAdapter = {
|
|
135
225
|
id: "cursor",
|
|
136
226
|
displayName: "Cursor",
|
|
227
|
+
writers: [
|
|
228
|
+
{ writer: InstructionsMdWriter, outputPath: "CLAUDE.md" },
|
|
229
|
+
{ writer: SkillsWriter, outputPath: ".claude/skills/" },
|
|
230
|
+
{ writer: CursorRulesWriter, outputPath: ".cursor/rules/" }
|
|
231
|
+
],
|
|
137
232
|
async init(ctx) {
|
|
138
|
-
const rulesDir =
|
|
233
|
+
const rulesDir = join6(ctx.projectRoot, ".cursor", "rules");
|
|
139
234
|
mkdirSync2(rulesDir, { recursive: true });
|
|
140
235
|
return {
|
|
141
236
|
filesCreated: [".cursor/rules/"],
|
|
@@ -143,66 +238,39 @@ var cursorAdapter = {
|
|
|
143
238
|
};
|
|
144
239
|
},
|
|
145
240
|
async sync(bundle, ctx) {
|
|
146
|
-
const
|
|
147
|
-
const filesDeleted = [];
|
|
148
|
-
const rulesDir = join3(ctx.projectRoot, ".cursor", "rules");
|
|
149
|
-
mkdirSync2(rulesDir, { recursive: true });
|
|
150
|
-
for (const rule of bundle.rules) {
|
|
151
|
-
const rulePath = join3(rulesDir, `omnidev-${rule.name}.mdc`);
|
|
152
|
-
await writeFile3(rulePath, rule.content, "utf-8");
|
|
153
|
-
filesWritten.push(`.cursor/rules/omnidev-${rule.name}.mdc`);
|
|
154
|
-
}
|
|
241
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
155
242
|
return {
|
|
156
|
-
filesWritten,
|
|
157
|
-
filesDeleted
|
|
243
|
+
filesWritten: result.filesWritten,
|
|
244
|
+
filesDeleted: []
|
|
158
245
|
};
|
|
159
246
|
}
|
|
160
247
|
};
|
|
161
248
|
// ../adapters/src/opencode/index.ts
|
|
162
|
-
import {
|
|
163
|
-
import {
|
|
164
|
-
import { join as join4 } from "node:path";
|
|
249
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
250
|
+
import { join as join7 } from "node:path";
|
|
165
251
|
var opencodeAdapter = {
|
|
166
252
|
id: "opencode",
|
|
167
253
|
displayName: "OpenCode",
|
|
254
|
+
writers: [
|
|
255
|
+
{ writer: InstructionsMdWriter, outputPath: "AGENTS.md" },
|
|
256
|
+
{ writer: SkillsWriter, outputPath: ".opencode/skills/" }
|
|
257
|
+
],
|
|
168
258
|
async init(ctx) {
|
|
169
|
-
const opencodeDir =
|
|
259
|
+
const opencodeDir = join7(ctx.projectRoot, ".opencode");
|
|
170
260
|
mkdirSync3(opencodeDir, { recursive: true });
|
|
171
261
|
return {
|
|
172
262
|
filesCreated: [".opencode/"],
|
|
173
263
|
message: "OpenCode adapter initialized"
|
|
174
264
|
};
|
|
175
265
|
},
|
|
176
|
-
async sync(
|
|
177
|
-
const
|
|
178
|
-
const filesDeleted = [];
|
|
179
|
-
const opencodeDir = join4(ctx.projectRoot, ".opencode");
|
|
180
|
-
mkdirSync3(opencodeDir, { recursive: true });
|
|
181
|
-
const instructionsPath = join4(opencodeDir, "instructions.md");
|
|
182
|
-
const instructionsContent = await generateOpencodeInstructionsContent(ctx.projectRoot);
|
|
183
|
-
await writeFile4(instructionsPath, instructionsContent, "utf-8");
|
|
184
|
-
filesWritten.push(".opencode/instructions.md");
|
|
266
|
+
async sync(bundle, ctx) {
|
|
267
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
185
268
|
return {
|
|
186
|
-
filesWritten,
|
|
187
|
-
filesDeleted
|
|
269
|
+
filesWritten: result.filesWritten,
|
|
270
|
+
filesDeleted: []
|
|
188
271
|
};
|
|
189
272
|
}
|
|
190
273
|
};
|
|
191
|
-
async function generateOpencodeInstructionsContent(projectRoot) {
|
|
192
|
-
const omniMdPath = join4(projectRoot, "OMNI.md");
|
|
193
|
-
let omniMdContent = "";
|
|
194
|
-
if (existsSync3(omniMdPath)) {
|
|
195
|
-
omniMdContent = await readFile3(omniMdPath, "utf-8");
|
|
196
|
-
}
|
|
197
|
-
let content = omniMdContent;
|
|
198
|
-
content += `
|
|
199
|
-
|
|
200
|
-
## OmniDev
|
|
201
|
-
|
|
202
|
-
@import ../.omni/instructions.md
|
|
203
|
-
`;
|
|
204
|
-
return content;
|
|
205
|
-
}
|
|
206
274
|
// ../adapters/src/registry.ts
|
|
207
275
|
import { readEnabledProviders } from "@omnidev-ai/core";
|
|
208
276
|
var builtInAdapters = [
|
|
@@ -223,59 +291,93 @@ async function getEnabledAdapters() {
|
|
|
223
291
|
import {
|
|
224
292
|
getActiveProfile,
|
|
225
293
|
loadBaseConfig,
|
|
294
|
+
patchAddCapabilitySource,
|
|
295
|
+
patchAddMcp,
|
|
296
|
+
patchAddToProfile,
|
|
226
297
|
syncAgentConfiguration,
|
|
227
|
-
|
|
298
|
+
readCapabilityIdFromPath
|
|
228
299
|
} from "@omnidev-ai/core";
|
|
229
300
|
import { buildCommand, buildRouteMap } from "@stricli/core";
|
|
230
|
-
function
|
|
231
|
-
if (
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
301
|
+
async function inferCapabilityId(source, sourceType) {
|
|
302
|
+
if (sourceType === "local") {
|
|
303
|
+
const localPath = source.startsWith("file://") ? source.slice(7) : source;
|
|
304
|
+
const resolvedPath = resolve(localPath);
|
|
305
|
+
const id = await readCapabilityIdFromPath(resolvedPath);
|
|
306
|
+
if (id) {
|
|
307
|
+
return id;
|
|
308
|
+
}
|
|
309
|
+
return basename(resolvedPath);
|
|
239
310
|
}
|
|
240
|
-
|
|
241
|
-
|
|
311
|
+
const parts = source.replace("github:", "").split("/");
|
|
312
|
+
if (parts.length >= 2) {
|
|
313
|
+
return parts[parts.length - 1] ?? parts[1] ?? "capability";
|
|
242
314
|
}
|
|
315
|
+
return "capability";
|
|
243
316
|
}
|
|
244
317
|
async function runAddCap(flags, name) {
|
|
245
318
|
try {
|
|
246
|
-
if (!
|
|
319
|
+
if (!existsSync3("omni.toml")) {
|
|
247
320
|
console.log("✗ No config file found");
|
|
248
321
|
console.log(" Run: omnidev init");
|
|
249
322
|
process.exit(1);
|
|
250
323
|
}
|
|
251
|
-
if (!flags.github.
|
|
252
|
-
console.error("✗
|
|
253
|
-
console.log("
|
|
254
|
-
console.log(" Example: omnidev add cap
|
|
324
|
+
if (!flags.github && !flags.local) {
|
|
325
|
+
console.error("✗ No source specified");
|
|
326
|
+
console.log(" Use --github or --local to specify the capability source");
|
|
327
|
+
console.log(" Example: omnidev add cap --github expo/skills");
|
|
328
|
+
console.log(" Example: omnidev add cap --local ./capabilities/my-cap");
|
|
255
329
|
process.exit(1);
|
|
256
330
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
331
|
+
if (flags.github && flags.local) {
|
|
332
|
+
console.error("✗ Cannot specify both --github and --local");
|
|
333
|
+
console.log(" Use only one source flag");
|
|
334
|
+
process.exit(1);
|
|
335
|
+
}
|
|
336
|
+
let source;
|
|
337
|
+
let sourceType;
|
|
338
|
+
if (flags.local) {
|
|
339
|
+
sourceType = "local";
|
|
340
|
+
const localPath = flags.local.startsWith("file://") ? flags.local.slice(7) : flags.local;
|
|
341
|
+
source = `file://${localPath}`;
|
|
342
|
+
if (!existsSync3(localPath)) {
|
|
343
|
+
console.error(`✗ Local path not found: ${localPath}`);
|
|
344
|
+
process.exit(1);
|
|
345
|
+
}
|
|
346
|
+
} else if (flags.github) {
|
|
347
|
+
sourceType = "github";
|
|
348
|
+
if (!flags.github.includes("/")) {
|
|
349
|
+
console.error("✗ Invalid GitHub repository format");
|
|
350
|
+
console.log(" Expected format: user/repo");
|
|
351
|
+
console.log(" Example: omnidev add cap --github expo/skills");
|
|
352
|
+
process.exit(1);
|
|
353
|
+
}
|
|
354
|
+
source = `github:${flags.github}`;
|
|
355
|
+
} else {
|
|
356
|
+
throw new Error("Unreachable: no source specified");
|
|
261
357
|
}
|
|
262
|
-
|
|
263
|
-
|
|
358
|
+
let capabilityId = name;
|
|
359
|
+
if (!capabilityId) {
|
|
360
|
+
const sourceValue = sourceType === "local" ? flags.local : flags.github;
|
|
361
|
+
if (!sourceValue) {
|
|
362
|
+
throw new Error("Unreachable: cannot infer capability ID");
|
|
363
|
+
}
|
|
364
|
+
capabilityId = await inferCapabilityId(sourceValue, sourceType);
|
|
365
|
+
console.log(` Inferred capability ID: ${capabilityId}`);
|
|
264
366
|
}
|
|
265
|
-
|
|
266
|
-
|
|
367
|
+
const config = await loadBaseConfig();
|
|
368
|
+
const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
|
|
369
|
+
if (config.capabilities?.sources?.[capabilityId]) {
|
|
370
|
+
console.error(`✗ Capability source "${capabilityId}" already exists`);
|
|
267
371
|
console.log(" Use a different name or remove the existing source first");
|
|
268
372
|
process.exit(1);
|
|
269
373
|
}
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
config.capabilities.sources[name] = { source, path: flags.path };
|
|
374
|
+
if (flags.path && sourceType === "github") {
|
|
375
|
+
await patchAddCapabilitySource(capabilityId, { source, path: flags.path });
|
|
273
376
|
} else {
|
|
274
|
-
|
|
377
|
+
await patchAddCapabilitySource(capabilityId, source);
|
|
275
378
|
}
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
console.log(`✓ Added capability source: ${name}`);
|
|
379
|
+
await patchAddToProfile(activeProfile, capabilityId);
|
|
380
|
+
console.log(`✓ Added capability source: ${capabilityId}`);
|
|
279
381
|
console.log(` Source: ${source}`);
|
|
280
382
|
if (flags.path) {
|
|
281
383
|
console.log(` Path: ${flags.path}`);
|
|
@@ -292,17 +394,14 @@ async function runAddCap(flags, name) {
|
|
|
292
394
|
}
|
|
293
395
|
async function runAddMcp(flags, name) {
|
|
294
396
|
try {
|
|
295
|
-
if (!
|
|
397
|
+
if (!existsSync3("omni.toml")) {
|
|
296
398
|
console.log("✗ No config file found");
|
|
297
399
|
console.log(" Run: omnidev init");
|
|
298
400
|
process.exit(1);
|
|
299
401
|
}
|
|
300
402
|
const config = await loadBaseConfig();
|
|
301
403
|
const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
|
|
302
|
-
if (
|
|
303
|
-
config.mcps = {};
|
|
304
|
-
}
|
|
305
|
-
if (config.mcps[name]) {
|
|
404
|
+
if (config.mcps?.[name]) {
|
|
306
405
|
console.error(`✗ MCP "${name}" already exists`);
|
|
307
406
|
console.log(" Use a different name or remove the existing MCP first");
|
|
308
407
|
process.exit(1);
|
|
@@ -356,9 +455,8 @@ async function runAddMcp(flags, name) {
|
|
|
356
455
|
}
|
|
357
456
|
}
|
|
358
457
|
}
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
await writeConfig(config);
|
|
458
|
+
await patchAddMcp(name, mcpConfig);
|
|
459
|
+
await patchAddToProfile(activeProfile, name);
|
|
362
460
|
console.log(`✓ Added MCP: ${name}`);
|
|
363
461
|
console.log(` Transport: ${transport}`);
|
|
364
462
|
if (mcpConfig.url) {
|
|
@@ -408,23 +506,46 @@ function parseArgs(argsString) {
|
|
|
408
506
|
return args;
|
|
409
507
|
}
|
|
410
508
|
async function runAddCapWrapper(flags, name) {
|
|
411
|
-
await runAddCap({ github: flags.github, path: flags.path }, name);
|
|
509
|
+
await runAddCap({ github: flags.github, local: flags.local, path: flags.path }, name);
|
|
412
510
|
}
|
|
413
511
|
var addCapCommand = buildCommand({
|
|
414
512
|
docs: {
|
|
415
|
-
brief: "Add a capability source from GitHub",
|
|
416
|
-
fullDescription:
|
|
513
|
+
brief: "Add a capability source from GitHub or local path",
|
|
514
|
+
fullDescription: `Add a capability source from a GitHub repository or local path. The capability will be auto-enabled in the active profile.
|
|
515
|
+
|
|
516
|
+
GitHub source:
|
|
517
|
+
omnidev add cap [name] --github user/repo [--path subdir]
|
|
518
|
+
|
|
519
|
+
Local source:
|
|
520
|
+
omnidev add cap [name] --local ./path/to/capability
|
|
521
|
+
|
|
522
|
+
If the capability name is omitted, it will be inferred from:
|
|
523
|
+
- For local sources: the ID in capability.toml or directory name
|
|
524
|
+
- For GitHub sources: the repository name or last path segment
|
|
525
|
+
|
|
526
|
+
Examples:
|
|
527
|
+
omnidev add cap my-cap --github expo/skills
|
|
528
|
+
omnidev add cap --github expo/skills # Infers name as "skills"
|
|
529
|
+
omnidev add cap --local ./capabilities/my-cap # Infers name from capability.toml
|
|
530
|
+
omnidev add cap custom-name --local ./capabilities/my-cap`
|
|
417
531
|
},
|
|
418
532
|
parameters: {
|
|
419
533
|
flags: {
|
|
420
534
|
github: {
|
|
421
535
|
kind: "parsed",
|
|
422
536
|
brief: "GitHub repository in user/repo format",
|
|
423
|
-
parse: String
|
|
537
|
+
parse: String,
|
|
538
|
+
optional: true
|
|
539
|
+
},
|
|
540
|
+
local: {
|
|
541
|
+
kind: "parsed",
|
|
542
|
+
brief: "Local path to capability directory",
|
|
543
|
+
parse: String,
|
|
544
|
+
optional: true
|
|
424
545
|
},
|
|
425
546
|
path: {
|
|
426
547
|
kind: "parsed",
|
|
427
|
-
brief: "Subdirectory within the repo containing the capability",
|
|
548
|
+
brief: "Subdirectory within the repo containing the capability (GitHub only)",
|
|
428
549
|
parse: String,
|
|
429
550
|
optional: true
|
|
430
551
|
}
|
|
@@ -433,10 +554,16 @@ var addCapCommand = buildCommand({
|
|
|
433
554
|
kind: "tuple",
|
|
434
555
|
parameters: [
|
|
435
556
|
{
|
|
436
|
-
brief: "Capability name",
|
|
437
|
-
parse: String
|
|
557
|
+
brief: "Capability name (optional, will be inferred if omitted)",
|
|
558
|
+
parse: String,
|
|
559
|
+
optional: true
|
|
438
560
|
}
|
|
439
561
|
]
|
|
562
|
+
},
|
|
563
|
+
aliases: {
|
|
564
|
+
g: "github",
|
|
565
|
+
l: "local",
|
|
566
|
+
p: "path"
|
|
440
567
|
}
|
|
441
568
|
},
|
|
442
569
|
func: runAddCapWrapper
|
|
@@ -542,15 +669,31 @@ var addRoutes = buildRouteMap({
|
|
|
542
669
|
});
|
|
543
670
|
|
|
544
671
|
// src/commands/capability.ts
|
|
672
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4 } from "node:fs";
|
|
673
|
+
import { writeFile as writeFile5 } from "node:fs/promises";
|
|
674
|
+
import { join as join8 } from "node:path";
|
|
675
|
+
import { input } from "@inquirer/prompts";
|
|
545
676
|
import {
|
|
546
677
|
disableCapability,
|
|
547
678
|
discoverCapabilities,
|
|
548
679
|
enableCapability,
|
|
680
|
+
generateCapabilityToml,
|
|
681
|
+
generateHooksTemplate,
|
|
682
|
+
generateHookScript,
|
|
683
|
+
generateRuleTemplate,
|
|
684
|
+
generateSkillTemplate,
|
|
549
685
|
getEnabledCapabilities,
|
|
550
686
|
loadCapabilityConfig,
|
|
551
687
|
syncAgentConfiguration as syncAgentConfiguration2
|
|
552
688
|
} from "@omnidev-ai/core";
|
|
553
689
|
import { buildCommand as buildCommand2, buildRouteMap as buildRouteMap2 } from "@stricli/core";
|
|
690
|
+
|
|
691
|
+
// src/prompts/capability.ts
|
|
692
|
+
function isValidCapabilityId(id) {
|
|
693
|
+
return /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(id);
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
// src/commands/capability.ts
|
|
554
697
|
async function runCapabilityList() {
|
|
555
698
|
try {
|
|
556
699
|
const enabledIds = await getEnabledCapabilities();
|
|
@@ -619,6 +762,105 @@ async function runCapabilityDisable(_flags, name) {
|
|
|
619
762
|
process.exit(1);
|
|
620
763
|
}
|
|
621
764
|
}
|
|
765
|
+
function toTitleCase(kebabCase) {
|
|
766
|
+
return kebabCase.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
767
|
+
}
|
|
768
|
+
async function runCapabilityNew(flags, capabilityId) {
|
|
769
|
+
try {
|
|
770
|
+
if (!existsSync4(".omni")) {
|
|
771
|
+
console.error("✗ OmniDev is not initialized in this directory.");
|
|
772
|
+
console.log("");
|
|
773
|
+
console.log(" Run: omnidev init");
|
|
774
|
+
process.exit(1);
|
|
775
|
+
}
|
|
776
|
+
if (!isValidCapabilityId(capabilityId)) {
|
|
777
|
+
console.error(`✗ Invalid capability ID: '${capabilityId}'`);
|
|
778
|
+
console.log("");
|
|
779
|
+
console.log(" ID must be lowercase, start with a letter, and use kebab-case");
|
|
780
|
+
console.log(" Example: my-capability, tasks, api-client");
|
|
781
|
+
process.exit(1);
|
|
782
|
+
}
|
|
783
|
+
const id = capabilityId;
|
|
784
|
+
let capabilityDir;
|
|
785
|
+
if (flags.path) {
|
|
786
|
+
capabilityDir = flags.path;
|
|
787
|
+
} else {
|
|
788
|
+
const defaultPath = `capabilities/${id}`;
|
|
789
|
+
capabilityDir = await input({
|
|
790
|
+
message: "Output path:",
|
|
791
|
+
default: defaultPath
|
|
792
|
+
});
|
|
793
|
+
}
|
|
794
|
+
if (existsSync4(capabilityDir)) {
|
|
795
|
+
console.error(`✗ Directory already exists at ${capabilityDir}`);
|
|
796
|
+
process.exit(1);
|
|
797
|
+
}
|
|
798
|
+
const name = toTitleCase(id);
|
|
799
|
+
mkdirSync4(capabilityDir, { recursive: true });
|
|
800
|
+
const capabilityToml = generateCapabilityToml({ id, name });
|
|
801
|
+
await writeFile5(join8(capabilityDir, "capability.toml"), capabilityToml, "utf-8");
|
|
802
|
+
const skillDir = join8(capabilityDir, "skills", "getting-started");
|
|
803
|
+
mkdirSync4(skillDir, { recursive: true });
|
|
804
|
+
await writeFile5(join8(skillDir, "SKILL.md"), generateSkillTemplate("getting-started"), "utf-8");
|
|
805
|
+
const rulesDir = join8(capabilityDir, "rules");
|
|
806
|
+
mkdirSync4(rulesDir, { recursive: true });
|
|
807
|
+
await writeFile5(join8(rulesDir, "coding-standards.md"), generateRuleTemplate("coding-standards"), "utf-8");
|
|
808
|
+
const hooksDir = join8(capabilityDir, "hooks");
|
|
809
|
+
mkdirSync4(hooksDir, { recursive: true });
|
|
810
|
+
await writeFile5(join8(hooksDir, "hooks.toml"), generateHooksTemplate(), "utf-8");
|
|
811
|
+
await writeFile5(join8(hooksDir, "example-hook.sh"), generateHookScript(), "utf-8");
|
|
812
|
+
console.log(`✓ Created capability: ${name}`);
|
|
813
|
+
console.log(` Location: ${capabilityDir}`);
|
|
814
|
+
console.log("");
|
|
815
|
+
console.log(" Files created:");
|
|
816
|
+
console.log(" - capability.toml");
|
|
817
|
+
console.log(" - skills/getting-started/SKILL.md");
|
|
818
|
+
console.log(" - rules/coding-standards.md");
|
|
819
|
+
console.log(" - hooks/hooks.toml");
|
|
820
|
+
console.log(" - hooks/example-hook.sh");
|
|
821
|
+
console.log("");
|
|
822
|
+
console.log("\uD83D\uDCA1 To add this capability as a local source, run:");
|
|
823
|
+
console.log(` omnidev add cap --local ./${capabilityDir}`);
|
|
824
|
+
} catch (error) {
|
|
825
|
+
console.error("Error creating capability:", error);
|
|
826
|
+
process.exit(1);
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
var newCommand = buildCommand2({
|
|
830
|
+
docs: {
|
|
831
|
+
brief: "Create a new capability with templates",
|
|
832
|
+
fullDescription: `Create a new capability with templates at a specified path.
|
|
833
|
+
|
|
834
|
+
By default, creates the capability at capabilities/<id>. You can specify a custom path using the --path flag or interactively.
|
|
835
|
+
|
|
836
|
+
Examples:
|
|
837
|
+
omnidev capability new my-cap # Prompts for path, defaults to capabilities/my-cap
|
|
838
|
+
omnidev capability new my-cap --path ./caps/my # Uses ./caps/my directly`
|
|
839
|
+
},
|
|
840
|
+
parameters: {
|
|
841
|
+
flags: {
|
|
842
|
+
path: {
|
|
843
|
+
kind: "parsed",
|
|
844
|
+
brief: "Output path for the capability (skips interactive prompt)",
|
|
845
|
+
parse: String,
|
|
846
|
+
optional: true
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
positional: {
|
|
850
|
+
kind: "tuple",
|
|
851
|
+
parameters: [
|
|
852
|
+
{
|
|
853
|
+
brief: "Capability ID (kebab-case)",
|
|
854
|
+
parse: String
|
|
855
|
+
}
|
|
856
|
+
]
|
|
857
|
+
},
|
|
858
|
+
aliases: {
|
|
859
|
+
p: "path"
|
|
860
|
+
}
|
|
861
|
+
},
|
|
862
|
+
func: runCapabilityNew
|
|
863
|
+
});
|
|
622
864
|
var listCommand = buildCommand2({
|
|
623
865
|
docs: {
|
|
624
866
|
brief: "List all discovered capabilities"
|
|
@@ -666,6 +908,7 @@ var disableCommand = buildCommand2({
|
|
|
666
908
|
});
|
|
667
909
|
var capabilityRoutes = buildRouteMap2({
|
|
668
910
|
routes: {
|
|
911
|
+
new: newCommand,
|
|
669
912
|
list: listCommand,
|
|
670
913
|
enable: enableCommand,
|
|
671
914
|
disable: disableCommand
|
|
@@ -678,7 +921,7 @@ var capabilityRoutes = buildRouteMap2({
|
|
|
678
921
|
// src/commands/doctor.ts
|
|
679
922
|
import { existsSync as existsSync5 } from "node:fs";
|
|
680
923
|
import { execFile } from "node:child_process";
|
|
681
|
-
import { readFile as
|
|
924
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
682
925
|
import { promisify } from "node:util";
|
|
683
926
|
import { buildCommand as buildCommand3 } from "@stricli/core";
|
|
684
927
|
var doctorCommand = buildCommand3({
|
|
@@ -818,7 +1061,7 @@ async function checkRootGitignore() {
|
|
|
818
1061
|
fix: "Run: omnidev init"
|
|
819
1062
|
};
|
|
820
1063
|
}
|
|
821
|
-
const content = await
|
|
1064
|
+
const content = await readFile3(gitignorePath, "utf-8");
|
|
822
1065
|
const lines = content.split(`
|
|
823
1066
|
`).map((line) => line.trim());
|
|
824
1067
|
const hasOmniDir = lines.includes(".omni/");
|
|
@@ -860,15 +1103,15 @@ async function checkCapabilitiesDir() {
|
|
|
860
1103
|
|
|
861
1104
|
// src/commands/init.ts
|
|
862
1105
|
import { exec } from "node:child_process";
|
|
863
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
864
|
-
import { readFile as
|
|
1106
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5 } from "node:fs";
|
|
1107
|
+
import { readFile as readFile4, writeFile as writeFile6 } from "node:fs/promises";
|
|
865
1108
|
import { promisify as promisify2 } from "node:util";
|
|
866
1109
|
import {
|
|
867
1110
|
generateOmniMdTemplate,
|
|
868
1111
|
loadConfig,
|
|
869
1112
|
setActiveProfile,
|
|
870
1113
|
syncAgentConfiguration as syncAgentConfiguration3,
|
|
871
|
-
writeConfig
|
|
1114
|
+
writeConfig,
|
|
872
1115
|
writeEnabledProviders
|
|
873
1116
|
} from "@omnidev-ai/core";
|
|
874
1117
|
import { buildCommand as buildCommand4 } from "@stricli/core";
|
|
@@ -910,9 +1153,9 @@ async function promptForGitignoreProviderFiles(selectedProviders) {
|
|
|
910
1153
|
var execAsync = promisify2(exec);
|
|
911
1154
|
async function runInit(_flags, providerArg) {
|
|
912
1155
|
console.log("Initializing OmniDev...");
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
1156
|
+
mkdirSync5(".omni", { recursive: true });
|
|
1157
|
+
mkdirSync5(".omni/capabilities", { recursive: true });
|
|
1158
|
+
mkdirSync5(".omni/state", { recursive: true });
|
|
916
1159
|
await updateRootGitignore();
|
|
917
1160
|
let providerIds;
|
|
918
1161
|
const isInteractive = !providerArg;
|
|
@@ -939,7 +1182,7 @@ async function runInit(_flags, providerArg) {
|
|
|
939
1182
|
}
|
|
940
1183
|
await writeEnabledProviders(providerIds);
|
|
941
1184
|
if (!existsSync6("omni.toml")) {
|
|
942
|
-
await
|
|
1185
|
+
await writeConfig({
|
|
943
1186
|
profiles: {
|
|
944
1187
|
default: {
|
|
945
1188
|
capabilities: []
|
|
@@ -955,7 +1198,7 @@ async function runInit(_flags, providerArg) {
|
|
|
955
1198
|
await setActiveProfile("default");
|
|
956
1199
|
}
|
|
957
1200
|
if (!existsSync6("OMNI.md")) {
|
|
958
|
-
await
|
|
1201
|
+
await writeFile6("OMNI.md", generateOmniMdTemplate(), "utf-8");
|
|
959
1202
|
}
|
|
960
1203
|
const config = await loadConfig();
|
|
961
1204
|
const ctx = {
|
|
@@ -1029,7 +1272,7 @@ async function addToGitignore(entriesToAdd, sectionHeader) {
|
|
|
1029
1272
|
const gitignorePath = ".gitignore";
|
|
1030
1273
|
let content = "";
|
|
1031
1274
|
if (existsSync6(gitignorePath)) {
|
|
1032
|
-
content = await
|
|
1275
|
+
content = await readFile4(gitignorePath, "utf-8");
|
|
1033
1276
|
}
|
|
1034
1277
|
const lines = content.split(`
|
|
1035
1278
|
`);
|
|
@@ -1044,7 +1287,7 @@ async function addToGitignore(entriesToAdd, sectionHeader) {
|
|
|
1044
1287
|
${missingEntries.join(`
|
|
1045
1288
|
`)}
|
|
1046
1289
|
`;
|
|
1047
|
-
await
|
|
1290
|
+
await writeFile6(gitignorePath, content + section, "utf-8");
|
|
1048
1291
|
}
|
|
1049
1292
|
async function getTrackedProviderFiles(files) {
|
|
1050
1293
|
const tracked = [];
|
|
@@ -1333,9 +1576,8 @@ async function runSync() {
|
|
|
1333
1576
|
console.log(" • Capability registry");
|
|
1334
1577
|
console.log(" • Capability sync hooks");
|
|
1335
1578
|
console.log(" • .omni/.gitignore");
|
|
1336
|
-
console.log(" • .omni/instructions.md");
|
|
1337
1579
|
if (adapters.length > 0) {
|
|
1338
|
-
console.log(" • Provider-specific files");
|
|
1580
|
+
console.log(" • Provider-specific files (instructions embedded)");
|
|
1339
1581
|
}
|
|
1340
1582
|
} catch (error) {
|
|
1341
1583
|
console.error("");
|
|
@@ -1437,10 +1679,10 @@ async function loadCapabilityCommands() {
|
|
|
1437
1679
|
return commands;
|
|
1438
1680
|
}
|
|
1439
1681
|
async function loadCapabilityExport(capability) {
|
|
1440
|
-
const capabilityPath =
|
|
1441
|
-
const indexPath =
|
|
1682
|
+
const capabilityPath = join9(process.cwd(), capability.path);
|
|
1683
|
+
const indexPath = join9(capabilityPath, "index.ts");
|
|
1442
1684
|
if (!existsSync8(indexPath)) {
|
|
1443
|
-
const jsIndexPath =
|
|
1685
|
+
const jsIndexPath = join9(capabilityPath, "index.js");
|
|
1444
1686
|
if (!existsSync8(jsIndexPath)) {
|
|
1445
1687
|
return null;
|
|
1446
1688
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@omnidev-ai/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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.
|
|
31
|
+
"@omnidev-ai/core": "0.10.0",
|
|
32
32
|
"@stricli/core": "^1.2.5"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
|
-
"@omnidev-ai/adapters": "0.0.
|
|
35
|
+
"@omnidev-ai/adapters": "0.0.11",
|
|
36
36
|
"bunup": "^0.16.20"
|
|
37
37
|
}
|
|
38
38
|
}
|