Module: Brainiac::Plugins::Basecamp::Client

Defined in:
lib/brainiac/plugins/basecamp/client.rb

Overview

Wrapper around the basecamp CLI binary. Shells out with --json for structured responses.

Class Method Summary collapse

Class Method Details

.add_comment(recording_id, content, project:) ⇒ Hash?

Add a comment to a recording (todo, card, message, etc.)

Parameters:

  • recording_id (String, Integer)

    The recording to comment on

  • content (String)

    Comment content (Markdown)

  • project (String, Integer)

    Basecamp project/bucket ID

Returns:

  • (Hash, nil)


116
117
118
# File 'lib/brainiac/plugins/basecamp/client.rb', line 116

def add_comment(recording_id, content, project:)
  run("comments", "create", recording_id.to_s, content, "--in", project.to_s, "--json")
end

.auth_statusHash?

Check auth status.

Returns:

  • (Hash, nil)


146
147
148
# File 'lib/brainiac/plugins/basecamp/client.rb', line 146

def auth_status
  run_safe("auth", "status", "--json")
end

.clear_agent_profileObject

Clear the current agent profile.



22
23
24
# File 'lib/brainiac/plugins/basecamp/client.rb', line 22

def clear_agent_profile
  @current_agent_profile = nil
end

.complete_subtask(step_id, project:) ⇒ Hash?

Complete a subtask.

Parameters:

  • step_id (String, Integer)

    Step/subtask ID

  • project (String, Integer)

    Basecamp project/bucket ID

Returns:

  • (Hash, nil)


103
104
105
106
107
108
# File 'lib/brainiac/plugins/basecamp/client.rb', line 103

def complete_subtask(step_id, project:)
  # Use raw API to complete a step
  run("api", "put",
      "/buckets/#{project}/card_tables/steps/#{step_id}/completions.json",
      "--data", '{"completion":"on"}', "--json")
end

.complete_todo(todo_id) ⇒ Hash?

Complete a todo.

Parameters:

  • todo_id (String, Integer)

    Todo ID

Returns:

  • (Hash, nil)


124
125
126
# File 'lib/brainiac/plugins/basecamp/client.rb', line 124

def complete_todo(todo_id)
  run("todos", "complete", todo_id.to_s, "--json")
end

.get_subtasks(todo_id, project:) ⇒ Hash?

List subtasks (steps) for a todo.

Parameters:

  • todo_id (String, Integer)

    Parent todo ID

  • project (String, Integer)

    Basecamp project/bucket ID

Returns:

  • (Hash, nil)


91
92
93
94
95
96
# File 'lib/brainiac/plugins/basecamp/client.rb', line 91

def get_subtasks(todo_id, project:)
  # Basecamp models todo subtasks as Kanban::Step records
  # Use the recordings list filtered by type and parent
  run("recordings", "list", "--in", project.to_s, "--type", "Kanban::Step",
      "--all", "--json")
end

.get_todo(todo_id, project:) ⇒ Hash?

Get a todo by ID with subtasks info.

Parameters:

  • todo_id (String, Integer)

    Todo ID

  • project (String, Integer)

    Basecamp project/bucket ID

Returns:

  • (Hash, nil)


82
83
84
# File 'lib/brainiac/plugins/basecamp/client.rb', line 82

def get_todo(todo_id, project:)
  run("todos", "show", todo_id.to_s, "--in", project.to_s, "--json")
end

.list_projectsHash

List projects.

Returns:

  • (Hash)


131
132
133
# File 'lib/brainiac/plugins/basecamp/client.rb', line 131

def list_projects
  run("projects", "list", "--json")
end

.parse_url(url) ⇒ Hash?

Parse a Basecamp URL to extract IDs.

Parameters:

  • url (String)

    Basecamp URL

Returns:

  • (Hash, nil)


139
140
141
# File 'lib/brainiac/plugins/basecamp/client.rb', line 139

def parse_url(url)
  run("url", "parse", url, "--json")
end

.run(*args, profile: nil) ⇒ Hash

Run a basecamp CLI command and return parsed JSON.

Parameters:

  • args (Array<String>)

    CLI arguments

  • profile (String, nil) (defaults to: nil)

    Named profile to use (defaults to agent name)

Returns:

  • (Hash)

    Parsed JSON response

Raises:



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/brainiac/plugins/basecamp/client.rb', line 40

def run(*args, profile: nil)
  cmd = ["basecamp"]
  # Use the specified profile, or fall back to the current agent's name as profile
  profile ||= @current_agent_profile
  cmd += ["--profile", profile] if profile
  cmd += args.flatten

  LOG.debug "[Basecamp:Client] Running: #{cmd.join(' ')}" if defined?(LOG) && LOG.debug?

  stdout, stderr, status = Open3.capture3(*cmd)

  unless status.success?
    error_body = parse_json_safe(stdout) || parse_json_safe(stderr)
    error_msg = error_body&.dig("error") || stderr.strip.split("\n").first || "Unknown error"
    raise ClientError.new(error_msg, exit_code: status.exitstatus, response: error_body)
  end

  result = parse_json_safe(stdout)
  unless result
    raise ClientError.new("Failed to parse JSON response", exit_code: 0, response: nil)
  end

  result
end

.run_as(agent_name, *args) ⇒ Hash

Run a basecamp CLI command as a specific agent.

Parameters:

  • agent_name (String)

    Agent name (used as profile)

  • args (Array<String>)

    CLI arguments

Returns:

  • (Hash)

    Parsed JSON response



31
32
33
# File 'lib/brainiac/plugins/basecamp/client.rb', line 31

def run_as(agent_name, *args)
  run(*args, profile: agent_name.downcase)
end

.run_safe(*args, profile: nil) ⇒ Hash?

Run a basecamp CLI command, returning nil on failure instead of raising.

Parameters:

  • args (Array<String>)

    CLI arguments

  • profile (String, nil) (defaults to: nil)

    Named profile to use

Returns:

  • (Hash, nil)

    Parsed JSON response or nil



70
71
72
73
74
75
# File 'lib/brainiac/plugins/basecamp/client.rb', line 70

def run_safe(*args, profile: nil)
  run(*args, profile: profile)
rescue ClientError => e
  LOG.warn "[Basecamp:Client] Command failed: #{e.message}" if defined?(LOG)
  nil
end

.with_agent_profile(agent_name) ⇒ Object

Set the current agent profile for basecamp CLI commands. The profile name matches the agent name (e.g., "galen" → basecamp --profile galen).

Parameters:

  • agent_name (String, nil)

    Agent name to use as profile



17
18
19
# File 'lib/brainiac/plugins/basecamp/client.rb', line 17

def with_agent_profile(agent_name)
  @current_agent_profile = agent_name&.downcase
end