@kleber.mottajr/juninho 1.0.0-alpha.1

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 (38) hide show
  1. package/README.md +52 -0
  2. package/dist/cli.d.ts +3 -0
  3. package/dist/cli.d.ts.map +1 -0
  4. package/dist/cli.js +46 -0
  5. package/dist/cli.js.map +1 -0
  6. package/dist/installer.d.ts +5 -0
  7. package/dist/installer.d.ts.map +1 -0
  8. package/dist/installer.js +85 -0
  9. package/dist/installer.js.map +1 -0
  10. package/dist/templates/agents.d.ts +2 -0
  11. package/dist/templates/agents.d.ts.map +1 -0
  12. package/dist/templates/agents.js +510 -0
  13. package/dist/templates/agents.js.map +1 -0
  14. package/dist/templates/commands.d.ts +2 -0
  15. package/dist/templates/commands.d.ts.map +1 -0
  16. package/dist/templates/commands.js +290 -0
  17. package/dist/templates/commands.js.map +1 -0
  18. package/dist/templates/docs.d.ts +3 -0
  19. package/dist/templates/docs.d.ts.map +1 -0
  20. package/dist/templates/docs.js +304 -0
  21. package/dist/templates/docs.js.map +1 -0
  22. package/dist/templates/plugins.d.ts +2 -0
  23. package/dist/templates/plugins.d.ts.map +1 -0
  24. package/dist/templates/plugins.js +470 -0
  25. package/dist/templates/plugins.js.map +1 -0
  26. package/dist/templates/skills.d.ts +2 -0
  27. package/dist/templates/skills.d.ts.map +1 -0
  28. package/dist/templates/skills.js +425 -0
  29. package/dist/templates/skills.js.map +1 -0
  30. package/dist/templates/state.d.ts +2 -0
  31. package/dist/templates/state.d.ts.map +1 -0
  32. package/dist/templates/state.js +97 -0
  33. package/dist/templates/state.js.map +1 -0
  34. package/dist/templates/tools.d.ts +2 -0
  35. package/dist/templates/tools.d.ts.map +1 -0
  36. package/dist/templates/tools.js +424 -0
  37. package/dist/templates/tools.js.map +1 -0
  38. package/package.json +20 -0
