Class: Clacky::CLI

Inherits:
Thor
  • Object
show all
Defined in:
lib/clacky/cli.rb

Class Method Summary collapse

Instance Method Summary collapse

Class Method Details

.exit_on_failure?Boolean

Returns:

  • (Boolean)


13
14
15
# File 'lib/clacky/cli.rb', line 13

def self.exit_on_failure?
  true
end

Instance Method Details

#agentObject



60
61
62
63
64
65
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
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
# File 'lib/clacky/cli.rb', line 60

def agent
  # Handle help option
  if options[:help]
    invoke :help, ["agent"]
    return
  end
  agent_config = Clacky::AgentConfig.load

  # Handle session listing
  if options[:list]
    list_sessions
    return
  end

  # Handle Ctrl+C gracefully - raise exception to be caught in the loop
  Signal.trap("INT") do
    Thread.main.raise(Clacky::AgentInterrupted, "Interrupted by user")
  end

  # Validate and get working directory
  working_dir = validate_working_directory(options[:path])

  # Update agent config with CLI options
  agent_config.permission_mode = options[:mode].to_sym if options[:mode]
  agent_config.verbose = options[:verbose] if options[:verbose]

  # Create client for current model
  client = Clacky::Client.new(agent_config.api_key, base_url: agent_config.base_url, model: agent_config.model_name, anthropic_format: agent_config.anthropic_format?)

  # Resolve agent profile name from --agent option
  agent_profile = options[:agent] || "coding"

  # Handle session loading/continuation
  session_manager = Clacky::SessionManager.new
  agent = nil
  is_session_load = false

  if options[:continue]
    agent = load_latest_session(client, agent_config, session_manager, working_dir, profile: agent_profile)
    is_session_load = !agent.nil?
  elsif options[:attach]
    agent = load_session_by_number(client, agent_config, session_manager, working_dir, options[:attach], profile: agent_profile)
    is_session_load = !agent.nil?
  end

  # Create new agent if no session loaded
  if agent.nil?
    agent = Clacky::Agent.new(client, agent_config, working_dir: working_dir, ui: nil, profile: agent_profile,
                              session_id: Clacky::SessionManager.generate_id, source: :manual)
    agent.rename("CLI Session")
  end

  # Change to working directory
  original_dir = Dir.pwd
  should_chdir = File.realpath(working_dir) != File.realpath(original_dir)
  Dir.chdir(working_dir) if should_chdir
  begin
    if options[:message]
      file_paths = Array(options[:file]) + Array(options[:image])
      run_non_interactive(agent, options[:message], file_paths, agent_config, session_manager)
    elsif options[:json]
      run_agent_with_json(agent, working_dir, agent_config, session_manager, client, profile: agent_profile)
    else
      run_agent_with_ui2(agent, working_dir, agent_config, session_manager, client, is_session_load: is_session_load)
    end
  ensure
    Dir.chdir(original_dir)
  end
end

#serverObject



811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
# File 'lib/clacky/cli.rb', line 811

def server
  if ENV["CLACKY_WORKER"] == "1"
    # ── Worker mode ───────────────────────────────────────────────────────
    # Spawned by Master. Inherit the listen socket from the file descriptor
    # passed via CLACKY_INHERIT_FD, and report back to master via CLACKY_MASTER_PID.
    require_relative "server/http_server"

    fd         = ENV["CLACKY_INHERIT_FD"].to_i
    master_pid = ENV["CLACKY_MASTER_PID"].to_i
    # Must use TCPServer.for_fd (not Socket.for_fd) so that accept_nonblock
    # returns a single Socket, not [Socket, Addrinfo] — WEBrick expects the former.
    socket     = TCPServer.for_fd(fd)

    Clacky::Logger.console = true
    Clacky::Logger.info("[cli worker PID=#{Process.pid}] CLACKY_INHERIT_FD=#{fd} CLACKY_MASTER_PID=#{master_pid} socket=#{socket.class} fd=#{socket.fileno}")

    agent_config = Clacky::AgentConfig.load
    agent_config.permission_mode = :confirm_all

    client_factory = lambda do
      Clacky::Client.new(
        agent_config.api_key,
        base_url: agent_config.base_url,
        model: agent_config.model_name,
        anthropic_format: agent_config.anthropic_format?
      )
    end

    Clacky::Server::HttpServer.new(
      host:           options[:host],
      port:           options[:port],
      agent_config:   agent_config,
      client_factory: client_factory,
      brand_test:     options[:brand_test],
      socket:         socket,
      master_pid:     master_pid
    ).start
  else
    # ── Master mode ───────────────────────────────────────────────────────
    # First invocation by the user. Start the Master process which holds the
    # socket and supervises worker processes.
    require_relative "server/server_master"

    if options[:brand_test]
      say "⚡ Brand test mode — license activation uses mock data (no remote API calls).", :yellow
      say ""
      say "  Test license keys (paste any into Settings → Brand & License):", :cyan
      say ""
      say "    00000001-FFFFFFFF-DEADBEEF-CAFEBABE-00000001  →  Brand1"
      say "    00000002-FFFFFFFF-DEADBEEF-CAFEBABE-00000002  →  Brand2"
      say "    00000003-FFFFFFFF-DEADBEEF-CAFEBABE-00000003  →  Brand3"
      say ""
      say "  To reset: rm ~/.clacky/brand.yml", :cyan
      say ""
    end

    extra_flags = []
    extra_flags << "--brand-test" if options[:brand_test]

    Clacky::Logger.console = true

    Clacky::Server::Master.new(
      host:        options[:host],
      port:        options[:port],
      extra_flags: extra_flags
    ).run
  end
end