Module: Tina4::AI

Defined in:
lib/tina4/ai.rb

Overview

Tina4 AI – Install AI coding assistant context files.

Simple menu-driven installer for AI tool context files. The user picks which tools they use, we install the appropriate files.

selection = Tina4::AI.show_menu(".")
Tina4::AI.install_selected(".", selection)

Constant Summary collapse

AI_TOOLS =

Ordered list of supported AI tools

[
  { name: "claude-code", description: "Claude Code", context_file: "CLAUDE.md", config_dir: ".claude" },
  { name: "cursor", description: "Cursor", context_file: ".cursorules", config_dir: ".cursor" },
  { name: "copilot", description: "GitHub Copilot", context_file: ".github/copilot-instructions.md", config_dir: ".github" },
  { name: "windsurf", description: "Windsurf", context_file: ".windsurfrules", config_dir: nil },
  { name: "aider", description: "Aider", context_file: "CONVENTIONS.md", config_dir: nil },
  { name: "cline", description: "Cline", context_file: ".clinerules", config_dir: nil },
  { name: "codex", description: "OpenAI Codex", context_file: "AGENTS.md", config_dir: nil }
].freeze

Class Method Summary collapse

Class Method Details

.generate_context(tool_name = "claude-code") ⇒ String

Generate per-tool Tina4 Ruby context document.

Parameters:

  • tool_name (String) (defaults to: "claude-code")

    AI tool name (default: “claude-code”)

Returns:

  • (String)


111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/tina4/ai.rb', line 111

def generate_context(tool_name = "claude-code")
  case tool_name
  when "claude-code"
    generate_claude_code_context
  when "cursor"
    generate_cursor_context
  when "copilot"
    generate_copilot_context
  when "windsurf"
    generate_windsurf_context
  when "aider"
    generate_aider_context
  when "cline"
    generate_cline_context
  when "codex"
    generate_codex_context
  else
    generate_claude_code_context
  end
end

.install_all(root = ".") ⇒ Array<String>

Install context for all AI tools (non-interactive).

Parameters:

  • root (String) (defaults to: ".")

    project root directory

Returns:

  • (Array<String>)

    list of created/updated file paths



103
104
105
# File 'lib/tina4/ai.rb', line 103

def install_all(root = ".")
  install_selected(root, "all")
end

.install_claude_skills(root) ⇒ Array<String>

Copy Claude Code skill files from the framework’s templates.

Parameters:

  • root (String)

    absolute project root path

Returns:

  • (Array<String>)

    list of created/updated relative file paths



168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/tina4/ai.rb', line 168

def install_claude_skills(root)
  created = []

  # Determine the framework root (where lib/tina4/ lives)
  framework_root = File.expand_path("../../..", __FILE__)

  # Copy skill directories from .claude/skills/ in the framework to the project
  framework_skills_dir = File.join(framework_root, ".claude", "skills")
  if Dir.exist?(framework_skills_dir)
    target_skills_dir = File.join(root, ".claude", "skills")
    FileUtils.mkdir_p(target_skills_dir)
    Dir.children(framework_skills_dir).each do |entry|
      skill_dir = File.join(framework_skills_dir, entry)
      next unless File.directory?(skill_dir)

      target_dir = File.join(target_skills_dir, entry)
      FileUtils.rm_rf(target_dir) if Dir.exist?(target_dir)
      FileUtils.cp_r(skill_dir, target_dir)
      rel = target_dir.sub("#{root}/", "")
      created << rel
      puts "  \e[32m✓\e[0m Updated #{rel}"
    end
  end

  # Copy claude-commands if they exist
  commands_source = File.join(framework_root, "templates", "ai", "claude-commands")
  if Dir.exist?(commands_source)
    commands_dir = File.join(root, ".claude", "commands")
    FileUtils.mkdir_p(commands_dir)
    Dir.glob(File.join(commands_source, "*.md")).each do |skill_file|
      target = File.join(commands_dir, File.basename(skill_file))
      FileUtils.cp(skill_file, target)
      rel = target.sub("#{root}/", "")
      created << rel
    end
  end

  created
end

.install_for_tool(root, tool, context) ⇒ Array<String>

Install context file for a single tool.

Parameters:

  • root (String)

    absolute project root path

  • tool (Hash)

    tool entry from AI_TOOLS

  • context (String)

    generated context content

