@ftarganski/omni-ai 0.1.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 (49) hide show
  1. package/dist/chunk-3RZELMF2.js +214 -0
  2. package/dist/chunk-5WELBZWN.js +70 -0
  3. package/dist/chunk-6APAA6WD.js +495 -0
  4. package/dist/chunk-6OPRALDC.js +163 -0
  5. package/dist/chunk-6YFFZMXY.js +104 -0
  6. package/dist/chunk-AG6NZIJ3.js +122 -0
  7. package/dist/chunk-AWMSN7OB.js +451 -0
  8. package/dist/chunk-JTXDF5KG.js +156 -0
  9. package/dist/chunk-M4QJF7CV.js +57 -0
  10. package/dist/chunk-PPTEJ2FH.js +102 -0
  11. package/dist/chunk-S5MK6LBH.js +136 -0
  12. package/dist/chunk-TFU437SW.js +107 -0
  13. package/dist/chunk-Y4EYGADJ.js +216 -0
  14. package/dist/cli/bin.js +2723 -0
  15. package/dist/index.d.ts +16 -0
  16. package/dist/index.js +42 -0
  17. package/dist/mcp.d.ts +1 -0
  18. package/dist/mcp.js +86 -0
  19. package/dist/memory.d.ts +1 -0
  20. package/dist/memory.js +320 -0
  21. package/dist/provider-anthropic.d.ts +1 -0
  22. package/dist/provider-anthropic.js +120 -0
  23. package/dist/provider-google.d.ts +1 -0
  24. package/dist/provider-google.js +141 -0
  25. package/dist/provider-openai.d.ts +1 -0
  26. package/dist/provider-openai.js +214 -0
  27. package/dist/skills/backend.d.ts +1 -0
  28. package/dist/skills/backend.js +12 -0
  29. package/dist/skills/code.d.ts +1 -0
  30. package/dist/skills/code.js +6 -0
  31. package/dist/skills/frontend.d.ts +1 -0
  32. package/dist/skills/frontend.js +10 -0
  33. package/dist/skills/fs.d.ts +1 -0
  34. package/dist/skills/fs.js +10 -0
  35. package/dist/skills/git.d.ts +1 -0
  36. package/dist/skills/git.js +12 -0
  37. package/dist/skills/http.d.ts +1 -0
  38. package/dist/skills/http.js +6 -0
  39. package/dist/skills/index.d.ts +1 -0
  40. package/dist/skills/index.js +60 -0
  41. package/dist/skills/multimodal.d.ts +1 -0
  42. package/dist/skills/multimodal.js +6 -0
  43. package/dist/skills/qa.d.ts +1 -0
  44. package/dist/skills/qa.js +8 -0
  45. package/dist/skills/ux.d.ts +1 -0
  46. package/dist/skills/ux.js +6 -0
  47. package/dist/src-6MUVU5ML.js +8 -0
  48. package/dist/src-ZHTGR7T6.js +8 -0
  49. package/package.json +136 -0
