Module: Aircana::CLI::KB

Defined in:
lib/aircana/cli/commands/kb.rb

Overview

rubocop:disable Metrics/ModuleLength

Class Method Summary collapse

Class Method Details

.add_url(kb_name, url) ⇒ Object

rubocop:disable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/aircana/cli/commands/kb.rb', line 88

def add_url(kb_name, url)
  normalized_kb_name = normalize_string(kb_name)

  unless kb_exists?(normalized_kb_name)
    Aircana.human_logger.error "KB '#{kb_name}' not found. Use 'aircana kb list' to see available KBs."
    exit 1
  end

  # Get kb_type from manifest
  kb_type = Aircana::Contexts::Manifest.kb_type_from_manifest(normalized_kb_name)

  web = Aircana::Contexts::Web.new
  result = web.fetch_url_for(kb_name: normalized_kb_name, url: url, kb_type: kb_type)

  if result
    # Update manifest with the new URL
    existing_sources = Aircana::Contexts::Manifest.sources_from_manifest(normalized_kb_name)
    web_sources = existing_sources.select { |s| s["type"] == "web" }
    other_sources = existing_sources.reject { |s| s["type"] == "web" }

    if web_sources.any?
      # Add to existing web source
      web_sources.first["urls"] << result
    else
      # Create new web source
      web_sources = [{ "type" => "web", "urls" => [result] }]
    end

    all_sources = other_sources + web_sources
    Aircana::Contexts::Manifest.update_manifest(normalized_kb_name, all_sources)

    # Regenerate SKILL.md and agent
    regenerate_skill_md(normalized_kb_name)
    regenerate_agent_md(normalized_kb_name)

    Aircana.human_logger.success "Successfully added URL to KB '#{kb_name}'"
  else
    Aircana.human_logger.error "Failed to fetch URL: #{url}"
    exit 1
  end
rescue Aircana::Error => e
  Aircana.human_logger.error "Failed to add URL: #{e.message}"
  exit 1
end

.createObject

rubocop:enable Metrics/MethodLength



33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/aircana/cli/commands/kb.rb', line 33

def create # rubocop:disable Metrics/MethodLength
  prompt = TTY::Prompt.new

  kb_name = prompt.ask("What topic should this knowledge base cover?",
                       default: "e.g., 'Canvas Backend Database', 'API Design'")
  short_description = prompt.ask("Briefly describe what this KB contains:")

  # Prompt for knowledge base type
  kb_type = prompt.select("Knowledge base type:", [
                            {
                              name: "Local - Version controlled, no refresh needed",
                              value: "local"
                            },
                            {
                              name: "Remote - Fetched from Confluence/web, " \
                                    "auto-refreshed via SessionStart hook",
                              value: "remote"
                            }
                          ])

  normalized_kb_name = normalize_string(kb_name)

  # Prompt for knowledge fetching
  fetched_confluence = prompt_for_knowledge_fetch(prompt, normalized_kb_name, kb_type, short_description)

  # Prompt for web URL fetching
  fetched_urls = prompt_for_url_fetch(prompt, normalized_kb_name, kb_type)

  # Generate SKILL.md and agent if no content was fetched during the prompts
  # (the prompt functions already generate it when they successfully fetch content)
  unless fetched_confluence || fetched_urls
    regenerate_skill_md(normalized_kb_name, short_description)
    regenerate_agent_md(normalized_kb_name)
  end

  # If remote kb_type, ensure SessionStart hook is installed
  ensure_remote_knowledge_refresh_hook if kb_type == "remote"

  # Ensure gitignore is configured
  ensure_gitignore_entry(kb_type)

  Aircana.human_logger.success "Knowledge base '#{kb_name}' setup complete!"
end

.listObject



77
78
79
80
81
82
83
84
85
# File 'lib/aircana/cli/commands/kb.rb', line 77

def list
  kb_dir = Aircana.configuration.kb_knowledge_dir
  return print_no_kbs_message unless Dir.exist?(kb_dir)

  kb_folders = find_kb_folders(kb_dir)
  return print_no_kbs_message if kb_folders.empty?

  print_kbs_list(kb_folders)
end

.refresh(kb_name) ⇒ Object

rubocop:disable Metrics/MethodLength



15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# File 'lib/aircana/cli/commands/kb.rb', line 15

def refresh(kb_name)
  normalized_kb_name = normalize_string(kb_name)

  # Check if this is a local knowledge base
  kb_type = Aircana::Contexts::Manifest.kb_type_from_manifest(normalized_kb_name)
  if kb_type == "local"
    Aircana.human_logger.info "⊘ Skipping #{normalized_kb_name} (local knowledge base - no refresh needed)"
    return
  end

  perform_manifest_aware_refresh(normalized_kb_name)
  regenerate_skill_md(normalized_kb_name)
  regenerate_agent_md(normalized_kb_name)
rescue Aircana::Error => e
  handle_refresh_error(normalized_kb_name, e)
end

.refresh_allObject

rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/PerceivedComplexity



134
135
136
137
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
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/aircana/cli/commands/kb.rb', line 134

def refresh_all # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
  kb_names = all_kbs

  if kb_names.empty?
    Aircana.human_logger.info "No knowledge bases found to refresh."
    return
  end

  Aircana.human_logger.info "Starting refresh for #{kb_names.size} KB(s)..."

  results = {
    total: kb_names.size,
    successful: 0,
    failed: 0,
    skipped: 0,
    total_pages: 0,
    failed_kbs: [],
    skipped_kbs: []
  }

  kb_names.each do |kb_name|
    # Check if this is a local knowledge base
    kb_type = Aircana::Contexts::Manifest.kb_type_from_manifest(kb_name)
    if kb_type == "local"
      Aircana.human_logger.info "⊘ Skipping #{kb_name} (local knowledge base)"
      results[:skipped] += 1
      results[:skipped_kbs] << kb_name
      next
    end

    result = refresh_single_kb(kb_name)
    if result[:success]
      results[:successful] += 1
      results[:total_pages] += result[:pages_count]
    else
      results[:failed] += 1
      results[:failed_kbs] << { name: kb_name, error: result[:error] }
    end
  end

  print_refresh_all_summary(results)
end