Returns:

  • (Array<String>)

    list of created/updated relative file paths



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
# File 'lib/tina4/ai.rb', line 138

def install_for_tool(root, tool, context)
  created = []
  context_path = File.join(root, tool[:context_file])

  # Create directories
  if tool[:config_dir]
    FileUtils.mkdir_p(File.join(root, tool[:config_dir]))
  end
  FileUtils.mkdir_p(File.dirname(context_path))

  # Always overwrite -- user chose to install
  action = File.exist?(context_path) ? "Updated" : "Installed"
  File.write(context_path, context)
  rel = context_path.sub("#{root}/", "")
  created << rel
  puts "  \e[32m✓\e[0m #{action} #{rel}"

  # Claude-specific extras
  if tool[:name] == "claude-code"
    skills = install_claude_skills(root)
    created.concat(skills)
  end

  created
end

.install_selected(root, selection) ⇒ Array<String>

Install context files for the selected tools.

Parameters:

  • root (String)

    project root directory

  • selection (String)

    comma-separated numbers like “1,2,3” or “all”

Returns:

  • (Array<String>)

    list of created/updated file paths



66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
# File 'lib/tina4/ai.rb', line 66

def install_selected(root, selection)
  root_path = File.expand_path(root)
  created = []

  if selection.downcase == "all"
    indices = (0...AI_TOOLS.length).to_a
    do_tina4_ai = true
  else
    parts = selection.split(",").map(&:strip).reject(&:empty?)
    indices = []
    do_tina4_ai = false
    parts.each do |p|
      n = Integer(p) rescue next
      if n == 8
        do_tina4_ai = true
      elsif n >= 1 && n <= AI_TOOLS.length
        indices << (n - 1)
      end
    end
  end

  indices.each do |idx|
    tool = AI_TOOLS[idx]
    context = generate_context(tool[:name])
    files = install_for_tool(root_path, tool, context)
    created.concat(files)
  end

  install_tina4_ai if do_tina4_ai

  created
end

.install_tina4_aiObject

Install tina4-ai package (provides mdview for markdown viewing).



209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
# File 'lib/tina4/ai.rb', line 209

def install_tina4_ai
  puts "  Installing tina4-ai tools..."
  %w[pip3 pip].each do |cmd|
    next unless system("which #{cmd} > /dev/null 2>&1")

    result = `#{cmd} install --upgrade tina4-ai 2>&1`
    if $?.success?
      puts "  \e[32m✓\e[0m Installed tina4-ai (mdview)"
      return
    else
      puts "  \e[33m!\e[0m #{cmd} failed: #{result.strip[0..100]}"
    end
  end
  puts "  \e[33m!\e[0m Python/pip not available -- skip tina4-ai"
end

.is_installed(root, tool) ⇒ Boolean

Check if a tool’s context file already exists.

Parameters:

  • root (String)

    project root directory

  • tool (Hash)

    tool entry from AI_TOOLS

Returns:

  • (Boolean)


32
33
34
# File 'lib/tina4/ai.rb', line 32

def is_installed(root, tool)
  File.exist?(File.join(File.expand_path(root), tool[:context_file]))
end

.show_menu(root = ".") ⇒ String

Print the numbered menu and return user input.

Parameters:

  • root (String) (defaults to: ".")

    project root directory (default: “.”)

Returns:

  • (String)

    user input (comma-separated numbers or “all”)



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/tina4/ai.rb', line 40

def show_menu(root = ".")
  root = File.expand_path(root)
  green = "\e[32m"
  reset = "\e[0m"

  puts "\n  Tina4 AI Context Installer\n"
  AI_TOOLS.each_with_index do |tool, i|
    marker = is_installed(root, tool) ? "  #{green}[installed]#{reset}" : ""
    puts format("  %d. %-20s %s%s", i + 1, tool[:description], tool[:context_file], marker)
  end

  # tina4-ai tools option
  tina4_ai_installed = system("which mdview > /dev/null 2>&1")
  marker = tina4_ai_installed ? "  #{green}[installed]#{reset}" : ""
  puts "  8. Install tina4-ai tools  (requires Python)#{marker}"
  puts

  print "  Select (comma-separated, or 'all'): "
  $stdin.gets&.strip || ""
end