@omnidev-ai/cli 0.9.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 +413 -179
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -24,149 +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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
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
|
+
}
|
|
44
48
|
return {
|
|
45
|
-
|
|
46
|
-
message: "Claude Code adapter initialized"
|
|
49
|
+
filesWritten
|
|
47
50
|
};
|
|
48
|
-
}
|
|
49
|
-
|
|
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 });
|
|
50
126
|
const filesWritten = [];
|
|
51
|
-
const filesDeleted = [];
|
|
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");
|
|
59
|
-
mkdirSync(skillsDir, { recursive: true });
|
|
60
127
|
for (const skill of bundle.skills) {
|
|
61
|
-
const skillDir =
|
|
62
|
-
|
|
63
|
-
const skillPath =
|
|
128
|
+
const skillDir = join4(skillsDir, skill.name);
|
|
129
|
+
await mkdir4(skillDir, { recursive: true });
|
|
130
|
+
const skillPath = join4(skillDir, "SKILL.md");
|
|
64
131
|
const content = `---
|
|
65
132
|
name: ${skill.name}
|
|
66
133
|
description: "${skill.description}"
|
|
67
134
|
---
|
|
68
135
|
|
|
69
136
|
${skill.instructions}`;
|
|
70
|
-
await
|
|
71
|
-
filesWritten.push(
|
|
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
|
-
}
|
|
137
|
+
await writeFile4(skillPath, content, "utf-8");
|
|
138
|
+
filesWritten.push(join4(ctx.outputPath, skill.name, "SKILL.md"));
|
|
79
139
|
}
|
|
80
140
|
return {
|
|
81
|
-
filesWritten
|
|
82
|
-
filesDeleted
|
|
141
|
+
filesWritten
|
|
83
142
|
};
|
|
84
143
|
}
|
|
85
144
|
};
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
@import .omni/instructions.md
|
|
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 = {};
|
|
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;
|
|
110
155
|
}
|
|
156
|
+
seen.add(key);
|
|
157
|
+
uniqueConfigs.push(config);
|
|
158
|
+
}
|
|
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);
|
|
111
166
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
167
|
+
return {
|
|
168
|
+
filesWritten: allFilesWritten,
|
|
169
|
+
deduplicatedCount
|
|
115
170
|
};
|
|
116
|
-
await writeFile(settingsPath, `${JSON.stringify(newSettings, null, 2)}
|
|
117
|
-
`, "utf-8");
|
|
118
|
-
return true;
|
|
119
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
|
+
};
|
|
120
195
|
// ../adapters/src/codex/index.ts
|
|
121
|
-
import {
|
|
122
|
-
import {
|
|
123
|
-
import { join as join2 } from "node:path";
|
|
196
|
+
import { mkdirSync } from "node:fs";
|
|
197
|
+
import { join as join5 } from "node:path";
|
|
124
198
|
var codexAdapter = {
|
|
125
199
|
id: "codex",
|
|
126
200
|
displayName: "Codex",
|
|
127
|
-
|
|
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 });
|
|
128
208
|
return {
|
|
129
|
-
filesCreated: [],
|
|
209
|
+
filesCreated: [".codex/"],
|
|
130
210
|
message: "Codex adapter initialized"
|
|
131
211
|
};
|
|
132
212
|
},
|
|
133
|
-
async sync(
|
|
134
|
-
const
|
|
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");
|
|
213
|
+
async sync(bundle, ctx) {
|
|
214
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
140
215
|
return {
|
|
141
|
-
filesWritten,
|
|
142
|
-
filesDeleted
|
|
216
|
+
filesWritten: result.filesWritten,
|
|
217
|
+
filesDeleted: []
|
|
143
218
|
};
|
|
144
219
|
}
|
|
145
220
|
};
|
|
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 += `
|
|
154
|
-
|
|
155
|
-
## OmniDev
|
|
156
|
-
|
|
157
|
-
@import .omni/instructions.md
|
|
158
|
-
`;
|
|
159
|
-
return content;
|
|
160
|
-
}
|
|
161
221
|
// ../adapters/src/cursor/index.ts
|
|
162
222
|
import { mkdirSync as mkdirSync2 } from "node:fs";
|
|
163
|
-
import {
|
|
164
|
-
import { join as join3 } from "node:path";
|
|
223
|
+
import { join as join6 } from "node:path";
|
|
165
224
|
var cursorAdapter = {
|
|
166
225
|
id: "cursor",
|
|
167
226
|
displayName: "Cursor",
|
|
227
|
+
writers: [
|
|
228
|
+
{ writer: InstructionsMdWriter, outputPath: "CLAUDE.md" },
|
|
229
|
+
{ writer: SkillsWriter, outputPath: ".claude/skills/" },
|
|
230
|
+
{ writer: CursorRulesWriter, outputPath: ".cursor/rules/" }
|
|
231
|
+
],
|
|
168
232
|
async init(ctx) {
|
|
169
|
-
const rulesDir =
|
|
233
|
+
const rulesDir = join6(ctx.projectRoot, ".cursor", "rules");
|
|
170
234
|
mkdirSync2(rulesDir, { recursive: true });
|
|
171
235
|
return {
|
|
172
236
|
filesCreated: [".cursor/rules/"],
|
|
@@ -174,66 +238,39 @@ var cursorAdapter = {
|
|
|
174
238
|
};
|
|
175
239
|
},
|
|
176
240
|
async sync(bundle, ctx) {
|
|
177
|
-
const
|
|
178
|
-
const filesDeleted = [];
|
|
179
|
-
const rulesDir = join3(ctx.projectRoot, ".cursor", "rules");
|
|
180
|
-
mkdirSync2(rulesDir, { recursive: true });
|
|
181
|
-
for (const rule of bundle.rules) {
|
|
182
|
-
const rulePath = join3(rulesDir, `omnidev-${rule.name}.mdc`);
|
|
183
|
-
await writeFile3(rulePath, rule.content, "utf-8");
|
|
184
|
-
filesWritten.push(`.cursor/rules/omnidev-${rule.name}.mdc`);
|
|
185
|
-
}
|
|
241
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
186
242
|
return {
|
|
187
|
-
filesWritten,
|
|
188
|
-
filesDeleted
|
|
243
|
+
filesWritten: result.filesWritten,
|
|
244
|
+
filesDeleted: []
|
|
189
245
|
};
|
|
190
246
|
}
|
|
191
247
|
};
|
|
192
248
|
// ../adapters/src/opencode/index.ts
|
|
193
|
-
import {
|
|
194
|
-
import {
|
|
195
|
-
import { join as join4 } from "node:path";
|
|
249
|
+
import { mkdirSync as mkdirSync3 } from "node:fs";
|
|
250
|
+
import { join as join7 } from "node:path";
|
|
196
251
|
var opencodeAdapter = {
|
|
197
252
|
id: "opencode",
|
|
198
253
|
displayName: "OpenCode",
|
|
254
|
+
writers: [
|
|
255
|
+
{ writer: InstructionsMdWriter, outputPath: "AGENTS.md" },
|
|
256
|
+
{ writer: SkillsWriter, outputPath: ".opencode/skills/" }
|
|
257
|
+
],
|
|
199
258
|
async init(ctx) {
|
|
200
|
-
const opencodeDir =
|
|
259
|
+
const opencodeDir = join7(ctx.projectRoot, ".opencode");
|
|
201
260
|
mkdirSync3(opencodeDir, { recursive: true });
|
|
202
261
|
return {
|
|
203
262
|
filesCreated: [".opencode/"],
|
|
204
263
|
message: "OpenCode adapter initialized"
|
|
205
264
|
};
|
|
206
265
|
},
|
|
207
|
-
async sync(
|
|
208
|
-
const
|
|
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");
|
|
266
|
+
async sync(bundle, ctx) {
|
|
267
|
+
const result = await executeWriters(this.writers, bundle, ctx.projectRoot);
|
|
216
268
|
return {
|
|
217
|
-
filesWritten,
|
|
218
|
-
filesDeleted
|
|
269
|
+
filesWritten: result.filesWritten,
|
|
270
|
+
filesDeleted: []
|
|
219
271
|
};
|
|
220
272
|
}
|
|
221
273
|
};
|
|
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 += `
|
|
230
|
-
|
|
231
|
-
## OmniDev
|
|
232
|
-
|
|
233
|
-
@import ../.omni/instructions.md
|
|
234
|
-
`;
|
|
235
|
-
return content;
|
|
236
|
-
}
|
|
237
274
|
// ../adapters/src/registry.ts
|
|
238
275
|
import { readEnabledProviders } from "@omnidev-ai/core";
|
|
239
276
|
var builtInAdapters = [
|
|
@@ -257,37 +294,90 @@ import {
|
|
|
257
294
|
patchAddCapabilitySource,
|
|
258
295
|
patchAddMcp,
|
|
259
296
|
patchAddToProfile,
|
|
260
|
-
syncAgentConfiguration
|
|
297
|
+
syncAgentConfiguration,
|
|
298
|
+
readCapabilityIdFromPath
|
|
261
299
|
} from "@omnidev-ai/core";
|
|
262
300
|
import { buildCommand, buildRouteMap } from "@stricli/core";
|
|
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);
|
|
310
|
+
}
|
|
311
|
+
const parts = source.replace("github:", "").split("/");
|
|
312
|
+
if (parts.length >= 2) {
|
|
313
|
+
return parts[parts.length - 1] ?? parts[1] ?? "capability";
|
|
314
|
+
}
|
|
315
|
+
return "capability";
|
|
316
|
+
}
|
|
263
317
|
async function runAddCap(flags, name) {
|
|
264
318
|
try {
|
|
265
|
-
if (!
|
|
319
|
+
if (!existsSync3("omni.toml")) {
|
|
266
320
|
console.log("✗ No config file found");
|
|
267
321
|
console.log(" Run: omnidev init");
|
|
268
322
|
process.exit(1);
|
|
269
323
|
}
|
|
270
|
-
if (!flags.github.
|
|
271
|
-
console.error("✗
|
|
272
|
-
console.log("
|
|
273
|
-
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");
|
|
329
|
+
process.exit(1);
|
|
330
|
+
}
|
|
331
|
+
if (flags.github && flags.local) {
|
|
332
|
+
console.error("✗ Cannot specify both --github and --local");
|
|
333
|
+
console.log(" Use only one source flag");
|
|
274
334
|
process.exit(1);
|
|
275
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");
|
|
357
|
+
}
|
|
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}`);
|
|
366
|
+
}
|
|
276
367
|
const config = await loadBaseConfig();
|
|
277
368
|
const activeProfile = await getActiveProfile() ?? config.active_profile ?? "default";
|
|
278
|
-
if (config.capabilities?.sources?.[
|
|
279
|
-
console.error(`✗ Capability source "${
|
|
369
|
+
if (config.capabilities?.sources?.[capabilityId]) {
|
|
370
|
+
console.error(`✗ Capability source "${capabilityId}" already exists`);
|
|
280
371
|
console.log(" Use a different name or remove the existing source first");
|
|
281
372
|
process.exit(1);
|
|
282
373
|
}
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
await patchAddCapabilitySource(name, { source, path: flags.path });
|
|
374
|
+
if (flags.path && sourceType === "github") {
|
|
375
|
+
await patchAddCapabilitySource(capabilityId, { source, path: flags.path });
|
|
286
376
|
} else {
|
|
287
|
-
await patchAddCapabilitySource(
|
|
377
|
+
await patchAddCapabilitySource(capabilityId, source);
|
|
288
378
|
}
|
|
289
|
-
await patchAddToProfile(activeProfile,
|
|
290
|
-
console.log(`✓ Added capability source: ${
|
|
379
|
+
await patchAddToProfile(activeProfile, capabilityId);
|
|
380
|
+
console.log(`✓ Added capability source: ${capabilityId}`);
|
|
291
381
|
console.log(` Source: ${source}`);
|
|
292
382
|
if (flags.path) {
|
|
293
383
|
console.log(` Path: ${flags.path}`);
|
|
@@ -304,7 +394,7 @@ async function runAddCap(flags, name) {
|
|
|
304
394
|
}
|
|
305
395
|
async function runAddMcp(flags, name) {
|
|
306
396
|
try {
|
|
307
|
-
if (!
|
|
397
|
+
if (!existsSync3("omni.toml")) {
|
|
308
398
|
console.log("✗ No config file found");
|
|
309
399
|
console.log(" Run: omnidev init");
|
|
310
400
|
process.exit(1);
|
|
@@ -416,23 +506,46 @@ function parseArgs(argsString) {
|
|
|
416
506
|
return args;
|
|
417
507
|
}
|
|
418
508
|
async function runAddCapWrapper(flags, name) {
|
|
419
|
-
await runAddCap({ github: flags.github, path: flags.path }, name);
|
|
509
|
+
await runAddCap({ github: flags.github, local: flags.local, path: flags.path }, name);
|
|
420
510
|
}
|
|
421
511
|
var addCapCommand = buildCommand({
|
|
422
512
|
docs: {
|
|
423
|
-
brief: "Add a capability source from GitHub",
|
|
424
|
-
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`
|
|
425
531
|
},
|
|
426
532
|
parameters: {
|
|
427
533
|
flags: {
|
|
428
534
|
github: {
|
|
429
535
|
kind: "parsed",
|
|
430
536
|
brief: "GitHub repository in user/repo format",
|
|
431
|
-
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
|
|
432
545
|
},
|
|
433
546
|
path: {
|
|
434
547
|
kind: "parsed",
|
|
435
|
-
brief: "Subdirectory within the repo containing the capability",
|
|
548
|
+
brief: "Subdirectory within the repo containing the capability (GitHub only)",
|
|
436
549
|
parse: String,
|
|
437
550
|
optional: true
|
|
438
551
|
}
|
|
@@ -441,10 +554,16 @@ var addCapCommand = buildCommand({
|
|
|
441
554
|
kind: "tuple",
|
|
442
555
|
parameters: [
|
|
443
556
|
{
|
|
444
|
-
brief: "Capability name",
|
|
445
|
-
parse: String
|
|
557
|
+
brief: "Capability name (optional, will be inferred if omitted)",
|
|
558
|
+
parse: String,
|
|
559
|
+
optional: true
|
|
446
560
|
}
|
|
447
561
|
]
|
|
562
|
+
},
|
|
563
|
+
aliases: {
|
|
564
|
+
g: "github",
|
|
565
|
+
l: "local",
|
|
566
|
+
p: "path"
|
|
448
567
|
}
|
|
449
568
|
},
|
|
450
569
|
func: runAddCapWrapper
|
|
@@ -550,15 +669,31 @@ var addRoutes = buildRouteMap({
|
|
|
550
669
|
});
|
|
551
670
|
|
|
552
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";
|
|
553
676
|
import {
|
|
554
677
|
disableCapability,
|
|
555
678
|
discoverCapabilities,
|
|
556
679
|
enableCapability,
|
|
680
|
+
generateCapabilityToml,
|
|
681
|
+
generateHooksTemplate,
|
|
682
|
+
generateHookScript,
|
|
683
|
+
generateRuleTemplate,
|
|
684
|
+
generateSkillTemplate,
|
|
557
685
|
getEnabledCapabilities,
|
|
558
686
|
loadCapabilityConfig,
|
|
559
687
|
syncAgentConfiguration as syncAgentConfiguration2
|
|
560
688
|
} from "@omnidev-ai/core";
|
|
561
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
|
|
562
697
|
async function runCapabilityList() {
|
|
563
698
|
try {
|
|
564
699
|
const enabledIds = await getEnabledCapabilities();
|
|
@@ -627,6 +762,105 @@ async function runCapabilityDisable(_flags, name) {
|
|
|
627
762
|
process.exit(1);
|
|
628
763
|
}
|
|
629
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
|
+
});
|
|
630
864
|
var listCommand = buildCommand2({
|
|
631
865
|
docs: {
|
|
632
866
|
brief: "List all discovered capabilities"
|
|
@@ -674,6 +908,7 @@ var disableCommand = buildCommand2({
|
|
|
674
908
|
});
|
|
675
909
|
var capabilityRoutes = buildRouteMap2({
|
|
676
910
|
routes: {
|
|
911
|
+
new: newCommand,
|
|
677
912
|
list: listCommand,
|
|
678
913
|
enable: enableCommand,
|
|
679
914
|
disable: disableCommand
|
|
@@ -686,7 +921,7 @@ var capabilityRoutes = buildRouteMap2({
|
|
|
686
921
|
// src/commands/doctor.ts
|
|
687
922
|
import { existsSync as existsSync5 } from "node:fs";
|
|
688
923
|
import { execFile } from "node:child_process";
|
|
689
|
-
import { readFile as
|
|
924
|
+
import { readFile as readFile3 } from "node:fs/promises";
|
|
690
925
|
import { promisify } from "node:util";
|
|
691
926
|
import { buildCommand as buildCommand3 } from "@stricli/core";
|
|
692
927
|
var doctorCommand = buildCommand3({
|
|
@@ -826,7 +1061,7 @@ async function checkRootGitignore() {
|
|
|
826
1061
|
fix: "Run: omnidev init"
|
|
827
1062
|
};
|
|
828
1063
|
}
|
|
829
|
-
const content = await
|
|
1064
|
+
const content = await readFile3(gitignorePath, "utf-8");
|
|
830
1065
|
const lines = content.split(`
|
|
831
1066
|
`).map((line) => line.trim());
|
|
832
1067
|
const hasOmniDir = lines.includes(".omni/");
|
|
@@ -868,8 +1103,8 @@ async function checkCapabilitiesDir() {
|
|
|
868
1103
|
|
|
869
1104
|
// src/commands/init.ts
|
|
870
1105
|
import { exec } from "node:child_process";
|
|
871
|
-
import { existsSync as existsSync6, mkdirSync as
|
|
872
|
-
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";
|
|
873
1108
|
import { promisify as promisify2 } from "node:util";
|
|
874
1109
|
import {
|
|
875
1110
|
generateOmniMdTemplate,
|
|
@@ -918,9 +1153,9 @@ async function promptForGitignoreProviderFiles(selectedProviders) {
|
|
|
918
1153
|
var execAsync = promisify2(exec);
|
|
919
1154
|
async function runInit(_flags, providerArg) {
|
|
920
1155
|
console.log("Initializing OmniDev...");
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
1156
|
+
mkdirSync5(".omni", { recursive: true });
|
|
1157
|
+
mkdirSync5(".omni/capabilities", { recursive: true });
|
|
1158
|
+
mkdirSync5(".omni/state", { recursive: true });
|
|
924
1159
|
await updateRootGitignore();
|
|
925
1160
|
let providerIds;
|
|
926
1161
|
const isInteractive = !providerArg;
|
|
@@ -963,7 +1198,7 @@ async function runInit(_flags, providerArg) {
|
|
|
963
1198
|
await setActiveProfile("default");
|
|
964
1199
|
}
|
|
965
1200
|
if (!existsSync6("OMNI.md")) {
|
|
966
|
-
await
|
|
1201
|
+
await writeFile6("OMNI.md", generateOmniMdTemplate(), "utf-8");
|
|
967
1202
|
}
|
|
968
1203
|
const config = await loadConfig();
|
|
969
1204
|
const ctx = {
|
|
@@ -1037,7 +1272,7 @@ async function addToGitignore(entriesToAdd, sectionHeader) {
|
|
|
1037
1272
|
const gitignorePath = ".gitignore";
|
|
1038
1273
|
let content = "";
|
|
1039
1274
|
if (existsSync6(gitignorePath)) {
|
|
1040
|
-
content = await
|
|
1275
|
+
content = await readFile4(gitignorePath, "utf-8");
|
|
1041
1276
|
}
|
|
1042
1277
|
const lines = content.split(`
|
|
1043
1278
|
`);
|
|
@@ -1052,7 +1287,7 @@ async function addToGitignore(entriesToAdd, sectionHeader) {
|
|
|
1052
1287
|
${missingEntries.join(`
|
|
1053
1288
|
`)}
|
|
1054
1289
|
`;
|
|
1055
|
-
await
|
|
1290
|
+
await writeFile6(gitignorePath, content + section, "utf-8");
|
|
1056
1291
|
}
|
|
1057
1292
|
async function getTrackedProviderFiles(files) {
|
|
1058
1293
|
const tracked = [];
|
|
@@ -1341,9 +1576,8 @@ async function runSync() {
|
|
|
1341
1576
|
console.log(" • Capability registry");
|
|
1342
1577
|
console.log(" • Capability sync hooks");
|
|
1343
1578
|
console.log(" • .omni/.gitignore");
|
|
1344
|
-
console.log(" • .omni/instructions.md");
|
|
1345
1579
|
if (adapters.length > 0) {
|
|
1346
|
-
console.log(" • Provider-specific files");
|
|
1580
|
+
console.log(" • Provider-specific files (instructions embedded)");
|
|
1347
1581
|
}
|
|
1348
1582
|
} catch (error) {
|
|
1349
1583
|
console.error("");
|
|
@@ -1445,10 +1679,10 @@ async function loadCapabilityCommands() {
|
|
|
1445
1679
|
return commands;
|
|
1446
1680
|
}
|
|
1447
1681
|
async function loadCapabilityExport(capability) {
|
|
1448
|
-
const capabilityPath =
|
|
1449
|
-
const indexPath =
|
|
1682
|
+
const capabilityPath = join9(process.cwd(), capability.path);
|
|
1683
|
+
const indexPath = join9(capabilityPath, "index.ts");
|
|
1450
1684
|
if (!existsSync8(indexPath)) {
|
|
1451
|
-
const jsIndexPath =
|
|
1685
|
+
const jsIndexPath = join9(capabilityPath, "index.js");
|
|
1452
1686
|
if (!existsSync8(jsIndexPath)) {
|
|
1453
1687
|
return null;
|
|
1454
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,7 +28,7 @@
|
|
|
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": {
|