package-names.ts 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. #!/usr/bin/env bun
  2. /**
  3. * Transform package names and branding from opencode to kilo
  4. *
  5. * This script transforms:
  6. * - opencode-ai -> @kilocode/cli
  7. * - @opencode-ai/cli -> @kilocode/cli
  8. * - @opencode-ai/sdk -> @kilocode/sdk
  9. * - @opencode-ai/plugin -> @kilocode/plugin
  10. * - OPENCODE_* -> KILO_* (env variables, excluding OPENCODE_API_KEY)
  11. * - x-opencode-* -> x-kilo-* (HTTP headers)
  12. * - opencode.db -> kilo.db (database filename)
  13. * - window.__OPENCODE__ -> window.__KILO__ (window global)
  14. */
  15. import { Glob } from "bun"
  16. import { info, success, warn, debug } from "../utils/logger"
  17. import { defaultConfig } from "../utils/config"
  18. export interface TransformResult {
  19. file: string
  20. changes: number
  21. dryRun: boolean
  22. }
  23. export interface TransformOptions {
  24. dryRun?: boolean
  25. verbose?: boolean
  26. }
  27. const PACKAGE_PATTERNS = [
  28. // In package.json name field
  29. { pattern: /"name":\s*"opencode-ai"/, replacement: '"name": "@kilocode/cli"' },
  30. { pattern: /"name":\s*"@opencode-ai\/cli"/, replacement: '"name": "@kilocode/cli"' },
  31. // In dependencies/devDependencies
  32. { pattern: /"opencode-ai":\s*"/g, replacement: '"@kilocode/cli": "' },
  33. { pattern: /"@opencode-ai\/cli":\s*"/g, replacement: '"@kilocode/cli": "' },
  34. { pattern: /"@opencode-ai\/sdk":\s*"/g, replacement: '"@kilocode/sdk": "' },
  35. { pattern: /"@opencode-ai\/plugin":\s*"/g, replacement: '"@kilocode/plugin": "' },
  36. // In any string context (mock.module, dynamic references, etc.)
  37. // Only cli, sdk, and plugin are renamed — other @opencode-ai/* packages
  38. // (e.g. @opencode-ai/ui, @opencode-ai/util) keep their upstream names.
  39. { pattern: /@opencode-ai\/cli(?=\/|"|'|`|$)/g, replacement: "@kilocode/cli" },
  40. { pattern: /@opencode-ai\/sdk(?=\/|"|'|`|$)/g, replacement: "@kilocode/sdk" },
  41. { pattern: /@opencode-ai\/plugin(?=\/|"|'|`|$)/g, replacement: "@kilocode/plugin" },
  42. // In import statements (supports subpaths like @opencode-ai/sdk/v2)
  43. { pattern: /from\s+["']opencode-ai["']/g, replacement: 'from "@kilocode/cli"' },
  44. { pattern: /from\s+["']@opencode-ai\/cli(\/[^"']*)?["']/g, replacement: 'from "@kilocode/cli$1"' },
  45. { pattern: /from\s+["']@opencode-ai\/sdk(\/[^"']*)?["']/g, replacement: 'from "@kilocode/sdk$1"' },
  46. { pattern: /from\s+["']@opencode-ai\/plugin(\/[^"']*)?["']/g, replacement: 'from "@kilocode/plugin$1"' },
  47. // In require statements (supports subpaths like @opencode-ai/sdk/v2)
  48. { pattern: /require\(["']opencode-ai["']\)/g, replacement: 'require("@kilocode/cli")' },
  49. { pattern: /require\(["']@opencode-ai\/cli(\/[^"']*)?["']\)/g, replacement: 'require("@kilocode/cli$1")' },
  50. { pattern: /require\(["']@opencode-ai\/sdk(\/[^"']*)?["']\)/g, replacement: 'require("@kilocode/sdk$1")' },
  51. { pattern: /require\(["']@opencode-ai\/plugin(\/[^"']*)?["']\)/g, replacement: 'require("@kilocode/plugin$1")' },
  52. // Internal placeholder hostname used for in-process RPC (never resolved by DNS)
  53. { pattern: /opencode\.internal/g, replacement: "kilo.internal" },
  54. // In npx/npm commands
  55. { pattern: /npx opencode-ai/g, replacement: "npx @kilocode/cli" },
  56. { pattern: /npm install opencode-ai/g, replacement: "npm install @kilocode/cli" },
  57. { pattern: /bun add opencode-ai/g, replacement: "bun add @kilocode/cli" },
  58. // SDK public API renames (Opencode → Kilo)
  59. // Order matters: longer names first to avoid partial matches
  60. { pattern: /OpencodeClientConfig/g, replacement: "KiloClientConfig" },
  61. { pattern: /createOpencodeClient/g, replacement: "createKiloClient" },
  62. { pattern: /createOpencodeServer/g, replacement: "createKiloServer" },
  63. { pattern: /createOpencodeTui/g, replacement: "createKiloTui" },
  64. { pattern: /OpencodeClient/g, replacement: "KiloClient" },
  65. // createOpencode (without suffix) needs negative lookahead to avoid matching createOpencodeClient
  66. { pattern: /\bcreateOpencode\b(?!Client|Server|Tui)/g, replacement: "createKilo" },
  67. // Branding: environment variables (exclude OPENCODE_API_KEY — upstream Zen SaaS key)
  68. { pattern: /\bOPENCODE_(?!API_KEY\b)([A-Z_]+)\b/g, replacement: "KILO_$1" },
  69. { pattern: /VITE_OPENCODE_/g, replacement: "VITE_KILO_" },
  70. { pattern: /_EXTENSION_OPENCODE_/g, replacement: "_EXTENSION_KILO_" },
  71. // Branding: HTTP header prefix
  72. { pattern: /x-opencode-/g, replacement: "x-kilo-" },
  73. // Branding: window global
  74. { pattern: /window\.__OPENCODE__/g, replacement: "window.__KILO__" },
  75. // Branding: database filename
  76. { pattern: /opencode\.db/g, replacement: "kilo.db" },
  77. ]
  78. /**
  79. * Transform package names in a single file
  80. */
  81. export async function transformFile(filePath: string, options: TransformOptions = {}): Promise<TransformResult> {
  82. const file = Bun.file(filePath)
  83. let content = await file.text()
  84. const original = content
  85. let changes = 0
  86. for (const { pattern, replacement } of PACKAGE_PATTERNS) {
  87. const regex = typeof pattern === "string" ? new RegExp(pattern, "g") : pattern
  88. const newContent = content.replace(regex, replacement)
  89. if (newContent !== content) {
  90. const count = (content.match(regex) || []).length
  91. changes += count
  92. content = newContent
  93. }
  94. }
  95. if (changes > 0 && !options.dryRun) {
  96. await Bun.write(filePath, content)
  97. }
  98. return {
  99. file: filePath,
  100. changes,
  101. dryRun: options.dryRun ?? false,
  102. }
  103. }
  104. /**
  105. * Transform package names in all relevant files
  106. */
  107. export async function transformAll(options: TransformOptions = {}): Promise<TransformResult[]> {
  108. const results: TransformResult[] = []
  109. // Find all relevant files
  110. const patterns = ["**/*.ts", "**/*.tsx", "**/*.js", "**/*.jsx", "**/*.json", "**/*.md"]
  111. const excludes = defaultConfig.excludePatterns
  112. for (const pattern of patterns) {
  113. const glob = new Glob(pattern)
  114. for await (const path of glob.scan({ absolute: true })) {
  115. // Skip excluded paths
  116. if (excludes.some((ex) => path.includes(ex.replace(/\*\*/g, "")))) {
  117. continue
  118. }
  119. const result = await transformFile(path, options)
  120. if (result.changes > 0) {
  121. results.push(result)
  122. if (options.dryRun) {
  123. info(`[DRY-RUN] Would transform ${result.file}: ${result.changes} changes`)
  124. } else {
  125. success(`Transformed ${result.file}: ${result.changes} changes`)
  126. }
  127. }
  128. }
  129. }
  130. return results
  131. }
  132. // CLI entry point
  133. if (import.meta.main) {
  134. const args = process.argv.slice(2)
  135. const dryRun = args.includes("--dry-run")
  136. const verbose = args.includes("--verbose")
  137. if (dryRun) {
  138. info("Running in dry-run mode (no files will be modified)")
  139. }
  140. const results = await transformAll({ dryRun, verbose })
  141. console.log()
  142. success(`Transformed ${results.length} files`)
  143. if (dryRun) {
  144. info("Run without --dry-run to apply changes")
  145. }
  146. }