@@ -0,0 +1,424 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.writeTools = writeTools;
7
+ const fs_1 = require("fs");
8
+ const path_1 = __importDefault(require("path"));
9
+ function writeTools(projectDir) {
10
+ const toolsDir = path_1.default.join(projectDir, ".opencode", "tools");
11
+ (0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "find-pattern.ts"), FIND_PATTERN);
12
+ (0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "next-version.ts"), NEXT_VERSION);
13
+ (0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "lsp.ts"), LSP);
14
+ (0, fs_1.writeFileSync)(path_1.default.join(toolsDir, "ast-grep.ts"), AST_GREP);
15
+ }
16
+ // ─── Find Pattern ─────────────────────────────────────────────────────────────
17
+ const FIND_PATTERN = `import { tool } from "@opencode-ai/plugin"
18
+ import { z } from "zod"
19
+ import { existsSync, readFileSync } from "fs"
20
+ import path from "path"
21
+
22
+ export const find_pattern = tool({
23
+ name: "find_pattern",
24
+ description: "Find canonical code patterns in the codebase for consistent implementation",
25
+ parameters: z.object({
26
+ patternType: z.enum([
27
+ "api-route",
28
+ "server-action",
29
+ "react-component",
30
+ "prisma-query",
31
+ "error-handler",
32
+ "test-unit",
33
+ "test-integration",
34
+ "zod-schema",
35
+ "middleware",
36
+ ]).describe("The type of pattern to find"),
37
+ cwd: z.string().optional().describe("Working directory (defaults to process.cwd())"),
38
+ }),
39
+ execute: async ({ patternType, cwd: cwdInput }) => {
40
+ const cwd = cwdInput ?? process.cwd()
41
+ const manifestPath = path.join(cwd, "docs", "principles", "manifest")
42
+
43
+ if (existsSync(manifestPath)) {
44
+ const manifest = readFileSync(manifestPath, "utf-8")
45
+ const lines = manifest.split("\\n")
46
+ const section = lines
47
+ .slice(lines.findIndex((l) => l.toLowerCase().includes(patternType)))
48
+ .slice(0, 20)
49
+ .join("\\n")
50
+ if (section.trim()) return { pattern: patternType, example: section }
51
+ }
52
+
53
+ // Fallback patterns
54
+ const FALLBACK_PATTERNS: Record<string, string> = {
55
+ "api-route": \`// app/api/example/route.ts
56
+ import { NextRequest, NextResponse } from "next/server"
57
+
58
+ export async function GET(req: NextRequest) {
59
+ try {
60
+ const data = await fetchData()
61
+ return NextResponse.json({ data })
62
+ } catch (error) {
63
+ return NextResponse.json({ error: "Internal server error" }, { status: 500 })
64
+ }
65
+ }\`,
66
+ "server-action": \`// app/actions/example.ts
67
+ "use server"
68
+ import { revalidatePath } from "next/cache"
69
+ import { z } from "zod"
70
+
71
+ const schema = z.object({ name: z.string().min(1) })
72
+
73
+ export async function createExample(formData: FormData) {
74
+ const result = schema.safeParse(Object.fromEntries(formData))
75
+ if (!result.success) return { error: result.error.flatten() }
76
+ // ... implementation
77
+ revalidatePath("/")
78
+ return { success: true }
79
+ }\`,
80
+ "zod-schema": \`import { z } from "zod"
81
+
82
+ export const ExampleSchema = z.object({
83
+ id: z.string().cuid(),
84
+ name: z.string().min(1).max(100),
85
+ createdAt: z.date(),
86
+ })
87
+
88
+ export type Example = z.infer<typeof ExampleSchema>\`,
89
+ }
90
+
91
+ return {
92
+ pattern: patternType,
93
+ example: FALLBACK_PATTERNS[patternType] ?? "No canonical pattern found. Check docs/principles/manifest.",
94
+ }
95
+ },
96
+ })
97
+ `;
98
+ // ─── Next Version ─────────────────────────────────────────────────────────────
99
+ const NEXT_VERSION = `import { tool } from "@opencode-ai/plugin"
100
+ import { z } from "zod"
101
+ import { existsSync, readdirSync } from "fs"
102
+ import path from "path"
103
+
104
+ export const next_version = tool({
105
+ name: "next_version",
106
+ description: "Get the next version number for migrations or schema files",
107
+ parameters: z.object({
108
+ type: z.enum(["migration", "schema"]).describe("Type of versioned file"),
109
+ cwd: z.string().optional().describe("Working directory"),
110
+ }),
111
+ execute: async ({ type, cwd: cwdInput }) => {
112
+ const cwd = cwdInput ?? process.cwd()
113
+
114
+ const dirs: Record<string, string[]> = {
115
+ migration: [
116
+ "prisma/migrations",
117
+ "db/migrations",
118
+ "migrations",
119
+ "drizzle",
120
+ ],
121
+ schema: [
122
+ "prisma",
123
+ "db",
124
+ "src/db",
125
+ ],
126
+ }
127
+
128
+ for (const dir of dirs[type]) {
129
+ const fullDir = path.join(cwd, dir)
130
+ if (!existsSync(fullDir)) continue
131
+
132
+ const entries = readdirSync(fullDir)
133
+ .filter((e) => /^\\d/.test(e))
134
+ .sort()
135
+
136
+ if (entries.length === 0) {
137
+ return { nextVersion: "0001", dir: fullDir, existing: [] }
138
+ }
139
+
140
+ const lastEntry = entries[entries.length - 1]
141
+ const match = /^(\\d+)/.exec(lastEntry)
142
+ if (!match) continue
143
+
144
+ const lastNum = parseInt(match[1], 10)
145
+ const nextNum = String(lastNum + 1).padStart(match[1].length, "0")
146
+
147
+ return {
148
+ nextVersion: nextNum,
149
+ dir: fullDir,
150
+ existing: entries.slice(-3),
151
+ lastEntry,
152
+ }
153
+ }
154
+
155
+ return {
156
+ nextVersion: "0001",
157
+ dir: "migrations/",
158
+ existing: [],
159
+ note: "No migration directory found. Create one first.",
160
+ }
161
+ },
162
+ })
163
+ `;
164
+ // ─── LSP ──────────────────────────────────────────────────────────────────────
165
+ const LSP = `import { tool } from "@opencode-ai/plugin"
166
+ import { z } from "zod"
167
+ import { execSync } from "child_process"
168
+
169
+ // LSP tools via typescript-language-server CLI
170
+ // Falls back to tsc for type checking if LSP not available
171
+
172
+ function runTsc(cwd: string, args: string): string {
173
+ try {
174
+ return execSync(\`npx tsc \${args}\`, { cwd, encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] })
175
+ } catch (e: any) {
176
+ return e.stdout ?? e.message ?? "tsc failed"
177
+ }
178
+ }
179
+
180
+ export const lsp_diagnostics = tool({
181
+ name: "lsp_diagnostics",
182
+ description: "Get TypeScript diagnostics (errors and warnings) for a file or directory",
183
+ parameters: z.object({
184
+ path: z.string().describe("File or directory to check"),
185
+ severity: z.enum(["error", "warning", "info"]).optional().default("error"),
186
+ }),
187
+ execute: async ({ path: targetPath, severity }) => {
188
+ const output = runTsc(process.cwd(), \`--noEmit --pretty false 2>&1 | grep "\${targetPath}"\`)
189
+ const lines = output.split("\\n").filter((l) => {
190
+ if (severity === "error") return l.includes("error TS")
191
+ if (severity === "warning") return l.includes("warning TS")
192
+ return l.trim().length > 0
193
+ })
194
+ return { diagnostics: lines, count: lines.length }
195
+ },
196
+ })
197
+
198
+ export const lsp_goto_definition = tool({
199
+ name: "lsp_goto_definition",
200
+ description: "Find where a symbol is defined",
201
+ parameters: z.object({
202
+ file: z.string().describe("Source file path"),
203
+ line: z.number().describe("Line number (1-indexed)"),
204
+ character: z.number().describe("Character position (0-indexed)"),
205
+ }),
206
+ execute: async ({ file, line, character }) => {
207
+ // Use grep as fallback for definition finding
208
+ try {
209
+ const content = require("fs").readFileSync(file, "utf-8")
210
+ const lines = content.split("\\n")
211
+ const targetLine = lines[line - 1] ?? ""
212
+ // Extract symbol at position
213
+ const before = targetLine.slice(0, character)
214
+ const after = targetLine.slice(character)
215
+ const symbolMatch = /[\\w$]+$/.exec(before)
216
+ const symbolEnd = /^[\\w$]*/.exec(after)
217
+ const symbol = (symbolMatch?.[0] ?? "") + (symbolEnd?.[0] ?? "")
218
+ return { symbol, hint: \`Search for 'export.*\${symbol}|function \${symbol}|class \${symbol}|const \${symbol}'\` }
219
+ } catch {
220
+ return { error: "Could not read file" }
221
+ }
222
+ },
223
+ })
224
+
225
+ export const lsp_find_references = tool({
226
+ name: "lsp_find_references",
227
+ description: "Find all references to a symbol across the codebase",
228
+ parameters: z.object({
229
+ file: z.string(),
230
+ line: z.number(),
231
+ character: z.number(),
232
+ includeDeclaration: z.boolean().optional().default(true),
233
+ }),
234
+ execute: async ({ file, line, character }) => {
235
+ try {
236
+ const content = require("fs").readFileSync(file, "utf-8")
237
+ const lineContent = content.split("\\n")[line - 1] ?? ""
238
+ const before = lineContent.slice(0, character)
239
+ const after = lineContent.slice(character)
240
+ const symbol = (/[\\w$]+$/.exec(before)?.[0] ?? "") + (/^[\\w$]*/.exec(after)?.[0] ?? "")
241
+ if (!symbol) return { error: "No symbol at position" }
242
+ const result = execSync(\`grep -rn --include="*.ts" --include="*.tsx" "\${symbol}" .\`, {
243
+ encoding: "utf-8",
244
+ stdio: ["pipe", "pipe", "pipe"],
245
+ })
246
+ const refs = result.split("\\n").filter(Boolean)
247
+ return { symbol, references: refs.slice(0, 20), total: refs.length }
248
+ } catch (e: any) {
249
+ return { references: [], note: e.stdout ?? "No references found" }
250
+ }
251
+ },
252
+ })
253
+
254
+ export const lsp_document_symbols = tool({
255
+ name: "lsp_document_symbols",
256
+ description: "Get all symbols (functions, classes, variables) in a file",
257
+ parameters: z.object({ file: z.string() }),
258
+ execute: async ({ file }) => {
259
+ try {
260
+ const content = require("fs").readFileSync(file, "utf-8")
261
+ const lines = content.split("\\n")
262
+ const symbols: Array<{ line: number; kind: string; name: string }> = []
263
+
264
+ for (let i = 0; i < lines.length; i++) {
265
+ const line = lines[i]
266
+ const patterns: Array<[RegExp, string]> = [
267
+ [/^export\\s+(async\\s+)?function\\s+(\\w+)/, "function"],
268
+ [/^export\\s+(const|let|var)\\s+(\\w+)/, "variable"],
269
+ [/^export\\s+(default\\s+)?class\\s+(\\w+)/, "class"],
270
+ [/^export\\s+(type|interface)\\s+(\\w+)/, "type"],
271
+ [/^\\s+(async\\s+)?(\\w+)\\s*\\(/, "method"],
272
+ ]
273
+ for (const [pattern, kind] of patterns) {
274
+ const match = pattern.exec(line)
275
+ if (match) {
276
+ symbols.push({ line: i + 1, kind, name: match[match.length - 1] })
277
+ break
278
+ }
279
+ }
280
+ }
281
+ return { file, symbols }
282
+ } catch {
283
+ return { error: "Could not read file" }
284
+ }
285
+ },
286
+ })
287
+
288
+ export const lsp_workspace_symbols = tool({
289
+ name: "lsp_workspace_symbols",
290
+ description: "Search for symbols by name across the entire workspace",
291
+ parameters: z.object({
292
+ query: z.string().describe("Symbol name or pattern to search"),
293
+ file: z.string().optional().describe("Any file in workspace (for language server context)"),
294
+ }),
295
+ execute: async ({ query }) => {
296
+ try {
297
+ const result = execSync(
298
+ \`grep -rn --include="*.ts" --include="*.tsx" -E "export.*(function|class|const|interface|type).*\${query}" .\`,
299
+ { encoding: "utf-8", stdio: ["pipe", "pipe", "pipe"] }
300
+ )
301
+ return { query, matches: result.split("\\n").filter(Boolean).slice(0, 15) }
302
+ } catch (e: any) {
303
+ return { query, matches: [], note: "No matches found" }
304
+ }
305
+ },
306
+ })
307
+
308
+ export const lsp_rename = tool({
309
+ name: "lsp_rename",
310
+ description: "Preview rename of a symbol across all files (dry-run only — apply with sed/Edit)",
311
+ parameters: z.object({
312
+ file: z.string(),
313
+ line: z.number(),
314
+ character: z.number(),
315
+ newName: z.string(),
316
+ }),
317
+ execute: async ({ file, line, character, newName }) => {
318
+ try {
319
+ const content = require("fs").readFileSync(file, "utf-8")
320
+ const lineContent = content.split("\\n")[line - 1] ?? ""
321
+ const before = lineContent.slice(0, character)
322
+ const after = lineContent.slice(character)
323
+ const oldName = (/[\\w$]+$/.exec(before)?.[0] ?? "") + (/^[\\w$]*/.exec(after)?.[0] ?? "")
324
+ if (!oldName) return { error: "No symbol at position" }
325
+
326
+ const result = execSync(\`grep -rln --include="*.ts" --include="*.tsx" "\\\\b\${oldName}\\\\b" .\`, {
327
+ encoding: "utf-8",
328
+ stdio: ["pipe", "pipe", "pipe"],
329
+ })
330
+ const files = result.split("\\n").filter(Boolean)
331
+ return {
332
+ oldName,
333
+ newName,
334
+ affectedFiles: files,
335
+ command: \`grep -rl "\\\\b\${oldName}\\\\b" . | xargs sed -i 's/\\\\b\${oldName}\\\\b/\${newName}/g'\`,
336
+ note: "This is a preview. Run the command above to apply the rename.",
337
+ }
338
+ } catch (e: any) {
339
+ return { error: e.message }
340
+ }
341
+ },
342
+ })
343
+ `;
344
+ // ─── AST Grep ─────────────────────────────────────────────────────────────────
345
+ const AST_GREP = `import { tool } from "@opencode-ai/plugin"
346
+ import { z } from "zod"
347
+ import { execSync } from "child_process"
348
+
349
+ function runAstGrep(args: string): { output: string; error?: string } {
350
+ try {
351
+ const output = execSync(\`ast-grep \${args}\`, {
352
+ encoding: "utf-8",
353
+ stdio: ["pipe", "pipe", "pipe"],
354
+ })
355
+ return { output }
356
+ } catch (e: any) {
357
+ // ast-grep may not be installed
358
+ if (e.code === "ENOENT" || e.message?.includes("not found")) {
359
+ return { output: "", error: "ast-grep not installed. Run: npm install -g @ast-grep/cli" }
360
+ }
361
+ return { output: e.stdout ?? "", error: e.stderr ?? e.message }
362
+ }
363
+ }
364
+
365
+ export const ast_grep_search = tool({
366
+ name: "ast_grep_search",
367
+ description: "Search for code patterns using AST matching. More precise than text search. Use meta-variables: $NAME (single node), $$$ARGS (multiple nodes).",
368
+ parameters: z.object({
369
+ pattern: z.string().describe("AST pattern with meta-variables. E.g.: 'console.log($MSG)', 'function $NAME($$$ARGS)'"),
370
+ language: z.enum(["typescript", "javascript", "tsx", "python", "rust", "go"]).default("typescript"),
371
+ path: z.string().optional().describe("Directory or file to search (defaults to current directory)"),
372
+ maxResults: z.number().optional().default(20),
373
+ }),
374
+ execute: async ({ pattern, language, path: searchPath, maxResults }) => {
375
+ const pathArg = searchPath ? \`--dir "\${searchPath}"\` : ""
376
+ const { output, error } = runAstGrep(\`scan --pattern '\${pattern}' --lang \${language} \${pathArg} --json\`)
377
+
378
+ if (error) return { error, pattern }
379
+
380
+ try {
381
+ const results = JSON.parse(output || "[]")
382
+ return {
383
+ pattern,
384
+ language,
385
+ matches: results.slice(0, maxResults),
386
+ total: results.length,
387
+ }
388
+ } catch {
389
+ return { pattern, output: output.slice(0, 2000) }
390
+ }
391
+ },
392
+ })
393
+
394
+ export const ast_grep_replace = tool({
395
+ name: "ast_grep_replace",
396
+ description: "Replace code patterns using AST matching. Use meta-variables in both pattern and replacement.",
397
+ parameters: z.object({
398
+ pattern: z.string().describe("Pattern to match (use meta-variables like $NAME, $$$ARGS)"),
399
+ replacement: z.string().describe("Replacement pattern (use same meta-variables)"),
400
+ language: z.enum(["typescript", "javascript", "tsx", "python", "rust", "go"]).default("typescript"),
401
+ path: z.string().optional().describe("Directory or file to transform"),
402
+ dryRun: z.boolean().optional().default(true).describe("Preview changes without applying (default: true)"),
403
+ }),
404
+ execute: async ({ pattern, replacement, language, path: targetPath, dryRun }) => {
405
+ const pathArg = targetPath ? \`--dir "\${targetPath}"\` : ""
406
+ const dryRunArg = dryRun ? "--dry-run" : ""
407
+
408
+ const { output, error } = runAstGrep(
409
+ \`scan --pattern '\${pattern}' --rewrite '\${replacement}' --lang \${language} \${pathArg} \${dryRunArg}\`
410
+ )
411
+
412
+ if (error) return { error, pattern, replacement }
413
+
414
+ return {
415
+ pattern,
416
+ replacement,
417
+ dryRun,
418
+ output: output.slice(0, 3000),
419
+ note: dryRun ? "Dry run — no files modified. Set dryRun: false to apply changes." : "Changes applied.",
420
+ }
421
+ },
422
+ })
423
+ `;
424
+ //# sourceMappingURL=tools.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.js","sourceRoot":"","sources":["../../src/templates/tools.ts"],"names":[],"mappings":";;;;;AAGA,gCAOC;AAVD,2BAAkC;AAClC,gDAAuB;AAEvB,SAAgB,UAAU,CAAC,UAAkB;IAC3C,MAAM,QAAQ,GAAG,cAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,EAAE,OAAO,CAAC,CAAA;IAE5D,IAAA,kBAAa,EAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,YAAY,CAAC,CAAA;IACnE,IAAA,kBAAa,EAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,iBAAiB,CAAC,EAAE,YAAY,CAAC,CAAA;IACnE,IAAA,kBAAa,EAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,GAAG,CAAC,CAAA;IACjD,IAAA,kBAAa,EAAC,cAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,aAAa,CAAC,EAAE,QAAQ,CAAC,CAAA;AAC7D,CAAC;AAED,iFAAiF;AAEjF,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgFpB,CAAA;AAED,iFAAiF;AAEjF,MAAM,YAAY,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgEpB,CAAA;AAED,iFAAiF;AAEjF,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAkLX,CAAA;AAED,iFAAiF;AAEjF,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA8EhB,CAAA"}
package/package.json ADDED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "@kleber.mottajr/juninho",
3
+ "version": "1.0.0-alpha.1",
4
+ "description": "Bootstrap the Agentic Coding Framework into any OpenCode project",
5
+ "bin": { "juninho": "dist/cli.js" },
6
+ "main": "dist/cli.js",
7
+ "scripts": {
8
+ "build": "tsc",
9
+ "dev": "tsc --watch",
10
+ "prepublishOnly": "npm run build"
11
+ },
12
+ "files": ["dist"],
13
+ "keywords": ["opencode", "agents", "framework", "cli"],
14
+ "license": "MIT",
15
+ "dependencies": {},
16
+ "devDependencies": {
17
+ "typescript": "^5.4.0",
18
+ "@types/node": "^20.0.0"
19
+ }
20
+ }