@@ -0,0 +1,2723 @@
1
+ #!/usr/bin/env node
2
+ import "../chunk-AG6NZIJ3.js";
3
+ import "../chunk-Y4EYGADJ.js";
4
+ import {
5
+ createRuntime,
6
+ getRegisteredProviders,
7
+ registerProvider
8
+ } from "../chunk-AWMSN7OB.js";
9
+
10
+ // ../cli/src/bin.ts
11
+ import { dirname as dirname4, resolve as resolve17 } from "path";
12
+ import { fileURLToPath as fileURLToPath3 } from "url";
13
+ import { Command } from "commander";
14
+ import { config as loadDotenv } from "dotenv";
15
+
16
+ // ../provider-google/src/provider.ts
17
+ import { GoogleGenerativeAI } from "@google/generative-ai";
18
+
19
+ // ../provider-google/src/mappers.ts
20
+ function toGeminiPart(part) {
21
+ if (part.type === "text") return { text: part.text };
22
+ return { inlineData: { mimeType: part.mimeType, data: part.data } };
23
+ }
24
+ function contentToParts(content) {
25
+ if (typeof content === "string") return [{ text: content }];
26
+ return content.map(toGeminiPart);
27
+ }
28
+ function extractSystemInstruction(request) {
29
+ const systemMsg = request.messages.find((m) => m.role === "system");
30
+ const raw = request.systemPrompt ?? systemMsg?.content;
31
+ if (raw === void 0) return void 0;
32
+ return typeof raw === "string" ? raw : raw.map((p) => p.type === "text" ? p.text : "").join("");
33
+ }
34
+ function toGeminiContents(messages) {
35
+ return messages.filter((m) => m.role !== "system").map((m) => ({
36
+ role: m.role === "assistant" ? "model" : "user",
37
+ parts: contentToParts(m.content)
38
+ }));
39
+ }
40
+ function toGeminiTools(tools) {
41
+ const functionDeclarations = tools.map((t) => ({
42
+ name: t.name,
43
+ description: t.description,
44
+ parameters: t.parameters
45
+ }));
46
+ return [{ functionDeclarations }];
47
+ }
48
+ function fromGeminiResponse(response, modelName, providerName) {
49
+ const candidate = response.candidates?.[0];
50
+ const parts = candidate?.content?.parts ?? [];
51
+ let content = "";
52
+ const toolCalls = [];
53
+ for (const part of parts) {
54
+ if ("text" in part && typeof part.text === "string") {
55
+ content += part.text;
56
+ } else if ("functionCall" in part && part.functionCall) {
57
+ toolCalls.push({
58
+ id: part.functionCall.name,
59
+ name: part.functionCall.name,
60
+ arguments: part.functionCall.args ?? {}
61
+ });
62
+ }
63
+ }
64
+ const usage = response.usageMetadata ? {
65
+ inputTokens: response.usageMetadata.promptTokenCount ?? 0,
66
+ outputTokens: response.usageMetadata.candidatesTokenCount ?? 0
67
+ } : void 0;
68
+ return {
69
+ content,
70
+ toolCalls: toolCalls.length > 0 ? toolCalls : void 0,
71
+ usage,
72
+ model: modelName,
73
+ provider: providerName
74
+ };
75
+ }
76
+
77
+ // ../provider-google/src/provider.ts
78
+ var GoogleProvider = class {
79
+ name;
80
+ capabilities = {
81
+ chat: true,
82
+ embedding: true,
83
+ streaming: true,
84
+ toolUse: true,
85
+ vision: true
86
+ };
87
+ client;
88
+ defaultModel;
89
+ constructor(options) {
90
+ this.name = options.name ?? "google";
91
+ this.defaultModel = options.defaultModel ?? "gemini-2.0-flash";
92
+ this.client = new GoogleGenerativeAI(options.apiKey);
93
+ }
94
+ async complete(request) {
95
+ const modelName = request.model ?? this.defaultModel;
96
+ const systemInstruction = extractSystemInstruction(request);
97
+ const contents = toGeminiContents(request.messages);
98
+ const tools = request.tools && request.tools.length > 0 ? toGeminiTools(request.tools) : void 0;
99
+ const model = this.client.getGenerativeModel({
100
+ model: modelName,
101
+ systemInstruction,
102
+ tools,
103
+ generationConfig: {
104
+ temperature: request.temperature,
105
+ maxOutputTokens: request.maxTokens
106
+ }
107
+ });
108
+ if (request.onToken) {
109
+ const result2 = await model.generateContentStream({ contents });
110
+ let fullContent = "";
111
+ for await (const chunk of result2.stream) {
112
+ const text = chunk.text();
113
+ if (text) {
114
+ fullContent += text;
115
+ request.onToken(text);
116
+ }
117
+ }
118
+ const finalResponse = await result2.response;
119
+ const response = fromGeminiResponse(finalResponse, modelName, this.name);
120
+ return { ...response, content: fullContent };
121
+ }
122
+ const result = await model.generateContent({ contents });
123
+ return fromGeminiResponse(result.response, modelName, this.name);
124
+ }
125
+ async embed(request) {
126
+ const modelName = "text-embedding-004";
127
+ const model = this.client.getGenerativeModel({ model: modelName });
128
+ const inputs = Array.isArray(request.input) ? request.input : [request.input];
129
+ const embeddings = await Promise.all(
130
+ inputs.map(async (text) => {
131
+ const result = await model.embedContent(text);
132
+ return result.embedding.values;
133
+ })
134
+ );
135
+ return { embeddings, model: modelName, provider: this.name };
136
+ }
137
+ };
138
+
139
+ // ../provider-google/src/index.ts
140
+ registerProvider("google", (config) => {
141
+ if (!config.apiKey) {
142
+ throw new Error(`Provider "${config.name}" (google) requires GOOGLE_API_KEY to be set in the environment.`);
143
+ }
144
+ return new GoogleProvider({
145
+ apiKey: config.apiKey,
146
+ defaultModel: config.defaultModel,
147
+ name: config.name
148
+ });
149
+ });
150
+
151
+ // ../cli/src/commands/chain.ts
152
+ import { writeFile as writeFile2 } from "fs/promises";
153
+
154
+ // ../skills/src/code/search-code.ts
155
+ import { readdir, readFile } from "fs/promises";
156
+ import { join } from "path";
157
+ import { z } from "zod";
158
+ var InputSchema = z.object({
159
+ directory: z.string().describe("Root directory to search in"),
160
+ pattern: z.string().describe("Text substring or regex pattern to search for"),
161
+ extensions: z.array(z.string()).default([".ts", ".tsx"]).describe("File extensions to include in the search"),
162
+ maxResults: z.number().int().positive().default(30).describe("Maximum number of matching lines to return"),
163
+ useRegex: z.boolean().default(false).describe("Treat pattern as a regular expression")
164
+ });
165
+ async function searchInFile(filePath, matcher, results, maxResults) {
166
+ if (results.length >= maxResults) return;
167
+ const text = await readFile(filePath, "utf-8");
168
+ const lines = text.split("\n");
169
+ for (let i = 0; i < lines.length; i++) {
170
+ if (results.length >= maxResults) break;
171
+ if (matcher(lines[i])) {
172
+ results.push({ file: filePath, line: i + 1, content: lines[i].trim() });
173
+ }
174
+ }
175
+ }
176
+ async function walkAndSearch(dir, extensions, matcher, results, maxResults) {
177
+ if (results.length >= maxResults) return;
178
+ const entries = await readdir(dir, { withFileTypes: true });
179
+ for (const entry of entries) {
180
+ if (results.length >= maxResults) break;
181
+ if (entry.name === "node_modules" || entry.name === "dist") continue;
182
+ const fullPath = join(dir, entry.name);
183
+ if (entry.isDirectory()) {
184
+ await walkAndSearch(fullPath, extensions, matcher, results, maxResults);
185
+ } else if (entry.isFile() && extensions.some((ext) => entry.name.endsWith(ext))) {
186
+ await searchInFile(fullPath, matcher, results, maxResults);
187
+ }
188
+ }
189
+ }
190
+ var searchCodeSkill = {
191
+ name: "search-code",
192
+ description: "Search for a text pattern across TypeScript/TSX source files. Use this to find existing components, hooks, types or patterns before generating new code \u2014 avoiding duplicates and understanding existing conventions.",
193
+ async execute(input5) {
194
+ const { directory, pattern, extensions, maxResults, useRegex } = InputSchema.parse(input5);
195
+ let matcher;
196
+ if (useRegex) {
197
+ const re = new RegExp(pattern);
198
+ matcher = (line) => re.test(line);
199
+ } else {
200
+ matcher = (line) => line.includes(pattern);
201
+ }
202
+ const results = [];
203
+ await walkAndSearch(directory, extensions, matcher, results, maxResults);
204
+ return results;
205
+ }
206
+ };
207
+
208
+ // ../skills/src/fs/list-directory.ts
209
+ import { readdir as readdir2 } from "fs/promises";
210
+ import { join as join2, resolve, sep } from "path";
211
+ import { z as z2 } from "zod";
212
+ var InputSchema2 = z2.object({
213
+ path: z2.string().describe("Directory path to list"),
214
+ recursive: z2.boolean().default(false).describe("Whether to list files in subdirectories recursively"),
215
+ extensions: z2.array(z2.string()).optional().describe('Filter results by file extension, e.g. [".ts", ".tsx"]')
216
+ });
217
+ function assertSafePath(inputPath) {
218
+ const cwd = process.cwd();
219
+ const resolved = resolve(cwd, inputPath);
220
+ const cwdWithSep = cwd.endsWith(sep) ? cwd : cwd + sep;
221
+ if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
222
+ throw new Error(`Access denied: "${inputPath}" resolves outside the working directory`);
223
+ }
224
+ return resolved;
225
+ }
226
+ async function walk(dir, recursive, extensions) {
227
+ const entries = await readdir2(dir, { withFileTypes: true });
228
+ const results = [];
229
+ for (const entry of entries) {
230
+ const fullPath = join2(dir, entry.name);
231
+ if (entry.isDirectory()) {
232
+ if (recursive) {
233
+ results.push(...await walk(fullPath, recursive, extensions));
234
+ }
235
+ } else if (entry.isFile()) {
236
+ const include = !extensions || extensions.some((ext) => entry.name.endsWith(ext));
237
+ if (include) results.push(fullPath);
238
+ }
239
+ }
240
+ return results;
241
+ }
242
+ var listDirectorySkill = {
243
+ name: "list-directory",
244
+ description: "List files in a directory. Use this to discover existing components, pages or hooks before creating new ones, avoiding duplicates.",
245
+ async execute(input5) {
246
+ const { path, recursive, extensions } = InputSchema2.parse(input5);
247
+ const safePath = assertSafePath(path);
248
+ return await walk(safePath, recursive, extensions);
249
+ }
250
+ };
251
+
252
+ // ../skills/src/fs/read-file.ts
253
+ import { readFile as readFile2 } from "fs/promises";
254
+ import { resolve as resolve2, sep as sep2 } from "path";
255
+ import { z as z3 } from "zod";
256
+ var InputSchema3 = z3.object({
257
+ path: z3.string().describe("Absolute or relative path to the file to read")
258
+ });
259
+ function assertSafePath2(inputPath) {
260
+ const cwd = process.cwd();
261
+ const resolved = resolve2(cwd, inputPath);
262
+ const cwdWithSep = cwd.endsWith(sep2) ? cwd : cwd + sep2;
263
+ if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
264
+ throw new Error(`Access denied: "${inputPath}" resolves outside the working directory`);
265
+ }
266
+ return resolved;
267
+ }
268
+ var readFileSkill = {
269
+ name: "read-file",
270
+ description: "Read the full text content of a file. Use this to inspect existing components, hooks, pages, config files or any source file before generating new code.",
271
+ async execute(input5) {
272
+ const { path } = InputSchema3.parse(input5);
273
+ const safePath = assertSafePath2(path);
274
+ return await readFile2(safePath, "utf-8");
275
+ }
276
+ };
277
+
278
+ // ../skills/src/fs/write-file.ts
279
+ import { mkdir, writeFile } from "fs/promises";
280
+ import { dirname, resolve as resolve3, sep as sep3 } from "path";
281
+ import { z as z4 } from "zod";
282
+ var InputSchema4 = z4.object({
283
+ path: z4.string().describe("Absolute or relative path where the file will be written"),
284
+ content: z4.string().describe("Full text content to write"),
285
+ createDirs: z4.boolean().default(true).describe("Create intermediate directories if they do not exist")
286
+ });
287
+ function assertSafePath3(inputPath) {
288
+ const cwd = process.cwd();
289
+ const resolved = resolve3(cwd, inputPath);
290
+ const cwdWithSep = cwd.endsWith(sep3) ? cwd : cwd + sep3;
291
+ if (resolved !== cwd && !resolved.startsWith(cwdWithSep)) {
292
+ throw new Error(`Access denied: "${inputPath}" resolves outside the working directory`);
293
+ }
294
+ return resolved;
295
+ }
296
+ var writeFileSkill = {
297
+ name: "write-file",
298
+ description: "Write or overwrite a file with the given content. Use this to create new components, hooks, pages or any source file after generating the code.",
299
+ async execute(input5) {
300
+ const { path, content, createDirs } = InputSchema4.parse(input5);
301
+ const safePath = assertSafePath3(path);
302
+ if (createDirs) {
303
+ await mkdir(dirname(safePath), { recursive: true });
304
+ }
305
+ await writeFile(safePath, content, "utf-8");
306
+ return `Written: ${safePath}`;
307
+ }
308
+ };
309
+
310
+ // ../skills/src/git/git-commit-message.ts
311
+ import { resolve as resolve4 } from "path";
312
+ import { z as z5 } from "zod";
313
+
314
+ // ../skills/src/git/shared.ts
315
+ import { spawn } from "child_process";
316
+ function runGit(args, cwd) {
317
+ return new Promise((resolve18, reject) => {
318
+ const proc = spawn("git", args, { cwd, shell: false });
319
+ const stdout = [];
320
+ const stderr = [];
321
+ proc.stdout.on("data", (chunk) => stdout.push(chunk));
322
+ proc.stderr.on("data", (chunk) => stderr.push(chunk));
323
+ proc.on("close", (code) => {
324
+ if (code !== 0) {
325
+ reject(new Error(Buffer.concat(stderr).toString().trim() || `git ${args[0]} exited with code ${code}`));
326
+ } else {
327
+ resolve18(Buffer.concat(stdout).toString());
328
+ }
329
+ });
330
+ proc.on("error", reject);
331
+ });
332
+ }
333
+
334
+ // ../skills/src/git/git-commit-message.ts
335
+ var InputSchema5 = z5.object({
336
+ cwd: z5.string().default(".").describe("Repository root directory"),
337
+ staged: z5.boolean().default(true).describe("Use staged diff (true) or full working-tree diff (false)"),
338
+ hint: z5.string().optional().describe("Optional free-text hint passed to the LLM, e.g. 'fix', 'feat', 'refactor'")
339
+ });
340
+ var SYSTEM = `You are an expert software engineer who writes concise, conventional commit messages.
341
+ Rules:
342
+ - Follow Conventional Commits: <type>(<scope>): <subject>
343
+ - Subject line \u2264 72 chars, imperative mood, no trailing period
344
+ - If changes span multiple concerns, add a short bullet body after a blank line
345
+ - Output ONLY the commit message \u2014 no explanation, no markdown fences`;
346
+ var gitCommitMessageSkill = {
347
+ name: "git-commit-message",
348
+ description: "Generate a conventional commit message for the current staged (or working-tree) changes using the configured LLM provider. Returns a ready-to-use commit message string.",
349
+ async execute(input5, ctx) {
350
+ const { cwd, staged, hint } = InputSchema5.parse(input5);
351
+ const dir = resolve4(cwd);
352
+ const diffArgs = ["diff"];
353
+ if (staged) diffArgs.push("--cached");
354
+ const diff = await runGit(diffArgs, dir);
355
+ if (!diff.trim()) {
356
+ return { message: "chore: no changes staged" };
357
+ }
358
+ const promptParts = [hint ? `Hint from the developer: ${hint}` : null, "--- GIT DIFF ---", diff.slice(0, 12e3)].filter(Boolean).join("\n");
359
+ const response = await ctx.provider.complete({
360
+ systemPrompt: SYSTEM,
361
+ messages: [{ role: "user", content: promptParts }],
362
+ temperature: 0.2,
363
+ maxTokens: 256
364
+ });
365
+ return { message: response.content.trim() };
366
+ }
367
+ };
368
+
369
+ // ../skills/src/git/git-diff.ts
370
+ import { resolve as resolve5 } from "path";
371
+ import { z as z6 } from "zod";
372
+ var InputSchema6 = z6.object({
373
+ cwd: z6.string().default(".").describe("Repository root directory"),
374
+ staged: z6.boolean().default(false).describe("Show staged (cached) diff instead of working-tree diff"),
375
+ file: z6.string().optional().describe("Limit diff to a specific file path"),
376
+ base: z6.string().optional().describe("Base ref to diff from (e.g. 'main', 'HEAD~1')")
377
+ });
378
+ var gitDiffSkill = {
379
+ name: "git-diff",
380
+ description: "Return the unified diff of uncommitted changes (or staged changes). Use this to understand what code changed before writing a commit message or performing a code review.",
381
+ async execute(input5) {
382
+ const { cwd, staged, file, base } = InputSchema6.parse(input5);
383
+ const dir = resolve5(cwd);
384
+ const diffArgs = ["diff"];
385
+ if (staged) diffArgs.push("--cached");
386
+ if (base) diffArgs.push(base);
387
+ if (file) diffArgs.push("--", file);
388
+ const nameArgs = ["diff", "--name-only"];
389
+ if (staged) nameArgs.push("--cached");
390
+ if (base) nameArgs.push(base);
391
+ if (file) nameArgs.push("--", file);
392
+ const [diff, names] = await Promise.all([runGit(diffArgs, dir), runGit(nameArgs, dir)]);
393
+ const changedFiles = names.split("\n").map((l) => l.trim()).filter(Boolean);
394
+ return { diff, changedFiles };
395
+ }
396
+ };
397
+
398
+ // ../skills/src/git/git-log.ts
399
+ import { resolve as resolve6 } from "path";
400
+ import { z as z7 } from "zod";
401
+ var InputSchema7 = z7.object({
402
+ cwd: z7.string().default(".").describe("Repository root directory"),
403
+ maxEntries: z7.number().int().positive().default(10).describe("Maximum number of commits to return"),
404
+ branch: z7.string().optional().describe("Branch or ref to read log from (defaults to HEAD)"),
405
+ since: z7.string().optional().describe("Show commits more recent than this date, e.g. '2 weeks ago'")
406
+ });
407
+ var SEP = "";
408
+ var FORMAT = [`%H${SEP}%h${SEP}%an${SEP}%ai${SEP}%s`].join("");
409
+ var gitLogSkill = {
410
+ name: "git-log",
411
+ description: "Return recent commit history with hash, author, date and subject. Use this to understand recent changes, find the last stable commit, or generate release notes.",
412
+ async execute(input5) {
413
+ const { cwd, maxEntries, branch, since } = InputSchema7.parse(input5);
414
+ const dir = resolve6(cwd);
415
+ const args = ["log", `--format=${FORMAT}`, `-n`, String(maxEntries)];
416
+ if (since) args.push(`--since=${since}`);
417
+ if (branch) args.push(branch);
418
+ const raw = await runGit(args, dir);
419
+ const commits = raw.split("\n").filter(Boolean).map((line) => {
420
+ const [hash, shortHash, author, date, ...rest] = line.split(SEP);
421
+ return { hash, shortHash, author, date, message: rest.join(SEP) };
422
+ });
423
+ return { commits };
424
+ }
425
+ };
426
+
427
+ // ../skills/src/git/git-status.ts
428
+ import { resolve as resolve7 } from "path";
429
+ import { z as z8 } from "zod";
430
+ var InputSchema8 = z8.object({
431
+ cwd: z8.string().default(".").describe("Repository root directory (defaults to process.cwd())")
432
+ });
433
+ function parsePorcelain(raw) {
434
+ const staged = [];
435
+ const unstaged = [];
436
+ const untracked = [];
437
+ for (const line of raw.split("\n")) {
438
+ if (!line || line.startsWith("##")) continue;
439
+ const x = line[0];
440
+ const y = line[1];
441
+ const path = line.slice(3);
442
+ if (x === "?" && y === "?") {
443
+ untracked.push(path);
444
+ } else {
445
+ if (x !== " " && x !== "?") staged.push({ path, status: x });
446
+ if (y !== " " && y !== "?") unstaged.push({ path, status: y });
447
+ }
448
+ }
449
+ return { staged, unstaged, untracked };
450
+ }
451
+ var gitStatusSkill = {
452
+ name: "git-status",
453
+ description: "Return the current git working-tree status: staged changes, unstaged modifications, untracked files and the active branch. Use this before generating a commit message or reviewing pending changes.",
454
+ async execute(input5) {
455
+ const { cwd } = InputSchema8.parse(input5);
456
+ const dir = resolve7(cwd);
457
+ const [porcelain, branchRaw] = await Promise.all([
458
+ runGit(["status", "--porcelain", "-b"], dir),
459
+ runGit(["rev-parse", "--abbrev-ref", "HEAD"], dir)
460
+ ]);
461
+ const branch = branchRaw.trim();
462
+ const { staged, unstaged, untracked } = parsePorcelain(porcelain);
463
+ return { branch, staged, unstaged, untracked };
464
+ }
465
+ };
466
+
467
+ // ../skills/src/http/http-request.ts
468
+ import { z as z9 } from "zod";
469
+ var BearerAuthSchema = z9.object({
470
+ type: z9.literal("bearer"),
471
+ token: z9.string().describe("Bearer token value")
472
+ });
473
+ var BasicAuthSchema = z9.object({
474
+ type: z9.literal("basic"),
475
+ username: z9.string(),
476
+ password: z9.string()
477
+ });
478
+ var OAuth2ClientSchema = z9.object({
479
+ type: z9.literal("oauth2-client-credentials"),
480
+ tokenUrl: z9.string().url().describe("Token endpoint URL"),
481
+ clientId: z9.string(),
482
+ clientSecret: z9.string(),
483
+ scope: z9.string().optional().describe("Space-separated OAuth2 scopes")
484
+ });
485
+ var AuthSchema = z9.discriminatedUnion("type", [BearerAuthSchema, BasicAuthSchema, OAuth2ClientSchema]);
486
+ var InputSchema9 = z9.object({
487
+ url: z9.string().url().describe("Target URL"),
488
+ method: z9.enum(["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"]).default("GET").describe("HTTP method"),
489
+ headers: z9.record(z9.string()).optional().describe("Additional request headers"),
490
+ body: z9.union([z9.string(), z9.record(z9.unknown())]).optional().describe("Request body \u2014 string or JSON object"),
491
+ auth: AuthSchema.optional().describe("Authentication configuration"),
492
+ timeoutMs: z9.number().int().positive().default(3e4).describe("Request timeout in milliseconds")
493
+ });
494
+ async function resolveToken(auth) {
495
+ if (auth.type === "bearer") return `Bearer ${auth.token}`;
496
+ if (auth.type === "basic") {
497
+ const encoded = Buffer.from(`${auth.username}:${auth.password}`).toString("base64");
498
+ return `Basic ${encoded}`;
499
+ }
500
+ const params = new URLSearchParams({
501
+ grant_type: "client_credentials",
502
+ client_id: auth.clientId,
503
+ client_secret: auth.clientSecret
504
+ });
505
+ if (auth.scope) params.set("scope", auth.scope);
506
+ const tokenRes = await fetch(auth.tokenUrl, {
507
+ method: "POST",
508
+ headers: { "Content-Type": "application/x-www-form-urlencoded" },
509
+ body: params.toString()
510
+ });
511
+ if (!tokenRes.ok) {
512
+ throw new Error(`OAuth2 token request failed: ${tokenRes.status} ${tokenRes.statusText}`);
513
+ }
514
+ const json = await tokenRes.json();
515
+ if (!json.access_token) throw new Error("OAuth2 response did not include access_token");
516
+ const tokenType = json.token_type ?? "Bearer";
517
+ return `${tokenType} ${json.access_token}`;
518
+ }
519
+ var httpRequestSkill = {
520
+ name: "http-request",
521
+ description: "Make an authenticated HTTP request to any URL. Supports Bearer token, HTTP Basic, and OAuth2 client-credentials flows. Returns status, headers and body. Use this to call external REST APIs from an agent.",
522
+ async execute(input5) {
523
+ const { url, method, headers: extraHeaders, body, auth, timeoutMs } = InputSchema9.parse(input5);
524
+ const headers = {
525
+ Accept: "application/json",
526
+ ...extraHeaders
527
+ };
528
+ if (auth) {
529
+ headers.Authorization = await resolveToken(auth);
530
+ }
531
+ let bodyStr;
532
+ if (body !== void 0) {
533
+ if (typeof body === "string") {
534
+ bodyStr = body;
535
+ } else {
536
+ bodyStr = JSON.stringify(body);
537
+ headers["Content-Type"] ??= "application/json";
538
+ }
539
+ }
540
+ const controller = new AbortController();
541
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
542
+ let response;
543
+ try {
544
+ response = await fetch(url, {
545
+ method,
546
+ headers,
547
+ body: bodyStr,
548
+ signal: controller.signal
549
+ });
550
+ } finally {
551
+ clearTimeout(timer);
552
+ }
553
+ const responseHeaders = {};
554
+ response.headers.forEach((value, key) => {
555
+ responseHeaders[key] = value;
556
+ });
557
+ const responseBody = await response.text();
558
+ return {
559
+ status: response.status,
560
+ statusText: response.statusText,
561
+ headers: responseHeaders,
562
+ body: responseBody,
563
+ ok: response.ok
564
+ };
565
+ }
566
+ };
567
+
568
+ // ../skills/src/multimodal/analyze-image.ts
569
+ import { readFile as readFile3 } from "fs/promises";
570
+ import { extname } from "path";
571
+ import { z as z10 } from "zod";
572
+ var SUPPORTED_MIME = ["image/jpeg", "image/png", "image/gif", "image/webp"];
573
+ var InputSchema10 = z10.object({
574
+ prompt: z10.string().describe("Question or instruction for the vision model, e.g. 'Describe this screenshot'"),
575
+ imagePath: z10.string().optional().describe("Absolute or relative path to an image file on disk"),
576
+ imageUrl: z10.string().url().optional().describe("Public URL of an image to fetch and analyse"),
577
+ imageBase64: z10.string().optional().describe("Base64-encoded image data (without data-URL prefix)"),
578
+ mimeType: z10.enum(SUPPORTED_MIME).optional().describe("MIME type \u2014 required when using imageBase64, inferred from extension otherwise")
579
+ }).refine((d) => d.imagePath ?? d.imageUrl ?? d.imageBase64, {
580
+ message: "Provide exactly one of: imagePath, imageUrl, or imageBase64"
581
+ });
582
+ var EXT_TO_MIME = {
583
+ ".jpg": "image/jpeg",
584
+ ".jpeg": "image/jpeg",
585
+ ".png": "image/png",
586
+ ".gif": "image/gif",
587
+ ".webp": "image/webp"
588
+ };
589
+ async function loadImage(input5) {
590
+ if (input5.imagePath) {
591
+ const ext = extname(input5.imagePath).toLowerCase();
592
+ const mimeType2 = input5.mimeType ?? EXT_TO_MIME[ext];
593
+ if (!mimeType2) throw new Error(`Unsupported image extension "${ext}". Use: ${Object.keys(EXT_TO_MIME).join(", ")}`);
594
+ const data = (await readFile3(input5.imagePath)).toString("base64");
595
+ return { data, mimeType: mimeType2 };
596
+ }
597
+ if (input5.imageUrl) {
598
+ const res = await fetch(input5.imageUrl);
599
+ if (!res.ok) throw new Error(`Failed to fetch image: ${res.status} ${res.statusText}`);
600
+ const contentType = res.headers.get("content-type") ?? "";
601
+ const mimeType2 = input5.mimeType ?? SUPPORTED_MIME.find((m) => contentType.startsWith(m));
602
+ if (!mimeType2) throw new Error(`Unsupported content-type "${contentType}"`);
603
+ const buffer = await res.arrayBuffer();
604
+ return { data: Buffer.from(buffer).toString("base64"), mimeType: mimeType2 };
605
+ }
606
+ const mimeType = input5.mimeType;
607
+ if (!mimeType) throw new Error("mimeType is required when supplying imageBase64");
608
+ return { data: input5.imageBase64, mimeType };
609
+ }
610
+ var analyzeImageSkill = {
611
+ name: "analyze-image",
612
+ description: "Analyse an image (screenshot, diagram, mockup, photo) using the configured vision-capable LLM provider. Accepts a file path, public URL, or raw base64 data. Returns a natural-language description or answer to your prompt.",
613
+ async execute(input5, ctx) {
614
+ const parsed = InputSchema10.parse(input5);
615
+ if (!ctx.provider.capabilities.vision) {
616
+ throw new Error(
617
+ `Provider "${ctx.provider.name}" does not support vision. Configure a vision-capable provider (e.g. Anthropic claude-3, OpenAI gpt-4o).`
618
+ );
619
+ }
620
+ const { data, mimeType } = await loadImage(parsed);
621
+ const imagePart = { type: "image", mimeType, data };
622
+ const response = await ctx.provider.complete({
623
+ messages: [
624
+ {
625
+ role: "user",
626
+ content: [imagePart, { type: "text", text: parsed.prompt }]
627
+ }
628
+ ],
629
+ temperature: 0.2
630
+ });
631
+ return { analysis: response.content };
632
+ }
633
+ };
634
+
635
+ // ../skills/src/ux/audit-accessibility.ts
636
+ import { readdir as readdir3, readFile as readFile4 } from "fs/promises";
637
+ import { join as join3 } from "path";
638
+ import { z as z11 } from "zod";
639
+ var InputSchema11 = z11.object({
640
+ path: z11.string().describe("File path (.tsx/.ts) or directory to audit recursively"),
641
+ recursive: z11.boolean().default(false).describe("Scan all .tsx files in the directory recursively")
642
+ });
643
+ var RULES = [
644
+ {
645
+ id: "img-missing-alt",
646
+ severity: "critical",
647
+ description: "<img> without alt attribute \u2014 screen readers will read the file name",
648
+ suggestion: 'Add alt="" for decorative images or alt="descriptive text" for informative ones',
649
+ pattern: /<img(?![^>]*\balt=)[^>]*/
650
+ },
651
+ {
652
+ id: "onclick-non-interactive",
653
+ severity: "critical",
654
+ description: "onClick on a non-interactive element (div/span/p) without role or tabIndex",
655
+ suggestion: 'Use <button> instead, or add role="button" tabIndex={0} onKeyDown handler',
656
+ pattern: /<(div|span|p|section|article)\s[^>]*onClick/,
657
+ isIssue: (_match, line) => !/role=["']button["']/.test(line) && !/tabIndex/.test(line)
658
+ },
659
+ {
660
+ id: "link-blank-no-rel",
661
+ severity: "moderate",
662
+ description: 'target="_blank" without rel="noopener noreferrer" \u2014 security risk and unexpected UX',
663
+ suggestion: 'Add rel="noopener noreferrer" to all target="_blank" links',
664
+ pattern: /target=["']_blank["']/,
665
+ isIssue: (_match, line) => !/rel=/.test(line)
666
+ },
667
+ {
668
+ id: "hardcoded-color",
669
+ severity: "moderate",
670
+ description: "Hardcoded Tailwind color scale class \u2014 will break dark mode and theme consistency",
671
+ suggestion: "Replace with shadcn/ui semantic tokens: bg-background, text-foreground, bg-muted, text-muted-foreground, border-border",
672
+ pattern: /className=[^>]*["'][^"']*\b(bg|text|border|ring|outline|fill|stroke)-(white|black|gray|zinc|slate|stone|neutral|red|orange|amber|yellow|lime|green|emerald|teal|cyan|sky|blue|indigo|violet|purple|fuchsia|pink|rose)-\d+[^"']*["']/
673
+ },
674
+ {
675
+ id: "icon-button-no-label",
676
+ severity: "critical",
677
+ description: "Button containing only an icon without aria-label \u2014 invisible to screen readers",
678
+ suggestion: 'Add aria-label="Descriptive action name" to the button',
679
+ pattern: /<button(?![^>]*aria-label)[^>]*>\s*<[A-Z][a-zA-Z]+\s*(?:size|className|strokeWidth)[^/]*\/>\s*<\/button>/
680
+ },
681
+ {
682
+ id: "input-missing-label",
683
+ severity: "critical",
684
+ description: "<input> or <Input> without aria-label, aria-labelledby, or associated <label>",
685
+ suggestion: "Wrap with Form.Item + Form.Label, or add aria-label directly to the input",
686
+ pattern: /<(?:input|Input)\s(?![^>]*(?:aria-label|aria-labelledby|id=))[^>]*/,
687
+ isIssue: (_match, _line, allLines, lineIndex) => {
688
+ const context = allLines.slice(Math.max(0, lineIndex - 5), lineIndex + 5).join("\n");
689
+ return !/<label|Form\.Label|htmlFor/.test(context);
690
+ }
691
+ },
692
+ {
693
+ id: "autofocus",
694
+ severity: "low",
695
+ description: "autoFocus used \u2014 can disorient screen reader users and break keyboard flow",
696
+ suggestion: "Manage focus programmatically with useEffect + ref.focus() only when truly needed (e.g. after modal opens)",
697
+ pattern: /\bautoFocus\b/
698
+ },
699
+ {
700
+ id: "inline-style",
701
+ severity: "low",
702
+ description: "Inline style attribute \u2014 bypasses theme system and cannot adapt to dark mode",
703
+ suggestion: "Replace with Tailwind utility classes or shadcn/ui semantic tokens",
704
+ pattern: /\bstyle=\{/
705
+ },
706
+ {
707
+ id: "no-focus-visible",
708
+ severity: "moderate",
709
+ description: "Interactive element may be missing focus-visible styles",
710
+ suggestion: "Ensure focus-visible:ring-2 focus-visible:ring-ring is present on all focusable elements",
711
+ pattern: /<(button|a|input|select|textarea|Input|Button|Link)[^>]*(?:className|class)=["'][^"']*["'][^>]*>/,
712
+ isIssue: (_match, line) => !/focus-visible/.test(line) && !/focus:ring/.test(line)
713
+ }
714
+ ];
715
+ async function auditFile(filePath) {
716
+ const content = await readFile4(filePath, "utf-8");
717
+ const lines = content.split("\n");
718
+ const issues = [];
719
+ for (let i = 0; i < lines.length; i++) {
720
+ const line = lines[i];
721
+ for (const rule of RULES) {
722
+ rule.pattern.lastIndex = 0;
723
+ const match = rule.pattern.exec(line);
724
+ if (!match) continue;
725
+ const isIssue = rule.isIssue ? rule.isIssue(match, line, lines, i) : true;
726
+ if (!isIssue) continue;
727
+ issues.push({
728
+ file: filePath,
729
+ line: i + 1,
730
+ severity: rule.severity,
731
+ rule: rule.id,
732
+ description: rule.description,
733
+ suggestion: rule.suggestion,
734
+ snippet: line.trim().slice(0, 120)
735
+ });
736
+ }
737
+ }
738
+ return issues;
739
+ }
740
+ var MAX_DEPTH = 10;
741
+ var MAX_FILES = 500;
742
+ async function collectFiles(dir, recursive, depth = 0, results = []) {
743
+ if (depth > MAX_DEPTH || results.length >= MAX_FILES) return results;
744
+ const entries = await readdir3(dir, { withFileTypes: true });
745
+ for (const entry of entries) {
746
+ if (results.length >= MAX_FILES) break;
747
+ if (entry.name === "node_modules" || entry.name === "dist") continue;
748
+ const full = join3(dir, entry.name);
749
+ if (entry.isDirectory() && recursive) {
750
+ await collectFiles(full, recursive, depth + 1, results);
751
+ } else if (entry.isFile() && (entry.name.endsWith(".tsx") || entry.name.endsWith(".ts"))) {
752
+ results.push(full);
753
+ }
754
+ }
755
+ return results;
756
+ }
757
+ var auditAccessibilitySkill = {
758
+ name: "audit-accessibility",
759
+ description: "Heuristic static scan of React TSX files for accessibility and dark-mode issues. Detects: missing alt, onClick on non-interactive elements, icon-only buttons without aria-label, inputs without labels, target=_blank without rel, hardcoded colors, missing focus-visible styles, autoFocus misuse, inline styles.",
760
+ async execute(input5) {
761
+ const { path, recursive } = InputSchema11.parse(input5);
762
+ let files;
763
+ try {
764
+ const entries = await readdir3(path);
765
+ files = await collectFiles(path, recursive ?? false);
766
+ void entries;
767
+ } catch {
768
+ files = [path];
769
+ }
770
+ const allIssues = [];
771
+ for (const file of files) {
772
+ allIssues.push(...await auditFile(file));
773
+ }
774
+ const critical = allIssues.filter((i) => i.severity === "critical").length;
775
+ const moderate = allIssues.filter((i) => i.severity === "moderate").length;
776
+ const low = allIssues.filter((i) => i.severity === "low").length;
777
+ return {
778
+ totalFiles: files.length,
779
+ totalIssues: allIssues.length,
780
+ critical,
781
+ moderate,
782
+ low,
783
+ issues: allIssues
784
+ };
785
+ }
786
+ };
787
+
788
+ // ../cli/src/commands/chain.ts
789
+ import chalk2 from "chalk";
790
+
791
+ // ../cli/src/utils/config-path.ts
792
+ import { dirname as dirname2, resolve as resolve8 } from "path";
793
+ import { fileURLToPath } from "url";
794
+ function resolveConfigPath() {
795
+ const __dirname3 = dirname2(fileURLToPath(import.meta.url));
796
+ const omniRoot = resolve8(__dirname3, "..", "..", "..", "..");
797
+ return resolve8(omniRoot, "config", "omni-ai.yaml");
798
+ }
799
+
800
+ // ../cli/src/utils/format.ts
801
+ import chalk from "chalk";
802
+ function agentHeader(name, providerModel) {
803
+ return chalk.bold.cyan(`\u25C6 ${name}`) + chalk.gray(` [${providerModel}]`);
804
+ }
805
+ function tokenSummary(inputTokens, outputTokens) {
806
+ const cost = ((inputTokens * 3 + outputTokens * 15) / 1e6).toFixed(4);
807
+ return chalk.gray(
808
+ `Tokens: ${inputTokens.toLocaleString("en-US")} entrada \xB7 ${outputTokens.toLocaleString("en-US")} sa\xEDda \xB7 ~$${cost}`
809
+ );
810
+ }
811
+ function iterationLine(n) {
812
+ return chalk.gray(` ... (${n} itera\xE7${n === 1 ? "\xE3o" : "\xF5es"})`);
813
+ }
814
+ function errorLine(message) {
815
+ return chalk.red(`\u2716 ${message}`);
816
+ }
817
+ function savedLine(path) {
818
+ return chalk.green(`Output salvo em: ${path}`);
819
+ }
820
+
821
+ // ../cli/src/commands/chain.ts
822
+ async function chainCommand(prompt, agents, opts) {
823
+ if (agents.length < 2) {
824
+ console.error(errorLine("omni chain requires at least 2 agents"));
825
+ process.exit(1);
826
+ }
827
+ const configPath = opts.config ?? resolveConfigPath();
828
+ const skills2 = [
829
+ readFileSkill,
830
+ writeFileSkill,
831
+ listDirectorySkill,
832
+ searchCodeSkill,
833
+ auditAccessibilitySkill,
834
+ gitStatusSkill,
835
+ gitDiffSkill,
836
+ gitLogSkill,
837
+ gitCommitMessageSkill,
838
+ httpRequestSkill,
839
+ analyzeImageSkill
840
+ ];
841
+ const runtime = await createRuntime({ configPath, skills: skills2 }).catch((err) => {
842
+ console.error(errorLine(`Failed to load config: ${err instanceof Error ? err.message : String(err)}`));
843
+ process.exit(1);
844
+ });
845
+ const providerLabel = `${runtime.config.defaultProvider} / ${runtime.config.providers.find((p) => p.name === runtime.config.defaultProvider)?.defaultModel ?? "default"}`;
846
+ const onToken = opts.stream ? (chunk) => process.stdout.write(chunk) : void 0;
847
+ let currentInput = prompt;
848
+ let totalInput = 0;
849
+ let totalOutput = 0;
850
+ let lastOutput = "";
851
+ console.log();
852
+ for (let i = 0; i < agents.length; i++) {
853
+ const agentName = agents[i];
854
+ console.log(agentHeader(agentName, providerLabel));
855
+ if (opts.stream) process.stdout.write("\n");
856
+ const result = await runtime.run(agentName, currentInput, { onToken }).catch((err) => {
857
+ console.error(errorLine(err instanceof Error ? err.message : String(err)));
858
+ process.exit(1);
859
+ });
860
+ if (opts.stream) {
861
+ process.stdout.write("\n");
862
+ } else if (opts.verbose) {
863
+ console.log(`
864
+ ${result.output}
865
+ `);
866
+ }
867
+ console.log(iterationLine(result.iterations));
868
+ if (result.usage) {
869
+ totalInput += result.usage.inputTokens;
870
+ totalOutput += result.usage.outputTokens;
871
+ }
872
+ lastOutput = result.output;
873
+ currentInput = result.output;
874
+ if (i < agents.length - 1) {
875
+ console.log(`${chalk2.gray(`
876
+ \u2193 sa\xEDda passada para: `) + chalk2.cyan(agents[i + 1])}
877
+ `);
878
+ }
879
+ }
880
+ if (!opts.stream && !opts.verbose) {
881
+ console.log(`
882
+ ${lastOutput}
883
+ `);
884
+ }
885
+ if (opts.output) {
886
+ await writeFile2(opts.output, lastOutput, "utf-8");
887
+ console.log(savedLine(opts.output));
888
+ }
889
+ if (totalInput > 0 || totalOutput > 0) {
890
+ console.log(tokenSummary(totalInput, totalOutput));
891
+ }
892
+ console.log();
893
+ }
894
+
895
+ // ../cli/src/commands/eval.ts
896
+ import { readFile as readFile5, writeFile as writeFile3 } from "fs/promises";
897
+ import chalk3 from "chalk";
898
+ function normalize(s) {
899
+ return s.trim().toLowerCase().replace(/\s+/g, " ");
900
+ }
901
+ function matchCase(expected, actual) {
902
+ const normExpected = normalize(expected);
903
+ const normActual = normalize(actual);
904
+ if (normActual === normExpected) return "exact";
905
+ if (normActual.includes(normExpected)) return "contains";
906
+ return "miss";
907
+ }
908
+ function buildReport(agent, results) {
909
+ const passed = results.filter((r) => r.match !== "miss").length;
910
+ return {
911
+ agent,
912
+ total: results.length,
913
+ passed,
914
+ score: results.length > 0 ? passed / results.length : 0,
915
+ cases: results
916
+ };
917
+ }
918
+ async function runBatch(cases, runFn, concurrency, onResult) {
919
+ const results = [];
920
+ let index = 0;
921
+ while (index < cases.length) {
922
+ const batch = cases.slice(index, index + concurrency);
923
+ const batchResults = await Promise.all(batch.map(runFn));
924
+ for (const r of batchResults) {
925
+ results.push(r);
926
+ onResult(r, results.length, cases.length);
927
+ }
928
+ index += concurrency;
929
+ }
930
+ return results;
931
+ }
932
+ async function evalCommand(agent, datasetPath, opts) {
933
+ const configPath = opts.config ?? resolveConfigPath();
934
+ const concurrency = opts.concurrency ? Number.parseInt(opts.concurrency, 10) : 3;
935
+ let cases;
936
+ try {
937
+ const raw = await readFile5(datasetPath, "utf-8");
938
+ cases = JSON.parse(raw);
939
+ } catch (err) {
940
+ console.error(errorLine(`Failed to read dataset: ${err instanceof Error ? err.message : String(err)}`));
941
+ process.exit(1);
942
+ }
943
+ if (!Array.isArray(cases) || cases.length === 0) {
944
+ console.error(errorLine("Dataset must be a non-empty JSON array of { input, expected } objects."));
945
+ process.exit(1);
946
+ }
947
+ const skills2 = [readFileSkill, writeFileSkill, listDirectorySkill, searchCodeSkill];
948
+ const runtime = await createRuntime({ configPath, skills: skills2 }).catch((err) => {
949
+ console.error(errorLine(`Failed to load config: ${err instanceof Error ? err.message : String(err)}`));
950
+ process.exit(1);
951
+ });
952
+ const providerLabel = `${runtime.config.defaultProvider} / ${runtime.config.providers.find((p) => p.name === runtime.config.defaultProvider)?.defaultModel ?? "default"}`;
953
+ console.log(`
954
+ ${agentHeader(agent, providerLabel)}`);
955
+ console.log(chalk3.gray(` Dataset: ${datasetPath} (${cases.length} cases, concurrency ${concurrency})
956
+ `));
957
+ const matchSymbol = (m) => {
958
+ if (m === "exact") return chalk3.green("\u2713");
959
+ if (m === "contains") return chalk3.yellow("~");
960
+ return chalk3.red("\u2716");
961
+ };
962
+ const results = await runBatch(
963
+ cases,
964
+ async (c) => {
965
+ const result = await runtime.run(agent, c.input).catch((err) => ({
966
+ output: `ERROR: ${err instanceof Error ? err.message : String(err)}`,
967
+ iterations: 0
968
+ }));
969
+ const match = matchCase(c.expected, result.output);
970
+ return { input: c.input, expected: c.expected, actual: result.output, match };
971
+ },
972
+ concurrency,
973
+ (r, done, total) => {
974
+ console.log(` ${matchSymbol(r.match)} ${String(done).padStart(String(total).length)}/${total} [${r.match}]`);
975
+ }
976
+ );
977
+ const report = buildReport(agent, results);
978
+ const scoreColor = report.score >= 0.8 ? chalk3.green : report.score >= 0.5 ? chalk3.yellow : chalk3.red;
979
+ const misses = report.total - report.passed;
980
+ console.log(
981
+ `
982
+ ${chalk3.bold("Resultado:")} ${scoreColor(`${report.passed}/${report.total} (${Math.round(report.score * 100)}%)`)} \xB7 ${misses === 0 ? chalk3.green("0 erros") : chalk3.red(`${misses} erros`)}
983
+ `
984
+ );
985
+ if (opts.output) {
986
+ await writeFile3(opts.output, JSON.stringify(report, null, 2), "utf-8");
987
+ console.log(chalk3.green(`Report saved to: ${opts.output}`));
988
+ }
989
+ }
990
+
991
+ // ../cli/src/commands/export.ts
992
+ import { writeFile as writeFile4 } from "fs/promises";
993
+
994
+ // ../memory/src/stores/sqlite.ts
995
+ import Database from "better-sqlite3";
996
+ var SQLiteMemoryStore = class {
997
+ db;
998
+ defaultLimit;
999
+ constructor(options = {}) {
1000
+ const path = options.path ?? "./omni-ai-memory.db";
1001
+ this.defaultLimit = options.defaultLimit ?? 100;
1002
+ this.db = new Database(path);
1003
+ this.db.pragma("journal_mode = WAL");
1004
+ this.migrate();
1005
+ }
1006
+ migrate() {
1007
+ this.db.exec(`
1008
+ CREATE TABLE IF NOT EXISTS messages (
1009
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
1010
+ resource_id TEXT NOT NULL,
1011
+ thread_id TEXT NOT NULL,
1012
+ role TEXT NOT NULL,
1013
+ content TEXT NOT NULL,
1014
+ timestamp INTEGER NOT NULL
1015
+ );
1016
+ CREATE INDEX IF NOT EXISTS idx_messages_session
1017
+ ON messages (resource_id, thread_id, id);
1018
+
1019
+ CREATE VIRTUAL TABLE IF NOT EXISTS messages_fts USING fts5(
1020
+ content,
1021
+ content='messages',
1022
+ content_rowid='id'
1023
+ );
1024
+
1025
+ CREATE TRIGGER IF NOT EXISTS messages_ai AFTER INSERT ON messages BEGIN
1026
+ INSERT INTO messages_fts (rowid, content) VALUES (new.id, new.content);
1027
+ END;
1028
+
1029
+ CREATE TABLE IF NOT EXISTS working_memory (
1030
+ resource_id TEXT NOT NULL,
1031
+ thread_id TEXT NOT NULL,
1032
+ content TEXT NOT NULL,
1033
+ updated_at INTEGER NOT NULL,
1034
+ PRIMARY KEY (resource_id, thread_id)
1035
+ );
1036
+ `);
1037
+ }
1038
+ async saveMessages(session, messages) {
1039
+ const insert = this.db.prepare(
1040
+ `INSERT INTO messages (resource_id, thread_id, role, content, timestamp)
1041
+ VALUES (@resourceId, @threadId, @role, @content, @timestamp)`
1042
+ );
1043
+ const insertMany = this.db.transaction((entries) => {
1044
+ for (const e of entries) {
1045
+ insert.run({ ...session, role: e.role, content: e.content, timestamp: e.timestamp });
1046
+ }
1047
+ });
1048
+ insertMany(messages);
1049
+ }
1050
+ async loadMessages(session, limit) {
1051
+ const n = limit ?? this.defaultLimit;
1052
+ const rows = this.db.prepare(
1053
+ `SELECT role, content, timestamp FROM (
1054
+ SELECT role, content, timestamp FROM messages
1055
+ WHERE resource_id = ? AND thread_id = ?
1056
+ ORDER BY id DESC LIMIT ?
1057
+ ) ORDER BY timestamp ASC`
1058
+ ).all(session.resourceId, session.threadId, n);
1059
+ return rows;
1060
+ }
1061
+ async search(session, query, topK = 5) {
1062
+ const rows = this.db.prepare(
1063
+ `SELECT m.content, rank AS score
1064
+ FROM messages_fts fts
1065
+ JOIN messages m ON m.id = fts.rowid
1066
+ WHERE fts.content MATCH ?
1067
+ AND m.resource_id = ? AND m.thread_id = ?
1068
+ ORDER BY rank LIMIT ?`
1069
+ ).all(query, session.resourceId, session.threadId, topK);
1070
+ return rows.map((r) => ({ content: r.content, score: r.score }));
1071
+ }
1072
+ async getWorkingMemory(session) {
1073
+ const row = this.db.prepare(`SELECT content FROM working_memory WHERE resource_id = ? AND thread_id = ?`).get(session.resourceId, session.threadId);
1074
+ return row?.content ?? null;
1075
+ }
1076
+ async setWorkingMemory(session, content) {
1077
+ this.db.prepare(
1078
+ `INSERT INTO working_memory (resource_id, thread_id, content, updated_at)
1079
+ VALUES (?, ?, ?, ?)
1080
+ ON CONFLICT (resource_id, thread_id) DO UPDATE SET content = excluded.content, updated_at = excluded.updated_at`
1081
+ ).run(session.resourceId, session.threadId, content, Date.now());
1082
+ }
1083
+ async close() {
1084
+ this.db.close();
1085
+ }
1086
+ };
1087
+
1088
+ // ../cli/src/commands/export.ts
1089
+ import chalk4 from "chalk";
1090
+ function getDbPath() {
1091
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
1092
+ return `${home}/.omni-ai/sessions.db`;
1093
+ }
1094
+ function formatAsMarkdown(session, messages) {
1095
+ const lines = [
1096
+ `# Session: ${session.resourceId} / ${session.threadId}`,
1097
+ `_Exported: ${(/* @__PURE__ */ new Date()).toISOString()}_`,
1098
+ ""
1099
+ ];
1100
+ for (const msg of messages) {
1101
+ const ts = new Date(msg.timestamp).toISOString();
1102
+ const label = msg.role === "user" ? chalk4.cyan("**user**") : chalk4.yellow("**assistant**");
1103
+ lines.push(`### ${label} \u2014 ${ts}`);
1104
+ lines.push("");
1105
+ lines.push(msg.content);
1106
+ lines.push("");
1107
+ }
1108
+ return lines.join("\n");
1109
+ }
1110
+ function formatAsJson(session, messages) {
1111
+ return JSON.stringify({ session, messages }, null, 2);
1112
+ }
1113
+ async function exportCommand(sessionArg, opts) {
1114
+ const [resourceId, ...rest] = sessionArg.split(":");
1115
+ const threadId = rest.join(":") || "default";
1116
+ const session = { resourceId, threadId };
1117
+ const limit = opts.limit ? Number.parseInt(opts.limit, 10) : void 0;
1118
+ const format = opts.format ?? "markdown";
1119
+ const store = new SQLiteMemoryStore({ path: getDbPath() });
1120
+ let messages;
1121
+ try {
1122
+ messages = await store.loadMessages(session, limit);
1123
+ } finally {
1124
+ await store.close?.();
1125
+ }
1126
+ if (messages.length === 0) {
1127
+ console.log(chalk4.gray(`No messages found for session "${sessionArg}".`));
1128
+ return;
1129
+ }
1130
+ const output = format === "json" ? formatAsJson(session, messages) : formatAsMarkdown(session, messages);
1131
+ if (opts.output) {
1132
+ await writeFile4(opts.output, output, "utf-8");
1133
+ console.log(chalk4.green(`Session exported to: ${opts.output}`));
1134
+ } else {
1135
+ console.log(output);
1136
+ }
1137
+ console.log(chalk4.gray(`
1138
+ ${messages.length} message(s) exported.`));
1139
+ }
1140
+
1141
+ // ../cli/src/commands/init.ts
1142
+ import { access, readFile as readFile6, writeFile as writeFile5 } from "fs/promises";
1143
+ import { dirname as dirname3, resolve as resolve9 } from "path";
1144
+ import { fileURLToPath as fileURLToPath2 } from "url";
1145
+ import { checkbox, confirm, input, password, select } from "@inquirer/prompts";
1146
+ import chalk5 from "chalk";
1147
+ var __dirname = dirname3(fileURLToPath2(import.meta.url));
1148
+ var OMNI_ROOT = resolve9(__dirname, "..", "..", "..", "..");
1149
+ var CONFIG_PATH = resolve9(OMNI_ROOT, "config", "omni-ai.yaml");
1150
+ var ENV_PATH = resolve9(OMNI_ROOT, ".env");
1151
+ async function fileExists(path) {
1152
+ try {
1153
+ await access(path);
1154
+ return true;
1155
+ } catch {
1156
+ return false;
1157
+ }
1158
+ }
1159
+ async function readEnvFile(path) {
1160
+ const map = /* @__PURE__ */ new Map();
1161
+ try {
1162
+ const content = await readFile6(path, "utf-8");
1163
+ for (const line of content.split("\n")) {
1164
+ const trimmed = line.trim();
1165
+ if (!trimmed || trimmed.startsWith("#")) continue;
1166
+ const eq = trimmed.indexOf("=");
1167
+ if (eq === -1) continue;
1168
+ map.set(trimmed.slice(0, eq).trim(), trimmed.slice(eq + 1).trim());
1169
+ }
1170
+ } catch {
1171
+ }
1172
+ return map;
1173
+ }
1174
+ function buildEnvFile(entries, template) {
1175
+ const lines = template.split("\n");
1176
+ const written = /* @__PURE__ */ new Set();
1177
+ const result = [];
1178
+ for (const line of lines) {
1179
+ const trimmed = line.trim();
1180
+ if (trimmed && !trimmed.startsWith("#")) {
1181
+ const eq = trimmed.indexOf("=");
1182
+ if (eq !== -1) {
1183
+ const key = trimmed.slice(0, eq).trim();
1184
+ const value = entries.get(key) ?? "";
1185
+ result.push(`${key}=${value}`);
1186
+ written.add(key);
1187
+ continue;
1188
+ }
1189
+ }
1190
+ result.push(line);
1191
+ }
1192
+ for (const [key, value] of entries) {
1193
+ if (!written.has(key)) {
1194
+ result.push(`${key}=${value}`);
1195
+ }
1196
+ }
1197
+ return result.join("\n");
1198
+ }
1199
+ function buildYaml(primary, extras) {
1200
+ const allProviders = [primary, ...extras];
1201
+ const providerBlocks = allProviders.map((p) => {
1202
+ const lines = [` - name: ${p.name}`, ` type: ${p.type}`, ` apiKey: \${${p.envKey}}`];
1203
+ if (p.baseUrl) lines.push(` baseUrl: ${p.baseUrl}`);
1204
+ lines.push(` defaultModel: ${p.defaultModel}`);
1205
+ return lines.join("\n");
1206
+ }).join("\n\n");
1207
+ return `version: "1"
1208
+
1209
+ # Provider padr\xE3o \u2014 todos os agentes herdam este, a menos que declarem o pr\xF3prio
1210
+ defaultProvider: ${primary.name}
1211
+
1212
+ # Diret\xF3rio dos YAML de agentes (relativo \xE0 raiz do reposit\xF3rio)
1213
+ agentsDir: agents
1214
+
1215
+ providers:
1216
+ ${providerBlocks}
1217
+
1218
+ # \u2500\u2500 Heran\xE7a de provider/modelo \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1219
+ # agent.provider ausente \u2192 usa defaultProvider acima
1220
+ # agent.model ausente \u2192 usa defaultModel do provider resolvido
1221
+ # agent.provider presente \u2192 sobrescreve s\xF3 para aquele agente
1222
+ # agent.model presente \u2192 sobrescreve o modelo s\xF3 para aquele agente
1223
+ # \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
1224
+
1225
+ agents:
1226
+ # Agente inline de exemplo \u2014 n\xE3o precisa de arquivo YAML separado
1227
+ - name: code-reviewer
1228
+ description: Revisa c\xF3digo para qualidade, bugs e problemas de seguran\xE7a
1229
+ systemPrompt: |
1230
+ Voc\xEA \xE9 um revisor de c\xF3digo especialista. Analise o c\xF3digo fornecido e
1231
+ d\xEA feedback acion\xE1vel sobre qualidade, poss\xEDveis bugs, vulnerabilidades
1232
+ de seguran\xE7a e problemas de performance. Seja preciso e objetivo.
1233
+ skills:
1234
+ - read-file
1235
+ - search-code
1236
+ maxIterations: 5
1237
+ temperature: 0.1
1238
+ `;
1239
+ }
1240
+ async function setupCopilot() {
1241
+ console.log();
1242
+ console.log(chalk5.dim(" Para obter seu token GitHub:"));
1243
+ console.log(chalk5.dim(" gh auth login # se ainda n\xE3o fez"));
1244
+ console.log(chalk5.dim(" gh auth token # mostra o token atual"));
1245
+ console.log();
1246
+ const token = await password({
1247
+ message: "Cole seu GitHub token (ghp_... ou github_pat_...):",
1248
+ validate: (v) => v.trim().length > 0 || "Token n\xE3o pode ser vazio"
1249
+ });
1250
+ const model = await select({
1251
+ message: "Modelo padr\xE3o:",
1252
+ choices: [
1253
+ { name: "gpt-4o (recomendado)", value: "gpt-4o" },
1254
+ { name: "gpt-4o-mini (r\xE1pido, custo baixo)", value: "gpt-4o-mini" },
1255
+ { name: "o1", value: "o1" }
1256
+ ]
1257
+ });
1258
+ return {
1259
+ name: "copilot",
1260
+ type: "copilot",
1261
+ envKey: "GITHUB_TOKEN",
1262
+ envValue: token.trim(),
1263
+ defaultModel: model,
1264
+ baseUrl: "https://api.githubcopilot.com"
1265
+ };
1266
+ }
1267
+ async function setupAnthropic() {
1268
+ console.log();
1269
+ console.log(chalk5.dim(" Como obter: https://console.anthropic.com \u2192 API Keys \u2192 Create key"));
1270
+ console.log(chalk5.dim(" Formato: sk-ant-api03-..."));
1271
+ console.log();
1272
+ const key = await password({
1273
+ message: "Cole sua Anthropic API key:",
1274
+ validate: (v) => v.trim().length > 0 || "API key n\xE3o pode ser vazia"
1275
+ });
1276
+ const model = await select({
1277
+ message: "Modelo padr\xE3o:",
1278
+ choices: [
1279
+ { name: "claude-sonnet-4-6 (recomendado \u2014 qualidade/velocidade balanceados)", value: "claude-sonnet-4-6" },
1280
+ { name: "claude-opus-4-7 (racioc\xEDnio complexo, contexto longo)", value: "claude-opus-4-7" },
1281
+ { name: "claude-haiku-4-5-20251001 (r\xE1pido, custo baixo)", value: "claude-haiku-4-5-20251001" }
1282
+ ]
1283
+ });
1284
+ return {
1285
+ name: "anthropic",
1286
+ type: "anthropic",
1287
+ envKey: "ANTHROPIC_API_KEY",
1288
+ envValue: key.trim(),
1289
+ defaultModel: model
1290
+ };
1291
+ }
1292
+ async function setupOpenAI() {
1293
+ console.log();
1294
+ console.log(chalk5.dim(" Como obter: https://platform.openai.com \u2192 API keys \u2192 Create new secret key"));
1295
+ console.log(chalk5.dim(" Formato: sk-proj-... (novo) ou sk-... (legado)"));
1296
+ console.log();
1297
+ const key = await password({
1298
+ message: "Cole sua OpenAI API key:",
1299
+ validate: (v) => v.trim().length > 0 || "API key n\xE3o pode ser vazia"
1300
+ });
1301
+ const model = await select({
1302
+ message: "Modelo padr\xE3o:",
1303
+ choices: [
1304
+ { name: "gpt-4o (recomendado \u2014 multimodal, racioc\xEDnio forte)", value: "gpt-4o" },
1305
+ { name: "gpt-4o-mini (r\xE1pido, custo eficiente)", value: "gpt-4o-mini" },
1306
+ { name: "o1", value: "o1" },
1307
+ { name: "o3-mini", value: "o3-mini" }
1308
+ ]
1309
+ });
1310
+ return {
1311
+ name: "openai",
1312
+ type: "openai",
1313
+ envKey: "OPENAI_API_KEY",
1314
+ envValue: key.trim(),
1315
+ defaultModel: model
1316
+ };
1317
+ }
1318
+ async function setupCustom() {
1319
+ console.log();
1320
+ console.log(chalk5.dim(" Compat\xEDvel com qualquer endpoint OpenAI-like:"));
1321
+ console.log(chalk5.dim(" Ollama: http://localhost:11434/v1"));
1322
+ console.log(chalk5.dim(" Groq: https://api.groq.com/openai/v1"));
1323
+ console.log(chalk5.dim(" Azure: https://<resource>.openai.azure.com"));
1324
+ console.log();
1325
+ const baseUrl = await input({
1326
+ message: "Base URL do endpoint:",
1327
+ default: "http://localhost:11434/v1",
1328
+ validate: (v) => v.trim().length > 0 || "URL n\xE3o pode ser vazia"
1329
+ });
1330
+ const key = await password({
1331
+ message: "API key (deixe vazio se n\xE3o for necess\xE1ria):"
1332
+ });
1333
+ const model = await input({
1334
+ message: "Modelo padr\xE3o:",
1335
+ default: "llama3.2",
1336
+ validate: (v) => v.trim().length > 0 || "Modelo n\xE3o pode ser vazio"
1337
+ });
1338
+ const envKey = "CUSTOM_LLM_API_KEY";
1339
+ const envBaseUrl = "CUSTOM_LLM_BASE_URL";
1340
+ return {
1341
+ name: "custom",
1342
+ type: "custom",
1343
+ envKey,
1344
+ envValue: key.trim(),
1345
+ defaultModel: model.trim(),
1346
+ baseUrl: `\${${envBaseUrl}}`,
1347
+ // we'll handle the base URL separately via a side-effect on envKey
1348
+ // store the real base URL in a special field
1349
+ ...baseUrl ? { _baseUrlValue: baseUrl } : {}
1350
+ };
1351
+ }
1352
+ async function selectProvider(label) {
1353
+ const choice = await select({
1354
+ message: label,
1355
+ choices: [
1356
+ { name: "GitHub Copilot (recomendado \u2014 usa seu token GitHub)", value: "copilot" },
1357
+ { name: "Anthropic (Claude)", value: "anthropic" },
1358
+ { name: "OpenAI (GPT)", value: "openai" },
1359
+ { name: "Custom / self-hosted (Ollama, Groq, Azure...)", value: "custom" }
1360
+ ]
1361
+ });
1362
+ switch (choice) {
1363
+ case "copilot":
1364
+ return setupCopilot();
1365
+ case "anthropic":
1366
+ return setupAnthropic();
1367
+ case "openai":
1368
+ return setupOpenAI();
1369
+ default:
1370
+ return setupCustom();
1371
+ }
1372
+ }
1373
+ async function initCommand() {
1374
+ console.log();
1375
+ console.log(chalk5.bold(" omni-ai \u2014 configura\xE7\xE3o inicial"));
1376
+ console.log(chalk5.dim(" Isso vai criar config/omni-ai.yaml e atualizar o .env"));
1377
+ console.log();
1378
+ if (await fileExists(CONFIG_PATH)) {
1379
+ const overwrite = await confirm({
1380
+ message: chalk5.yellow("config/omni-ai.yaml j\xE1 existe. Sobrescrever?"),
1381
+ default: false
1382
+ });
1383
+ if (!overwrite) {
1384
+ console.log(chalk5.dim("\n Opera\xE7\xE3o cancelada.\n"));
1385
+ return;
1386
+ }
1387
+ }
1388
+ const primary = await selectProvider("Qual provider de IA usar?");
1389
+ const addSecond = await confirm({
1390
+ message: "Adicionar um segundo provider (para usar em agentes espec\xEDficos)?",
1391
+ default: false
1392
+ });
1393
+ const extras = [];
1394
+ if (addSecond) {
1395
+ console.log();
1396
+ const second = await selectProvider("Segundo provider:");
1397
+ if (second.name !== primary.name) {
1398
+ extras.push(second);
1399
+ } else {
1400
+ console.log(chalk5.dim(" Mesmo provider \u2014 ignorado."));
1401
+ }
1402
+ }
1403
+ const agentSets = await checkbox({
1404
+ message: "Quais grupos de agentes voc\xEA vai usar? (informativo \u2014 todos j\xE1 est\xE3o dispon\xEDveis)",
1405
+ choices: [
1406
+ { name: "Backend \u2014 NestJS, DynamoDB, GraphQL (7 agentes)", value: "backend", checked: true },
1407
+ { name: "Frontend \u2014 React, TanStack Router, shadcn/ui (5 agentes)", value: "frontend", checked: true },
1408
+ { name: "UX \u2014 auditoria, forms, states, motion (5 agentes)", value: "ux", checked: true },
1409
+ { name: "QA \u2014 valida\xE7\xE3o e veredicto de merge (4 agentes)", value: "qa", checked: true }
1410
+ ]
1411
+ });
1412
+ const envEntries = await readEnvFile(ENV_PATH);
1413
+ envEntries.set(primary.envKey, primary.envValue);
1414
+ for (const extra of extras) {
1415
+ envEntries.set(extra.envKey, extra.envValue);
1416
+ }
1417
+ const allProviders = [primary, ...extras];
1418
+ for (const p of allProviders) {
1419
+ const custom = p;
1420
+ if (custom._baseUrlValue) {
1421
+ envEntries.set("CUSTOM_LLM_BASE_URL", custom._baseUrlValue);
1422
+ }
1423
+ }
1424
+ const envTemplatePath = resolve9(OMNI_ROOT, ".env.example");
1425
+ let envTemplate = "";
1426
+ try {
1427
+ envTemplate = await readFile6(envTemplatePath, "utf-8");
1428
+ } catch {
1429
+ envTemplate = Array.from(envEntries.entries()).map(([k, v]) => `${k}=${v}`).join("\n");
1430
+ }
1431
+ const envContent = buildEnvFile(envEntries, envTemplate);
1432
+ const yamlContent = buildYaml(primary, extras);
1433
+ await writeFile5(CONFIG_PATH, yamlContent, "utf-8");
1434
+ await writeFile5(ENV_PATH, envContent, "utf-8");
1435
+ console.log();
1436
+ console.log(`${chalk5.green(" \u2713")} config/omni-ai.yaml criado`);
1437
+ console.log(`${chalk5.green(" \u2713")} .env atualizado`);
1438
+ console.log();
1439
+ console.log(chalk5.bold(" Pr\xF3ximos passos:"));
1440
+ console.log();
1441
+ console.log(chalk5.cyan(" omni list agents") + chalk5.dim(" # ver todos os agentes dispon\xEDveis"));
1442
+ console.log(chalk5.cyan(" omni run code-reviewer") + chalk5.dim(' "revise src/app.ts"'));
1443
+ console.log();
1444
+ if (agentSets.includes("backend")) {
1445
+ console.log(chalk5.cyan(" omni run backend-dev") + chalk5.dim(' "crie o m\xF3dulo de pedidos"'));
1446
+ }
1447
+ if (agentSets.includes("frontend")) {
1448
+ console.log(chalk5.cyan(" omni run frontend-dev") + chalk5.dim(' "crie a p\xE1gina de listagem de pedidos"'));
1449
+ }
1450
+ if (agentSets.includes("ux")) {
1451
+ console.log(chalk5.cyan(" omni run ux-lead") + chalk5.dim(' "audite o componente OrderForm"'));
1452
+ }
1453
+ if (agentSets.includes("qa")) {
1454
+ console.log(chalk5.cyan(" omni run qa-lead") + chalk5.dim(' "valide src/orders/"'));
1455
+ }
1456
+ console.log();
1457
+ console.log(chalk5.dim(" Para usar de outro projeto:"));
1458
+ console.log(chalk5.dim(" cd /caminho/para/meu-projeto"));
1459
+ console.log(chalk5.cyan(" omni run backend-dev") + chalk5.dim(' "..."'));
1460
+ console.log();
1461
+ }
1462
+
1463
+ // ../cli/src/commands/list.ts
1464
+ import chalk6 from "chalk";
1465
+ async function listCommand(target, opts) {
1466
+ const configPath = opts.config ?? resolveConfigPath();
1467
+ if (target === "providers") {
1468
+ await import("../src-ZHTGR7T6.js");
1469
+ await import("../src-6MUVU5ML.js");
1470
+ const names = getRegisteredProviders();
1471
+ console.log(chalk6.bold("Registered providers:"));
1472
+ for (const name of names) {
1473
+ console.log(` ${chalk6.cyan("\xB7")} ${name}`);
1474
+ }
1475
+ return;
1476
+ }
1477
+ const runtime = await createRuntime({ configPath }).catch(() => null);
1478
+ if (target === "agents") {
1479
+ if (!runtime) {
1480
+ console.error(chalk6.red("Could not load config. Run from the omni-ai directory or pass --config."));
1481
+ process.exit(1);
1482
+ }
1483
+ const agents = await runtime.listAgents();
1484
+ if (agents.length === 0) {
1485
+ console.log(chalk6.gray("No agents found."));
1486
+ return;
1487
+ }
1488
+ console.log(chalk6.bold(`Agents (${agents.length}):
1489
+ `));
1490
+ for (const agent of agents) {
1491
+ console.log(` ${chalk6.cyan(agent.name.padEnd(30))} ${chalk6.gray(agent.description)}`);
1492
+ }
1493
+ return;
1494
+ }
1495
+ if (target === "skills") {
1496
+ if (!runtime) {
1497
+ console.error(chalk6.red("Could not load config."));
1498
+ process.exit(1);
1499
+ }
1500
+ const skills2 = runtime.skills.all();
1501
+ if (skills2.length === 0) {
1502
+ console.log(chalk6.gray("No skills registered."));
1503
+ return;
1504
+ }
1505
+ console.log(chalk6.bold(`Skills (${skills2.length}):
1506
+ `));
1507
+ for (const skill of skills2) {
1508
+ console.log(` ${chalk6.yellow(skill.name.padEnd(25))} ${chalk6.gray(skill.description)}`);
1509
+ }
1510
+ return;
1511
+ }
1512
+ console.error(chalk6.red(`Unknown list target: "${target}". Use: agents | skills | providers`));
1513
+ process.exit(1);
1514
+ }
1515
+
1516
+ // ../mcp/src/server.ts
1517
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
1518
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
1519
+ import { z as z12 } from "zod";
1520
+ var stubProvider = {
1521
+ name: "mcp-stub",
1522
+ capabilities: {},
1523
+ complete: () => {
1524
+ throw new Error("Provider not available in MCP server context");
1525
+ }
1526
+ };
1527
+ function createMcpServer(skills2, options) {
1528
+ const server = new McpServer({
1529
+ name: options?.name ?? "omni-ai",
1530
+ version: options?.version ?? "0.1.0"
1531
+ });
1532
+ const ctx = {
1533
+ provider: options?.ctx?.provider ?? stubProvider,
1534
+ config: options?.ctx?.config ?? {}
1535
+ };
1536
+ for (const skill of skills2) {
1537
+ server.registerTool(
1538
+ skill.name,
1539
+ { description: skill.description, inputSchema: z12.record(z12.unknown()) },
1540
+ async (args) => {
1541
+ try {
1542
+ const result = await skill.execute(args, ctx);
1543
+ const text = typeof result === "string" ? result : JSON.stringify(result);
1544
+ return { content: [{ type: "text", text }] };
1545
+ } catch (err) {
1546
+ return {
1547
+ content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }],
1548
+ isError: true
1549
+ };
1550
+ }
1551
+ }
1552
+ );
1553
+ }
1554
+ return server;
1555
+ }
1556
+ async function serveMcp(skills2, transport, options) {
1557
+ const server = createMcpServer(skills2, options);
1558
+ await server.connect(transport);
1559
+ return server;
1560
+ }
1561
+ async function serveStdioMcp(skills2, options) {
1562
+ const transport = new StdioServerTransport();
1563
+ return serveMcp(skills2, transport, options);
1564
+ }
1565
+
1566
+ // ../mcp/src/skill.ts
1567
+ import { Client } from "@modelcontextprotocol/sdk/client/index.js";
1568
+
1569
+ // ../skills/src/backend/analyze-dynamo-schema.ts
1570
+ import { readFile as readFile7 } from "fs/promises";
1571
+ import { resolve as resolve10 } from "path";
1572
+ import { z as z13 } from "zod";
1573
+ var InputSchema12 = z13.object({
1574
+ path: z13.string().describe("Path to the *.model.ts file containing the OneTable schema")
1575
+ });
1576
+ function extractSchemaName(source) {
1577
+ const match = /export\s+const\s+(\w+Schema)\s*=/.exec(source);
1578
+ return match?.[1] ?? "UnknownSchema";
1579
+ }
1580
+ function extractEntityType(source) {
1581
+ const match = /export\s+type\s+(\w+)\s*=\s*Entity</.exec(source);
1582
+ return match?.[1] ?? "UnknownEntity";
1583
+ }
1584
+ function extractFields(source) {
1585
+ const fieldsKeyIdx = source.indexOf("fields:");
1586
+ if (fieldsKeyIdx === -1) return [];
1587
+ const braceOpen = source.indexOf("{", fieldsKeyIdx);
1588
+ if (braceOpen === -1) return [];
1589
+ let depth = 0;
1590
+ let fieldsEnd = -1;
1591
+ for (let i = braceOpen; i < source.length; i++) {
1592
+ if (source[i] === "{") depth++;
1593
+ else if (source[i] === "}") {
1594
+ depth--;
1595
+ if (depth === 0) {
1596
+ fieldsEnd = i;
1597
+ break;
1598
+ }
1599
+ }
1600
+ }
1601
+ if (fieldsEnd === -1) return [];
1602
+ const fieldsBlock = source.slice(braceOpen + 1, fieldsEnd);
1603
+ const fields = [];
1604
+ for (const m of fieldsBlock.matchAll(/(\w+)\s*:\s*\{([^}]*)\}/g)) {
1605
+ const body = m[2];
1606
+ const typeMatch = /type\s*:\s*['"]?(\w+)['"]?/.exec(body);
1607
+ const requiredMatch = /required\s*:\s*(true|false)/.exec(body);
1608
+ fields.push({
1609
+ name: m[1],
1610
+ type: typeMatch?.[1] ?? "unknown",
1611
+ required: requiredMatch?.[1] === "true",
1612
+ isEnum: /enum\s*:\s*\w+/.test(body)
1613
+ });
1614
+ }
1615
+ return fields;
1616
+ }
1617
+ function extractEnums(source) {
1618
+ const enums = [];
1619
+ for (const m of source.matchAll(/export\s+(?:const\s+)?enum\s+(\w+)\s*\{([^}]*)\}/g)) {
1620
+ const values = m[2].split(",").map((v) => v.trim().split("=")[0].trim()).filter(Boolean);
1621
+ enums.push({ name: m[1], values });
1622
+ }
1623
+ return enums;
1624
+ }
1625
+ function extractTypenames(source) {
1626
+ const matches = source.match(/Typenames\.\w+/g) ?? [];
1627
+ return [...new Set(matches)];
1628
+ }
1629
+ var analyzeDynamoSchemaSkill = {
1630
+ name: "analyze-dynamo-schema",
1631
+ description: "Parse a OneTable *.model.ts file and extract the schema name, entity type, fields (with types and required flags), enums, and Typenames references. Use this before generating a service to understand the exact entity shape.",
1632
+ async execute(input5) {
1633
+ const { path } = InputSchema12.parse(input5);
1634
+ const source = await readFile7(resolve10(path), "utf-8");
1635
+ return {
1636
+ schemaName: extractSchemaName(source),
1637
+ entityType: extractEntityType(source),
1638
+ fields: extractFields(source),
1639
+ enums: extractEnums(source),
1640
+ typenames: extractTypenames(source)
1641
+ };
1642
+ }
1643
+ };
1644
+
1645
+ // ../skills/src/backend/analyze-graphql-schema.ts
1646
+ import { readFile as readFile8 } from "fs/promises";
1647
+ import { resolve as resolve11 } from "path";
1648
+ import { z as z14 } from "zod";
1649
+ var InputSchema13 = z14.object({
1650
+ path: z14.string().describe("Path to the *.schema.graphql file")
1651
+ });
1652
+ function extractOperations(source, section) {
1653
+ const sectionRe = new RegExp(`extend\\s+type\\s+${section}\\s*\\{([^}]*)\\}`, "s");
1654
+ const match = sectionRe.exec(source);
1655
+ if (!match) return [];
1656
+ const ops = [];
1657
+ for (const m of match[1].matchAll(/(\w+)\s*(\([^)]*\))?\s*:\s*([^\n@#]+)((?:\s*@\w+[^\n]*)*)/g)) {
1658
+ const directives = m[4].trim().split(/\s*@/).filter(Boolean).map((d) => `@${d.trim()}`);
1659
+ ops.push({
1660
+ name: m[1],
1661
+ args: (m[2] ?? "").trim(),
1662
+ returns: m[3].trim(),
1663
+ directives
1664
+ });
1665
+ }
1666
+ return ops;
1667
+ }
1668
+ function extractTypes(source) {
1669
+ const types = [];
1670
+ for (const m of source.matchAll(/(?:^|\n)\s*(?:extend\s+)?type\s+(\w+)\s*(?:implements[^{]*)?\{/g)) {
1671
+ if (!["Query", "Mutation", "Subscription"].includes(m[1])) types.push(m[1]);
1672
+ }
1673
+ return [...new Set(types)];
1674
+ }
1675
+ function extractInputs(source) {
1676
+ return [...source.matchAll(/input\s+(\w+)\s*\{/g)].map((m) => m[1]);
1677
+ }
1678
+ function extractEnums2(source) {
1679
+ return [...source.matchAll(/(?:extend\s+)?enum\s+(\w+)\s*\{/g)].map((m) => m[1]);
1680
+ }
1681
+ var analyzeGraphqlSchemaSkill = {
1682
+ name: "analyze-graphql-schema",
1683
+ description: "Parse a GraphQL SDL file and extract types, queries, mutations, inputs and enums. Use this before generating a resolver to understand what operations already exist and what the correct return types are.",
1684
+ async execute(input5) {
1685
+ const { path } = InputSchema13.parse(input5);
1686
+ const source = await readFile8(resolve11(path), "utf-8");
1687
+ return {
1688
+ types: extractTypes(source),
1689
+ queries: extractOperations(source, "Query"),
1690
+ mutations: extractOperations(source, "Mutation"),
1691
+ inputs: extractInputs(source),
1692
+ enums: extractEnums2(source)
1693
+ };
1694
+ }
1695
+ };
1696
+
1697
+ // ../skills/src/backend/analyze-nestjs-module.ts
1698
+ import { readFile as readFile9 } from "fs/promises";
1699
+ import { resolve as resolve12 } from "path";
1700
+ import { z as z15 } from "zod";
1701
+ var InputSchema14 = z15.object({
1702
+ path: z15.string().describe("Path to the *.module.ts file")
1703
+ });
1704
+ function extractArrayItems(source, key) {
1705
+ const re = new RegExp(`${key}\\s*:\\s*\\[([^\\]]*?)\\]`, "s");
1706
+ const match = re.exec(source);
1707
+ if (!match) return [];
1708
+ return match[1].split(",").map(
1709
+ (s) => s.trim().replace(/\/\/.*$/m, "").trim()
1710
+ ).filter(Boolean);
1711
+ }
1712
+ function extractModuleName(source) {
1713
+ const match = /export\s+class\s+(\w+Module)/.exec(source);
1714
+ return match?.[1] ?? "UnknownModule";
1715
+ }
1716
+ var analyzeNestjsModuleSkill = {
1717
+ name: "analyze-nestjs-module",
1718
+ description: "Parse a NestJS *.module.ts file and extract its imports, providers, exports and controllers. Use this to understand what a module exposes before adding new providers or resolving injection dependencies.",
1719
+ async execute(input5) {
1720
+ const { path } = InputSchema14.parse(input5);
1721
+ const source = await readFile9(resolve12(path), "utf-8");
1722
+ return {
1723
+ moduleName: extractModuleName(source),
1724
+ imports: extractArrayItems(source, "imports"),
1725
+ providers: extractArrayItems(source, "providers"),
1726
+ exports: extractArrayItems(source, "exports"),
1727
+ controllers: extractArrayItems(source, "controllers")
1728
+ };
1729
+ }
1730
+ };
1731
+
1732
+ // ../skills/src/backend/find-code-pattern.ts
1733
+ import { readdir as readdir4, readFile as readFile10 } from "fs/promises";
1734
+ import { join as join4 } from "path";
1735
+ import { z as z16 } from "zod";
1736
+ var patternSuffixes = {
1737
+ service: [".service.ts"],
1738
+ resolver: [".resolver.ts"],
1739
+ listener: [".listener.ts"],
1740
+ model: [".model.ts"],
1741
+ test: [".spec.ts"],
1742
+ schema: [".schema.graphql"]
1743
+ };
1744
+ var InputSchema15 = z16.object({
1745
+ patternType: z16.enum(["service", "resolver", "listener", "model", "test", "schema"]).describe("Type of NestJS pattern to find examples of"),
1746
+ directory: z16.string().describe("Root directory to search in"),
1747
+ maxExamples: z16.number().int().positive().default(3).describe("Maximum number of examples to return")
1748
+ });
1749
+ async function walkForPattern(dir, suffixes, results, max) {
1750
+ if (results.length >= max) return;
1751
+ const entries = await readdir4(dir, { withFileTypes: true }).catch(() => null);
1752
+ if (!entries) return;
1753
+ for (const entry of entries) {
1754
+ if (results.length >= max) break;
1755
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
1756
+ const fullPath = join4(dir, entry.name);
1757
+ if (entry.isDirectory()) {
1758
+ await walkForPattern(fullPath, suffixes, results, max);
1759
+ } else if (entry.isFile() && suffixes.some((s) => entry.name.endsWith(s))) {
1760
+ const content = await readFile10(fullPath, "utf-8");
1761
+ results.push({ file: fullPath, content });
1762
+ }
1763
+ }
1764
+ }
1765
+ var findCodePatternSkill = {
1766
+ name: "find-code-pattern",
1767
+ description: "Find real examples of NestJS patterns (service, resolver, listener, model, test, schema) from the codebase. Use this before generating new code to understand the exact conventions and patterns already in use.",
1768
+ async execute(input5) {
1769
+ const { patternType, directory, maxExamples } = InputSchema15.parse(input5);
1770
+ const suffixes = patternSuffixes[patternType];
1771
+ const results = [];
1772
+ await walkForPattern(directory, suffixes, results, maxExamples);
1773
+ return results;
1774
+ }
1775
+ };
1776
+
1777
+ // ../skills/src/frontend/analyze-component.ts
1778
+ import { readFile as readFile11 } from "fs/promises";
1779
+ import { resolve as resolve13 } from "path";
1780
+ import { z as z17 } from "zod";
1781
+ var InputSchema16 = z17.object({
1782
+ path: z17.string().describe("Path to the React component file (.tsx or .ts)")
1783
+ });
1784
+ function extractComponentName(source, filePath) {
1785
+ const fnMatch = /(?:export\s+(?:default\s+)?function\s+|const\s+)([A-Z]\w*)/.exec(source);
1786
+ if (fnMatch) return fnMatch[1];
1787
+ const parts = filePath.split(/[/\\]/);
1788
+ return (parts[parts.length - 1] ?? "").replace(/\.(tsx?|jsx?)$/, "");
1789
+ }
1790
+ function extractPropsInterface(source) {
1791
+ const ifaceMatch = /interface\s+(\w*Props\w*)\s*\{([^}]*)\}/.exec(source);
1792
+ if (ifaceMatch) return `interface ${ifaceMatch[1]} {${ifaceMatch[2]}}`;
1793
+ const typeMatch = /type\s+(\w*Props\w*)\s*=\s*\{([^}]*)\}/.exec(source);
1794
+ if (typeMatch) return `type ${typeMatch[1]} = {${typeMatch[2]}}`;
1795
+ return null;
1796
+ }
1797
+ function extractHooksUsed(source) {
1798
+ const matches = source.match(/\buse[A-Z]\w*/g) ?? [];
1799
+ return [...new Set(matches)];
1800
+ }
1801
+ function extractNamedExports(source) {
1802
+ return [...source.matchAll(/export\s+(?:const|function|class|type|interface|enum)\s+(\w+)/g)].map((m) => m[1]);
1803
+ }
1804
+ var analyzeComponentSkill = {
1805
+ name: "analyze-component",
1806
+ description: "Parse a React component file and extract its name, props interface, hooks used, named exports, and whether it has a default export. Use this to understand an existing component's contract before creating a related component or hook.",
1807
+ async execute(input5) {
1808
+ const { path } = InputSchema16.parse(input5);
1809
+ const source = await readFile11(resolve13(path), "utf-8");
1810
+ return {
1811
+ componentName: extractComponentName(source, path),
1812
+ propsInterface: extractPropsInterface(source),
1813
+ hooksUsed: extractHooksUsed(source),
1814
+ namedExports: extractNamedExports(source),
1815
+ hasDefaultExport: /export\s+default\s+/.test(source)
1816
+ };
1817
+ }
1818
+ };
1819
+
1820
+ // ../skills/src/frontend/analyze-module-structure.ts
1821
+ import { readdir as readdir5 } from "fs/promises";
1822
+ import { join as join5 } from "path";
1823
+ import { z as z18 } from "zod";
1824
+ var InputSchema17 = z18.object({
1825
+ directory: z18.string().describe("Root directory of the React module or feature folder")
1826
+ });
1827
+ async function collectFiles2(dir) {
1828
+ const entries = await readdir5(dir, { withFileTypes: true }).catch(() => null);
1829
+ if (!entries) return [];
1830
+ const files = [];
1831
+ for (const entry of entries) {
1832
+ if (entry.name === "node_modules" || entry.name === "dist") continue;
1833
+ const fullPath = join5(dir, entry.name);
1834
+ if (entry.isDirectory()) {
1835
+ files.push(...await collectFiles2(fullPath));
1836
+ } else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name)) {
1837
+ files.push(fullPath);
1838
+ }
1839
+ }
1840
+ return files;
1841
+ }
1842
+ function categorize(files) {
1843
+ const result = { components: [], hooks: [], pages: [], stores: [], indexFiles: [] };
1844
+ for (const f of files) {
1845
+ const name = f.split(/[/\\]/).pop() ?? "";
1846
+ if (name.includes(".spec.") || name.includes(".test.")) continue;
1847
+ if (name === "index.ts" || name === "index.tsx") result.indexFiles.push(f);
1848
+ else if (name.startsWith("use")) result.hooks.push(f);
1849
+ else if (/page|route/i.test(name)) result.pages.push(f);
1850
+ else if (/store|context|provider/i.test(name)) result.stores.push(f);
1851
+ else if (/^[A-Z]/.test(name)) result.components.push(f);
1852
+ }
1853
+ return result;
1854
+ }
1855
+ var analyzeModuleStructureSkill = {
1856
+ name: "analyze-module-structure",
1857
+ description: "Map the file structure of a React module directory: lists components, hooks, pages, stores and index files. Use this before creating a new feature module to understand the existing layout and avoid duplicating files.",
1858
+ async execute(input5) {
1859
+ const { directory } = InputSchema17.parse(input5);
1860
+ const files = await collectFiles2(directory);
1861
+ return categorize(files);
1862
+ }
1863
+ };
1864
+
1865
+ // ../skills/src/frontend/find-component-pattern.ts
1866
+ import { readdir as readdir6, readFile as readFile12 } from "fs/promises";
1867
+ import { join as join6 } from "path";
1868
+ import { z as z19 } from "zod";
1869
+ var patternFilters = {
1870
+ component: (name) => /^[A-Z]/.test(name) && (name.endsWith(".tsx") || name.endsWith(".ts")) && !name.includes(".spec."),
1871
+ hook: (name) => name.startsWith("use") && (name.endsWith(".ts") || name.endsWith(".tsx")) && !name.includes(".spec."),
1872
+ page: (name) => /page|route/i.test(name) && (name.endsWith(".tsx") || name.endsWith(".ts")) && !name.includes(".spec."),
1873
+ module: (name) => (name === "index.ts" || name === "index.tsx" || /module/i.test(name)) && !name.includes(".spec.")
1874
+ };
1875
+ var InputSchema18 = z19.object({
1876
+ patternType: z19.enum(["component", "hook", "page", "module"]).describe("Type of React pattern to find examples of"),
1877
+ directory: z19.string().describe("Root directory to search in"),
1878
+ maxExamples: z19.number().int().positive().default(3).describe("Maximum number of examples to return")
1879
+ });
1880
+ async function walkForComponent(dir, filter, results, max) {
1881
+ if (results.length >= max) return;
1882
+ const entries = await readdir6(dir, { withFileTypes: true }).catch(() => null);
1883
+ if (!entries) return;
1884
+ for (const entry of entries) {
1885
+ if (results.length >= max) break;
1886
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
1887
+ const fullPath = join6(dir, entry.name);
1888
+ if (entry.isDirectory()) {
1889
+ await walkForComponent(fullPath, filter, results, max);
1890
+ } else if (entry.isFile() && filter(entry.name)) {
1891
+ const content = await readFile12(fullPath, "utf-8");
1892
+ results.push({ file: fullPath, content });
1893
+ }
1894
+ }
1895
+ }
1896
+ var findComponentPatternSkill = {
1897
+ name: "find-component-pattern",
1898
+ description: "Find real examples of React patterns (component, hook, page, module) from the codebase. Use this before generating new UI code to understand the exact naming conventions, file structure and import patterns already in use.",
1899
+ async execute(input5) {
1900
+ const { patternType, directory, maxExamples } = InputSchema18.parse(input5);
1901
+ const filter = patternFilters[patternType];
1902
+ const results = [];
1903
+ await walkForComponent(directory, filter, results, maxExamples);
1904
+ return results;
1905
+ }
1906
+ };
1907
+
1908
+ // ../skills/src/qa/analyze-test-coverage.ts
1909
+ import { readdir as readdir7 } from "fs/promises";
1910
+ import { join as join7 } from "path";
1911
+ import { z as z20 } from "zod";
1912
+ var InputSchema19 = z20.object({
1913
+ directory: z20.string().describe("Root directory to scan for source files and their tests"),
1914
+ extensions: z20.array(z20.string()).default([".ts", ".tsx"]).describe("Source file extensions to check"),
1915
+ ignorePatterns: z20.array(z20.string()).default(["index.ts", "index.tsx", ".spec.", ".test.", ".d.ts", ".module.ts"]).describe("Substrings that mark files to skip (index, spec, declaration, module files)")
1916
+ });
1917
+ async function collectFiles3(dir, exts, ignore) {
1918
+ const entries = await readdir7(dir, { withFileTypes: true }).catch(() => null);
1919
+ if (!entries) return [];
1920
+ const files = [];
1921
+ for (const entry of entries) {
1922
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
1923
+ const fullPath = join7(dir, entry.name);
1924
+ if (entry.isDirectory()) {
1925
+ files.push(...await collectFiles3(fullPath, exts, ignore));
1926
+ } else if (entry.isFile() && exts.some((e) => entry.name.endsWith(e)) && !ignore.some((p) => entry.name.includes(p) || fullPath.includes(p))) {
1927
+ files.push(fullPath);
1928
+ }
1929
+ }
1930
+ return files;
1931
+ }
1932
+ async function collectAllFiles(dir) {
1933
+ const files = await collectFiles3(dir, [".ts", ".tsx"], []);
1934
+ return new Set(files);
1935
+ }
1936
+ function specPathCandidates(filePath) {
1937
+ const base = filePath.replace(/\.(tsx?)$/, "");
1938
+ return [`${base}.spec.ts`, `${base}.spec.tsx`, `${base}.test.ts`, `${base}.test.tsx`];
1939
+ }
1940
+ var analyzeTestCoverageSkill = {
1941
+ name: "analyze-test-coverage",
1942
+ description: "Scan a directory and identify which source files have a corresponding .spec.ts test file and which do not. Use this before a QA pass to prioritize which files need tests written.",
1943
+ async execute(input5) {
1944
+ const { directory, extensions, ignorePatterns } = InputSchema19.parse(input5);
1945
+ const sourceFiles = await collectFiles3(directory, extensions, ignorePatterns);
1946
+ const allFiles = await collectAllFiles(directory);
1947
+ const covered = [];
1948
+ const uncovered = [];
1949
+ for (const file of sourceFiles) {
1950
+ const hasSpec = specPathCandidates(file).some((s) => allFiles.has(s));
1951
+ if (hasSpec) covered.push(file);
1952
+ else uncovered.push(file);
1953
+ }
1954
+ const total = covered.length + uncovered.length;
1955
+ return {
1956
+ covered,
1957
+ uncovered,
1958
+ coverageRate: total === 0 ? 1 : covered.length / total
1959
+ };
1960
+ }
1961
+ };
1962
+
1963
+ // ../skills/src/qa/find-test-pattern.ts
1964
+ import { readdir as readdir8, readFile as readFile13 } from "fs/promises";
1965
+ import { join as join8 } from "path";
1966
+ import { z as z21 } from "zod";
1967
+ var patternFilters2 = {
1968
+ service: (name) => name.endsWith(".service.spec.ts"),
1969
+ component: (name) => /^[A-Z]/.test(name) && (name.endsWith(".spec.ts") || name.endsWith(".spec.tsx")),
1970
+ hook: (name) => name.startsWith("use") && (name.endsWith(".spec.ts") || name.endsWith(".spec.tsx")),
1971
+ resolver: (name) => name.endsWith(".resolver.spec.ts")
1972
+ };
1973
+ var InputSchema20 = z21.object({
1974
+ patternType: z21.enum(["service", "component", "hook", "resolver"]).describe("Type of test pattern to find examples of"),
1975
+ directory: z21.string().describe("Root directory to search in"),
1976
+ maxExamples: z21.number().int().positive().default(2).describe("Maximum number of test examples to return")
1977
+ });
1978
+ async function walkForTests(dir, filter, results, max) {
1979
+ if (results.length >= max) return;
1980
+ const entries = await readdir8(dir, { withFileTypes: true }).catch(() => null);
1981
+ if (!entries) return;
1982
+ for (const entry of entries) {
1983
+ if (results.length >= max) break;
1984
+ if (entry.name === "node_modules" || entry.name === "dist" || entry.name === ".git") continue;
1985
+ const fullPath = join8(dir, entry.name);
1986
+ if (entry.isDirectory()) {
1987
+ await walkForTests(fullPath, filter, results, max);
1988
+ } else if (entry.isFile() && filter(entry.name)) {
1989
+ const content = await readFile13(fullPath, "utf-8");
1990
+ results.push({ file: fullPath, content });
1991
+ }
1992
+ }
1993
+ }
1994
+ var findTestPatternSkill = {
1995
+ name: "find-test-pattern",
1996
+ description: "Find real test file examples (service, component, hook, resolver) from the codebase. Use this before writing tests to understand the exact test structure, setup() pattern, mocking conventions and assertion style already in use.",
1997
+ async execute(input5) {
1998
+ const { patternType, directory, maxExamples } = InputSchema20.parse(input5);
1999
+ const filter = patternFilters2[patternType];
2000
+ const results = [];
2001
+ await walkForTests(directory, filter, results, maxExamples);
2002
+ return results;
2003
+ }
2004
+ };
2005
+
2006
+ // ../cli/src/commands/mcp-serve.ts
2007
+ import chalk7 from "chalk";
2008
+ var skills = [
2009
+ readFileSkill,
2010
+ writeFileSkill,
2011
+ listDirectorySkill,
2012
+ searchCodeSkill,
2013
+ auditAccessibilitySkill,
2014
+ gitStatusSkill,
2015
+ gitDiffSkill,
2016
+ gitLogSkill,
2017
+ gitCommitMessageSkill,
2018
+ httpRequestSkill,
2019
+ analyzeImageSkill,
2020
+ findCodePatternSkill,
2021
+ analyzeNestjsModuleSkill,
2022
+ analyzeDynamoSchemaSkill,
2023
+ analyzeGraphqlSchemaSkill,
2024
+ findComponentPatternSkill,
2025
+ analyzeComponentSkill,
2026
+ analyzeModuleStructureSkill,
2027
+ findTestPatternSkill,
2028
+ analyzeTestCoverageSkill
2029
+ ];
2030
+ async function mcpServeCommand() {
2031
+ process.stderr.write(chalk7.bold.cyan("\n\u25C6 omni mcp serve\n"));
2032
+ process.stderr.write(chalk7.gray(` Transport: stdio
2033
+ `));
2034
+ process.stderr.write(chalk7.gray(` Skills: ${skills.length}
2035
+ `));
2036
+ process.stderr.write(chalk7.gray("\n Waiting for MCP client connection\u2026\n\n"));
2037
+ await serveStdioMcp(skills, { name: "omni-ai" });
2038
+ await new Promise(() => {
2039
+ });
2040
+ }
2041
+
2042
+ // ../cli/src/commands/new.ts
2043
+ import { readFile as readFile14 } from "fs/promises";
2044
+ import { join as join12 } from "path";
2045
+ import { select as select4 } from "@inquirer/prompts";
2046
+ import chalk11 from "chalk";
2047
+ import YAML from "yaml";
2048
+
2049
+ // ../cli/src/commands/new/scaffold-agent.ts
2050
+ import { access as access2, mkdir as mkdir2, writeFile as writeFile6 } from "fs/promises";
2051
+ import { join as join9, resolve as resolve14 } from "path";
2052
+ import { checkbox as checkbox2, confirm as confirm2, input as input2, select as select2 } from "@inquirer/prompts";
2053
+ import chalk8 from "chalk";
2054
+
2055
+ // ../cli/src/commands/new/templates.ts
2056
+ function kebabToPascal(s) {
2057
+ return s.replace(/(^|-)([a-z])/g, (_, __, c) => c.toUpperCase());
2058
+ }
2059
+ function kebabToCamel(s) {
2060
+ return s.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
2061
+ }
2062
+ function generateAgentYaml(params) {
2063
+ const skillLines = params.skills.length > 0 ? params.skills.map((s) => ` - ${s}`).join("\n") : " [] # add skill names here";
2064
+ const promptLines = params.systemPrompt.split("\n").map((l) => ` ${l}`).join("\n");
2065
+ return `name: ${params.name}
2066
+ description: ${params.description}
2067
+ systemPrompt: |
2068
+ ${promptLines}
2069
+ skills:
2070
+ ${skillLines}
2071
+ maxIterations: ${params.maxIterations}
2072
+ temperature: ${params.temperature}
2073
+ `;
2074
+ }
2075
+ function generateSkillTs(params) {
2076
+ const pascal = kebabToPascal(params.kebabName);
2077
+ const camel = kebabToCamel(params.kebabName);
2078
+ return `import type { ISkill, SkillContext } from "@omni-ai/core";
2079
+ import { z } from "zod";
2080
+
2081
+ const InputSchema = z.object({
2082
+ query: z.string().describe("Input for ${params.kebabName}"),
2083
+ });
2084
+
2085
+ export type ${pascal}Input = z.infer<typeof InputSchema>;
2086
+
2087
+ export const ${camel}Skill: ISkill<${pascal}Input, string> = {
2088
+ name: "${params.kebabName}",
2089
+ description: "${params.description}",
2090
+
2091
+ async execute(input: ${pascal}Input, _ctx: SkillContext): Promise<string> {
2092
+ const { query } = InputSchema.parse(input);
2093
+ // TODO: implement ${params.kebabName} logic
2094
+ return query;
2095
+ },
2096
+ };
2097
+ `;
2098
+ }
2099
+ function generateProviderFiles(params) {
2100
+ const pascal = kebabToPascal(params.kebabName);
2101
+ const indexTs = `import type {
2102
+ CompletionRequest,
2103
+ CompletionResponse,
2104
+ IProvider,
2105
+ ProviderCapabilities,
2106
+ } from "@omni-ai/core";
2107
+ import { ProviderRegistry } from "@omni-ai/core";
2108
+
2109
+ export class ${pascal}Provider implements IProvider {
2110
+ readonly name = "${params.kebabName}";
2111
+ readonly capabilities: ProviderCapabilities = {
2112
+ chat: true,
2113
+ embedding: ${params.hasEmbedding},
2114
+ streaming: false,
2115
+ toolUse: true,
2116
+ vision: ${params.hasVision},
2117
+ };
2118
+
2119
+ async complete(_request: CompletionRequest): Promise<CompletionResponse> {
2120
+ // TODO: implement ${params.displayName} API call
2121
+ throw new Error("Not implemented");
2122
+ }
2123
+ }
2124
+
2125
+ ProviderRegistry.register(new ${pascal}Provider());
2126
+ `;
2127
+ const packageJson = JSON.stringify(
2128
+ {
2129
+ name: `@omni-ai/provider-${params.kebabName}`,
2130
+ version: "0.1.0",
2131
+ type: "module",
2132
+ exports: {
2133
+ ".": {
2134
+ import: "./dist/index.js",
2135
+ types: "./dist/index.d.ts"
2136
+ }
2137
+ },
2138
+ scripts: {
2139
+ build: "tsc",
2140
+ typecheck: "tsc --noEmit"
2141
+ },
2142
+ dependencies: {
2143
+ "@omni-ai/core": "workspace:*"
2144
+ },
2145
+ devDependencies: {
2146
+ typescript: "^5.7.0",
2147
+ "@types/node": "^22.0.0"
2148
+ }
2149
+ },
2150
+ null,
2151
+ 2
2152
+ );
2153
+ const tsconfigJson = JSON.stringify(
2154
+ {
2155
+ compilerOptions: {
2156
+ target: "ES2022",
2157
+ module: "NodeNext",
2158
+ moduleResolution: "NodeNext",
2159
+ lib: ["ES2022"],
2160
+ outDir: "dist",
2161
+ rootDir: "src",
2162
+ declaration: true,
2163
+ declarationDir: "dist",
2164
+ strict: true,
2165
+ esModuleInterop: false,
2166
+ skipLibCheck: true
2167
+ },
2168
+ include: ["src"],
2169
+ references: [{ path: "../core" }]
2170
+ },
2171
+ null,
2172
+ 2
2173
+ );
2174
+ return { indexTs, packageJson, tsconfigJson };
2175
+ }
2176
+
2177
+ // ../cli/src/commands/new/scaffold-agent.ts
2178
+ var ALL_SKILLS = [
2179
+ // Layer 1 — primitives
2180
+ "read-file",
2181
+ "write-file",
2182
+ "list-directory",
2183
+ "search-code",
2184
+ "audit-accessibility",
2185
+ "git-status",
2186
+ "git-diff",
2187
+ "git-log",
2188
+ "git-commit-message",
2189
+ "http-request",
2190
+ "analyze-image",
2191
+ // Layer 2 — domain readers
2192
+ "find-code-pattern",
2193
+ "analyze-nestjs-module",
2194
+ "analyze-dynamo-schema",
2195
+ "analyze-graphql-schema",
2196
+ "find-component-pattern",
2197
+ "analyze-component",
2198
+ "analyze-module-structure",
2199
+ "find-test-pattern",
2200
+ "analyze-test-coverage"
2201
+ ];
2202
+ async function writeAgentFile(params, agentsDir) {
2203
+ const dir = resolve14(process.cwd(), agentsDir, params.domain);
2204
+ const filePath = join9(dir, `${params.domain}-${params.role}.yaml`);
2205
+ const content = generateAgentYaml({
2206
+ name: `${params.domain}-${params.role}`,
2207
+ description: params.description,
2208
+ systemPrompt: params.systemPrompt,
2209
+ skills: params.skills,
2210
+ maxIterations: 10,
2211
+ temperature: 0.3
2212
+ });
2213
+ await mkdir2(dir, { recursive: true });
2214
+ await writeFile6(filePath, content, "utf-8");
2215
+ return filePath;
2216
+ }
2217
+ async function agentWizard(agentsDir) {
2218
+ console.log();
2219
+ const domain = await select2({
2220
+ message: "Dom\xEDnio do agente:",
2221
+ choices: [
2222
+ { value: "backend", name: "backend \u2014 NestJS, DynamoDB, GraphQL" },
2223
+ { value: "frontend", name: "frontend \u2014 React, hooks, componentes" },
2224
+ { value: "ux", name: "ux \u2014 acessibilidade, design system" },
2225
+ { value: "qa", name: "qa \u2014 testes, coverage, valida\xE7\xE3o" }
2226
+ ]
2227
+ });
2228
+ const role = await input2({
2229
+ message: "Role (slug, ex: service, dev, test):",
2230
+ validate: (v) => /^[a-z][a-z0-9-]*$/.test(v.trim()) || "Use letras min\xFAsculas, n\xFAmeros e h\xEDfens"
2231
+ });
2232
+ const description = await input2({
2233
+ message: "Descri\xE7\xE3o (uma linha):",
2234
+ validate: (v) => v.trim().length > 0 || "Descri\xE7\xE3o n\xE3o pode ser vazia"
2235
+ });
2236
+ const systemPrompt = await input2({
2237
+ message: "System prompt (pode editar no arquivo depois):",
2238
+ default: `You are a ${domain} developer specialized in ${role} tasks.`
2239
+ });
2240
+ const skills2 = await checkbox2({
2241
+ message: "Skills dispon\xEDveis para este agente:",
2242
+ choices: ALL_SKILLS.map((s) => ({ value: s, name: s }))
2243
+ });
2244
+ const outPath = resolve14(process.cwd(), agentsDir, domain, `${domain}-${role}.yaml`);
2245
+ console.log();
2246
+ console.log(chalk8.dim(` Destino: ${outPath}`));
2247
+ let shouldWrite = true;
2248
+ try {
2249
+ await access2(outPath);
2250
+ shouldWrite = await confirm2({ message: chalk8.yellow("Arquivo j\xE1 existe. Sobrescrever?"), default: false });
2251
+ } catch {
2252
+ }
2253
+ if (!shouldWrite) {
2254
+ console.log(chalk8.dim("\n Opera\xE7\xE3o cancelada.\n"));
2255
+ return;
2256
+ }
2257
+ const filePath = await writeAgentFile({ domain, role, description, systemPrompt, skills: skills2 }, agentsDir);
2258
+ console.log();
2259
+ console.log(`${chalk8.green(" \u2713")} Agente criado: ${chalk8.cyan(filePath)}`);
2260
+ console.log(chalk8.dim(" Adicione ao omni-ai.yaml (se\xE7\xE3o agents:) ou coloque no agentsDir para uso autom\xE1tico."));
2261
+ console.log(chalk8.dim(` Testar: omni run ${domain}-${role} "sua tarefa aqui"`));
2262
+ console.log();
2263
+ }
2264
+
2265
+ // ../cli/src/commands/new/scaffold-provider.ts
2266
+ import { access as access3, mkdir as mkdir3, writeFile as writeFile7 } from "fs/promises";
2267
+ import { join as join10, resolve as resolve15 } from "path";
2268
+ import { confirm as confirm3, input as input3 } from "@inquirer/prompts";
2269
+ import chalk9 from "chalk";
2270
+ async function writeProviderFiles(params, providersDir) {
2271
+ const dir = resolve15(process.cwd(), providersDir, `provider-${params.kebabName}`, "src");
2272
+ const packageDir = resolve15(process.cwd(), providersDir, `provider-${params.kebabName}`);
2273
+ const { indexTs, packageJson, tsconfigJson } = generateProviderFiles(params);
2274
+ await mkdir3(dir, { recursive: true });
2275
+ await writeFile7(join10(dir, "index.ts"), indexTs, "utf-8");
2276
+ await writeFile7(join10(packageDir, "package.json"), packageJson, "utf-8");
2277
+ await writeFile7(join10(packageDir, "tsconfig.json"), tsconfigJson, "utf-8");
2278
+ return packageDir;
2279
+ }
2280
+ async function providerWizard(providersDir) {
2281
+ console.log();
2282
+ const kebabName = await input3({
2283
+ message: "Nome do provider (kebab-case, ex: mistral, groq, bedrock):",
2284
+ validate: (v) => /^[a-z][a-z0-9-]*$/.test(v.trim()) || "Use letras min\xFAsculas, n\xFAmeros e h\xEDfens"
2285
+ });
2286
+ const displayName = await input3({
2287
+ message: "Nome de exibi\xE7\xE3o (ex: Mistral AI, Groq):",
2288
+ default: kebabName.replace(/-/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()),
2289
+ validate: (v) => v.trim().length > 0 || "Nome n\xE3o pode ser vazio"
2290
+ });
2291
+ const hasVision = await confirm3({ message: "Suporta vis\xE3o (imagens)?", default: false });
2292
+ const hasEmbedding = await confirm3({ message: "Suporta embeddings?", default: false });
2293
+ const outDir = resolve15(process.cwd(), providersDir, `provider-${kebabName}`);
2294
+ console.log();
2295
+ console.log(chalk9.dim(` Destino: ${outDir}/`));
2296
+ let shouldWrite = true;
2297
+ try {
2298
+ await access3(outDir);
2299
+ shouldWrite = await confirm3({
2300
+ message: chalk9.yellow(`packages/provider-${kebabName}/ j\xE1 existe. Sobrescrever arquivos?`),
2301
+ default: false
2302
+ });
2303
+ } catch {
2304
+ }
2305
+ if (!shouldWrite) {
2306
+ console.log(chalk9.dim("\n Opera\xE7\xE3o cancelada.\n"));
2307
+ return;
2308
+ }
2309
+ const packageDir = await writeProviderFiles({ kebabName, displayName, hasVision, hasEmbedding }, providersDir);
2310
+ console.log();
2311
+ console.log(`${chalk9.green(" \u2713")} Provider criado: ${chalk9.cyan(`${packageDir}/`)}`);
2312
+ console.log(chalk9.dim(" Pr\xF3ximos passos:"));
2313
+ console.log(chalk9.dim(` 1. Implemente complete() em src/index.ts`));
2314
+ console.log(
2315
+ chalk9.dim(` 2. Adicione \xE0 raiz tsconfig.json \u2192 references: { path: "./${providersDir}/provider-${kebabName}" }`)
2316
+ );
2317
+ console.log(
2318
+ chalk9.dim(` 3. Importe em packages/cli/src/commands/run.ts: import "@omni-ai/provider-${kebabName}";`)
2319
+ );
2320
+ console.log(chalk9.dim(` 4. Adicione a chave de API ao .env.example`));
2321
+ console.log();
2322
+ }
2323
+
2324
+ // ../cli/src/commands/new/scaffold-skill.ts
2325
+ import { access as access4, mkdir as mkdir4, writeFile as writeFile8 } from "fs/promises";
2326
+ import { join as join11, resolve as resolve16 } from "path";
2327
+ import { confirm as confirm4, input as input4, select as select3 } from "@inquirer/prompts";
2328
+ import chalk10 from "chalk";
2329
+ var DOMAINS = ["backend", "frontend", "qa", "fs", "git", "http", "multimodal", "ux"];
2330
+ async function writeSkillFile(params, skillsDir) {
2331
+ const dir = resolve16(process.cwd(), skillsDir, params.domain);
2332
+ const filePath = join11(dir, `${params.kebabName}.ts`);
2333
+ const content = generateSkillTs({ kebabName: params.kebabName, description: params.description });
2334
+ await mkdir4(dir, { recursive: true });
2335
+ await writeFile8(filePath, content, "utf-8");
2336
+ return filePath;
2337
+ }
2338
+ async function skillWizard(skillsDir) {
2339
+ console.log();
2340
+ const domain = await select3({
2341
+ message: "Dom\xEDnio da skill:",
2342
+ choices: DOMAINS.map((d) => ({ value: d, name: d }))
2343
+ });
2344
+ const kebabName = await input4({
2345
+ message: "Nome da skill (kebab-case, ex: analyze-orders):",
2346
+ validate: (v) => /^[a-z][a-z0-9-]*$/.test(v.trim()) || "Use letras min\xFAsculas, n\xFAmeros e h\xEDfens"
2347
+ });
2348
+ const description = await input4({
2349
+ message: "Descri\xE7\xE3o (usada pelo agente para decidir quando chamar esta skill):",
2350
+ validate: (v) => v.trim().length > 0 || "Descri\xE7\xE3o n\xE3o pode ser vazia"
2351
+ });
2352
+ const outPath = resolve16(process.cwd(), skillsDir, domain, `${kebabName}.ts`);
2353
+ console.log();
2354
+ console.log(chalk10.dim(` Destino: ${outPath}`));
2355
+ let shouldWrite = true;
2356
+ try {
2357
+ await access4(outPath);
2358
+ shouldWrite = await confirm4({ message: chalk10.yellow("Arquivo j\xE1 existe. Sobrescrever?"), default: false });
2359
+ } catch {
2360
+ }
2361
+ if (!shouldWrite) {
2362
+ console.log(chalk10.dim("\n Opera\xE7\xE3o cancelada.\n"));
2363
+ return;
2364
+ }
2365
+ const filePath = await writeSkillFile({ domain, kebabName, description }, skillsDir);
2366
+ console.log();
2367
+ console.log(`${chalk10.green(" \u2713")} Skill criada: ${chalk10.cyan(filePath)}`);
2368
+ console.log(chalk10.dim(` Pr\xF3ximos passos:`));
2369
+ console.log(chalk10.dim(` 1. Implemente a l\xF3gica em execute()`));
2370
+ console.log(chalk10.dim(` 2. Exporte de ${skillsDir}/${domain}/index.ts`));
2371
+ console.log(chalk10.dim(` 3. Registre em packages/cli/src/commands/run.ts`));
2372
+ console.log();
2373
+ }
2374
+
2375
+ // ../cli/src/commands/new.ts
2376
+ var DEFAULTS = {
2377
+ agentsDir: "agents",
2378
+ skills: "src/skills",
2379
+ providers: "packages"
2380
+ };
2381
+ async function loadScaffoldConfig() {
2382
+ try {
2383
+ const raw = await readFile14(join12(process.cwd(), "config", "omni-ai.yaml"), "utf-8");
2384
+ const data = YAML.parse(raw);
2385
+ if (!data) return DEFAULTS;
2386
+ const sp = data.scaffoldPaths;
2387
+ return {
2388
+ agentsDir: data.agentsDir ?? DEFAULTS.agentsDir,
2389
+ skills: sp?.skills ?? DEFAULTS.skills,
2390
+ providers: sp?.providers ?? DEFAULTS.providers
2391
+ };
2392
+ } catch {
2393
+ return DEFAULTS;
2394
+ }
2395
+ }
2396
+ async function newCommand() {
2397
+ console.log();
2398
+ console.log(chalk11.bold(" omni new \u2014 scaffold interativo"));
2399
+ console.log(chalk11.dim(" Cria um agente, skill ou provider a partir de template"));
2400
+ const config = await loadScaffoldConfig();
2401
+ const type = await select4({
2402
+ message: "O que deseja criar?",
2403
+ choices: [
2404
+ { value: "agent", name: "Agent \u2014 arquivo YAML que define um agente de IA" },
2405
+ { value: "skill", name: "Skill \u2014 fun\xE7\xE3o TypeScript que o agente pode chamar" },
2406
+ { value: "provider", name: "Provider \u2014 adapter para uma nova API de LLM" }
2407
+ ]
2408
+ });
2409
+ switch (type) {
2410
+ case "agent":
2411
+ await agentWizard(config.agentsDir);
2412
+ break;
2413
+ case "skill":
2414
+ await skillWizard(config.skills);
2415
+ break;
2416
+ case "provider":
2417
+ await providerWizard(config.providers);
2418
+ break;
2419
+ }
2420
+ }
2421
+
2422
+ // ../cli/src/commands/run.ts
2423
+ import { writeFile as writeFile9 } from "fs/promises";
2424
+ function parseSession(raw) {
2425
+ const [resourceId, ...rest] = raw.split(":");
2426
+ const threadId = rest.join(":") || "default";
2427
+ return { resourceId, threadId };
2428
+ }
2429
+ function getDbPath2() {
2430
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
2431
+ return `${home}/.omni-ai/sessions.db`;
2432
+ }
2433
+ async function runCommand(agent, prompt, opts) {
2434
+ const configPath = opts.config ?? resolveConfigPath();
2435
+ const skills2 = [
2436
+ // Layer 1 — primitives
2437
+ readFileSkill,
2438
+ writeFileSkill,
2439
+ listDirectorySkill,
2440
+ searchCodeSkill,
2441
+ auditAccessibilitySkill,
2442
+ gitStatusSkill,
2443
+ gitDiffSkill,
2444
+ gitLogSkill,
2445
+ gitCommitMessageSkill,
2446
+ httpRequestSkill,
2447
+ analyzeImageSkill,
2448
+ // Layer 2 — domain readers
2449
+ findCodePatternSkill,
2450
+ analyzeNestjsModuleSkill,
2451
+ analyzeDynamoSchemaSkill,
2452
+ analyzeGraphqlSchemaSkill,
2453
+ findComponentPatternSkill,
2454
+ analyzeComponentSkill,
2455
+ analyzeModuleStructureSkill,
2456
+ findTestPatternSkill,
2457
+ analyzeTestCoverageSkill
2458
+ ];
2459
+ let memoryStore;
2460
+ let sessionId;
2461
+ if (opts.session) {
2462
+ sessionId = parseSession(opts.session);
2463
+ memoryStore = new SQLiteMemoryStore({ path: getDbPath2() });
2464
+ }
2465
+ const runtime = await createRuntime({ configPath, skills: skills2, memoryStore }).catch((err) => {
2466
+ console.error(errorLine(`Failed to load config: ${err instanceof Error ? err.message : String(err)}`));
2467
+ process.exit(1);
2468
+ });
2469
+ const providerLabel = `${runtime.config.defaultProvider} / ${runtime.config.providers.find((p) => p.name === runtime.config.defaultProvider)?.defaultModel ?? "default"}`;
2470
+ console.log(`
2471
+ ${agentHeader(agent, providerLabel)}`);
2472
+ const onToken = opts.stream ? (chunk) => process.stdout.write(chunk) : void 0;
2473
+ if (opts.stream) process.stdout.write("\n");
2474
+ const result = await runtime.run(agent, prompt, { session: sessionId, onToken }).catch(async (err) => {
2475
+ console.error(errorLine(err instanceof Error ? err.message : String(err)));
2476
+ await memoryStore?.close?.();
2477
+ process.exit(1);
2478
+ });
2479
+ if (opts.stream) {
2480
+ process.stdout.write("\n");
2481
+ } else {
2482
+ console.log(`
2483
+ ${result.output}
2484
+ `);
2485
+ }
2486
+ console.log(iterationLine(result.iterations));
2487
+ if (opts.output) {
2488
+ await writeFile9(opts.output, result.output, "utf-8");
2489
+ console.log(savedLine(opts.output));
2490
+ }
2491
+ if (result.usage) {
2492
+ console.log(tokenSummary(result.usage.inputTokens, result.usage.outputTokens));
2493
+ }
2494
+ console.log();
2495
+ await memoryStore?.close?.();
2496
+ }
2497
+
2498
+ // ../cli/src/commands/serve.ts
2499
+ import chalk12 from "chalk";
2500
+ import express from "express";
2501
+ function getDbPath3() {
2502
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? ".";
2503
+ return `${home}/.omni-ai/sessions.db`;
2504
+ }
2505
+ function parseSession2(raw) {
2506
+ const [resourceId, ...rest] = raw.split(":");
2507
+ return { resourceId, threadId: rest.join(":") || "default" };
2508
+ }
2509
+ async function serveCommand(opts) {
2510
+ const port = opts.port ? Number.parseInt(opts.port, 10) : 3e3;
2511
+ const configPath = opts.config ?? resolveConfigPath();
2512
+ const skills2 = [
2513
+ readFileSkill,
2514
+ writeFileSkill,
2515
+ listDirectorySkill,
2516
+ searchCodeSkill,
2517
+ auditAccessibilitySkill,
2518
+ gitStatusSkill,
2519
+ gitDiffSkill,
2520
+ gitLogSkill,
2521
+ gitCommitMessageSkill,
2522
+ httpRequestSkill,
2523
+ analyzeImageSkill,
2524
+ findCodePatternSkill,
2525
+ analyzeNestjsModuleSkill,
2526
+ analyzeDynamoSchemaSkill,
2527
+ analyzeGraphqlSchemaSkill,
2528
+ findComponentPatternSkill,
2529
+ analyzeComponentSkill,
2530
+ analyzeModuleStructureSkill,
2531
+ findTestPatternSkill,
2532
+ analyzeTestCoverageSkill
2533
+ ];
2534
+ const runtime = await createRuntime({ configPath, skills: skills2 }).catch((err) => {
2535
+ console.error(chalk12.red(`\u2716 Failed to load config: ${err instanceof Error ? err.message : String(err)}`));
2536
+ process.exit(1);
2537
+ });
2538
+ const app = express();
2539
+ app.use(express.json());
2540
+ app.use((_req, res, next) => {
2541
+ res.setHeader("Access-Control-Allow-Origin", "*");
2542
+ res.setHeader("Access-Control-Allow-Headers", "Content-Type");
2543
+ res.setHeader("Access-Control-Allow-Methods", "GET, POST, OPTIONS");
2544
+ next();
2545
+ });
2546
+ app.options("*", (_req, res) => {
2547
+ res.sendStatus(204);
2548
+ });
2549
+ app.get("/health", (_req, res) => {
2550
+ res.json({ status: "ok", version: "0.1.0" });
2551
+ });
2552
+ app.get("/agents", async (_req, res) => {
2553
+ try {
2554
+ const agents = await runtime.listAgents();
2555
+ res.json({ agents });
2556
+ } catch (err) {
2557
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
2558
+ }
2559
+ });
2560
+ app.post("/run", async (req, res) => {
2561
+ const { agent, prompt, session } = req.body;
2562
+ if (!agent || !prompt) {
2563
+ res.status(400).json({ error: "agent and prompt are required" });
2564
+ return;
2565
+ }
2566
+ try {
2567
+ const sessionId = session ? parseSession2(session) : void 0;
2568
+ const result = await runtime.run(agent, prompt, { session: sessionId });
2569
+ res.json({ output: result.output, iterations: result.iterations, usage: result.usage });
2570
+ } catch (err) {
2571
+ res.status(500).json({ error: err instanceof Error ? err.message : String(err) });
2572
+ }
2573
+ });
2574
+ app.post("/run/stream", async (req, res) => {
2575
+ const { agent, prompt, session } = req.body;
2576
+ if (!agent || !prompt) {
2577
+ res.status(400).json({ error: "agent and prompt are required" });
2578
+ return;
2579
+ }
2580
+ res.setHeader("Content-Type", "text/event-stream");
2581
+ res.setHeader("Cache-Control", "no-cache");
2582
+ res.setHeader("Connection", "keep-alive");
2583
+ res.flushHeaders();
2584
+ const sendEvent = (data) => res.write(`data: ${data}
2585
+
2586
+ `);
2587
+ try {
2588
+ const memoryStore = session ? new SQLiteMemoryStore({ path: getDbPath3() }) : void 0;
2589
+ const sessionId = session ? parseSession2(session) : void 0;
2590
+ const result = await runtime.run(agent, prompt, {
2591
+ session: sessionId,
2592
+ onToken: (chunk) => sendEvent(JSON.stringify({ type: "token", content: chunk }))
2593
+ });
2594
+ await memoryStore?.close?.();
2595
+ sendEvent(JSON.stringify({ type: "done", usage: result.usage, iterations: result.iterations }));
2596
+ } catch (err) {
2597
+ sendEvent(JSON.stringify({ type: "error", error: err instanceof Error ? err.message : String(err) }));
2598
+ } finally {
2599
+ res.end();
2600
+ }
2601
+ });
2602
+ app.listen(port, () => {
2603
+ console.log(chalk12.bold.cyan(`
2604
+ \u25C6 omni serve`));
2605
+ console.log(chalk12.gray(` Listening on http://localhost:${port}`));
2606
+ console.log(chalk12.gray(` GET /health`));
2607
+ console.log(chalk12.gray(` GET /agents`));
2608
+ console.log(chalk12.gray(` POST /run \u2014 JSON response`));
2609
+ console.log(chalk12.gray(` POST /run/stream \u2014 SSE stream`));
2610
+ console.log(chalk12.gray("\n Press Ctrl+C to stop.\n"));
2611
+ });
2612
+ await new Promise(() => {
2613
+ });
2614
+ }
2615
+
2616
+ // ../cli/src/commands/watch.ts
2617
+ import chalk13 from "chalk";
2618
+ import chokidar from "chokidar";
2619
+ function buildDebounce(fn, ms) {
2620
+ let timer = null;
2621
+ return () => {
2622
+ if (timer) clearTimeout(timer);
2623
+ timer = setTimeout(fn, ms);
2624
+ };
2625
+ }
2626
+ async function runOnce(agent, prompt, configPath, stream) {
2627
+ const skills2 = [
2628
+ readFileSkill,
2629
+ writeFileSkill,
2630
+ listDirectorySkill,
2631
+ searchCodeSkill,
2632
+ gitStatusSkill,
2633
+ gitDiffSkill,
2634
+ gitLogSkill,
2635
+ gitCommitMessageSkill
2636
+ ];
2637
+ const runtime = await createRuntime({ configPath, skills: skills2 }).catch((err) => {
2638
+ console.error(errorLine(`Failed to load config: ${err instanceof Error ? err.message : String(err)}`));
2639
+ return null;
2640
+ });
2641
+ if (!runtime) return;
2642
+ const providerLabel = `${runtime.config.defaultProvider} / ${runtime.config.providers.find((p) => p.name === runtime.config.defaultProvider)?.defaultModel ?? "default"}`;
2643
+ console.log(`
2644
+ ${agentHeader(agent, providerLabel)}`);
2645
+ const onToken = stream ? (chunk) => process.stdout.write(chunk) : void 0;
2646
+ if (stream) process.stdout.write("\n");
2647
+ const result = await runtime.run(agent, prompt, { onToken }).catch((err) => {
2648
+ console.error(errorLine(err instanceof Error ? err.message : String(err)));
2649
+ return null;
2650
+ });
2651
+ if (!result) return;
2652
+ if (stream) {
2653
+ process.stdout.write("\n");
2654
+ } else {
2655
+ console.log(`
2656
+ ${result.output}
2657
+ `);
2658
+ }
2659
+ console.log(iterationLine(result.iterations));
2660
+ if (result.usage) console.log(tokenSummary(result.usage.inputTokens, result.usage.outputTokens));
2661
+ }
2662
+ async function watchCommand(agent, prompt, opts) {
2663
+ const configPath = opts.config ?? resolveConfigPath();
2664
+ const glob = opts.glob ?? "src/**/*.{ts,js,yaml,json}";
2665
+ const debounceMs = opts.debounce ? Number.parseInt(opts.debounce, 10) : 500;
2666
+ console.log(chalk13.bold.cyan(`
2667
+ \u25C6 omni watch`));
2668
+ console.log(chalk13.gray(` agent: ${agent}`));
2669
+ console.log(chalk13.gray(` glob: ${glob}`));
2670
+ console.log(chalk13.gray(` debounce: ${debounceMs}ms`));
2671
+ console.log(chalk13.gray(" Press Ctrl+C to stop.\n"));
2672
+ await runOnce(agent, prompt, configPath, opts.stream ?? false);
2673
+ const trigger = buildDebounce(async () => {
2674
+ console.log(chalk13.gray(`
2675
+ \u21BB Rerunning ${agent}...`));
2676
+ await runOnce(agent, prompt, configPath, opts.stream ?? false);
2677
+ }, debounceMs);
2678
+ const watcher = chokidar.watch(glob, { ignoreInitial: true, ignored: /node_modules/ });
2679
+ watcher.on("change", (file) => {
2680
+ console.log(chalk13.gray(`
2681
+ \u270E Changed: ${file}`));
2682
+ trigger();
2683
+ });
2684
+ watcher.on("add", (file) => {
2685
+ console.log(chalk13.gray(`
2686
+ + Added: ${file}`));
2687
+ trigger();
2688
+ });
2689
+ watcher.on("error", (err) => {
2690
+ console.error(errorLine(`Watcher error: ${err}`));
2691
+ });
2692
+ process.on("SIGINT", async () => {
2693
+ console.log(chalk13.gray("\n\n Stopping watcher..."));
2694
+ await watcher.close();
2695
+ process.exit(0);
2696
+ });
2697
+ await new Promise(() => {
2698
+ });
2699
+ }
2700
+
2701
+ // ../cli/src/bin.ts
2702
+ var __dirname2 = dirname4(fileURLToPath3(import.meta.url));
2703
+ loadDotenv({ path: resolve17(__dirname2, "..", "..", "..", ".env") });
2704
+ var program = new Command();
2705
+ program.name("omni").description("omni-ai \u2014 provider-agnostic AI agents for developer workflows").version("0.1.0");
2706
+ program.command("run <agent> <prompt>").description("Run an agent with a prompt").option("-c, --config <path>", "Path to omni-ai.yaml").option("-s, --session <id>", "Session ID for memory (format: resourceId:threadId)").option("-o, --output <file>", "Save output to file").option("-v, --verbose", "Show iteration details").option("--stream", "Stream tokens in real time as the agent responds").action(runCommand);
2707
+ program.command("init").description("Interactive setup wizard \u2014 creates omni-ai.yaml and configures .env").action(initCommand);
2708
+ program.command("new").description("Scaffold a new agent, skill or provider from a template").action(newCommand);
2709
+ program.command("chain <prompt> <agents...>").description("Run a sequence of agents, piping each output as the next agent's input").option("-c, --config <path>", "Path to omni-ai.yaml").option("-o, --output <file>", "Save final output to file").option("-v, --verbose", "Print each agent's full output as the chain progresses").option("-s, --stream", "Stream tokens from each agent in real time").action(chainCommand);
2710
+ program.command("export <sessionId>").description("Export a session history as markdown or JSON").option("-f, --format <format>", "Output format: markdown | json (default: markdown)").option("-o, --output <file>", "Save output to file instead of stdout").option("-l, --limit <n>", "Limit to last N messages").action(exportCommand);
2711
+ program.command("watch <agent> <prompt>").description("Re-run an agent automatically when project files change").option("-c, --config <path>", "Path to omni-ai.yaml").option("-g, --glob <pattern>", "Glob of files to watch (default: src/**/*.{ts,js,yaml,json})").option("-d, --debounce <ms>", "Debounce delay in ms (default: 500)").option("--stream", "Stream tokens in real time").action(watchCommand);
2712
+ program.command("serve").description("Start a local HTTP server to run agents via REST or SSE").option("-p, --port <number>", "Port to listen on (default: 3000)").option("-c, --config <path>", "Path to omni-ai.yaml").action(serveCommand);
2713
+ program.command("eval <agent> <dataset>").description("Evaluate an agent against a dataset of (input, expected) pairs").option("-c, --config <path>", "Path to omni-ai.yaml").option("--concurrency <n>", "Number of parallel evaluations (default: 3)").option("-o, --output <file>", "Save JSON report to file").action(evalCommand);
2714
+ var mcp = program.command("mcp").description("MCP (Model Context Protocol) integration");
2715
+ mcp.command("serve").description("Expose all registered skills as MCP tools over stdio").option("-c, --config <path>", "Path to omni-ai.yaml").action(mcpServeCommand);
2716
+ var list = program.command("list").description("List available resources");
2717
+ list.command("agents").description("List all available agents").option("-c, --config <path>", "Path to omni-ai.yaml").action((opts) => listCommand("agents", opts));
2718
+ list.command("skills").description("List registered skills").option("-c, --config <path>", "Path to omni-ai.yaml").action((opts) => listCommand("skills", opts));
2719
+ list.command("providers").description("List registered providers").action((opts) => listCommand("providers", opts));
2720
+ program.parseAsync(process.argv).catch((err) => {
2721
+ console.error(err instanceof Error ? err.message : String(err));
2722
+ process.exit(1);
2723
+ });