baid 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/baid +6 -0
- data/lib/baid/agent_detector.rb +21 -0
- data/lib/baid/api_client.rb +29 -0
- data/lib/baid/cli.rb +80 -0
- data/lib/baid/commands/init.rb +32 -0
- data/lib/baid/commands/install.rb +38 -0
- data/lib/baid/commands/login.rb +49 -0
- data/lib/baid/commands/logout.rb +16 -0
- data/lib/baid/commands/reconfigure.rb +28 -0
- data/lib/baid/commands/search.rb +29 -0
- data/lib/baid/commands/skills.rb +89 -0
- data/lib/baid/commands/update.rb +35 -0
- data/lib/baid/commands/whoami.rb +32 -0
- data/lib/baid/commands/workspace.rb +31 -0
- data/lib/baid/config.rb +44 -0
- data/lib/baid/mcp_configurator.rb +42 -0
- data/lib/baid/skill_writer.rb +51 -0
- data/lib/baid.rb +28 -0
- metadata +140 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 83bacc2e8f9264fa7203aa6b9c9d1ded8a18974c63c72fbd3ef4f0e0ae6f7a76
|
|
4
|
+
data.tar.gz: '094836c43eab0251dc04fc8738b3b885b57cda69edb7197704c123a40d0450c1'
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2f39c86e23698bd6883c23521050f745ffbea60547c235a5d0bcb7083f575b8ef77361faac640169354403fdfbdcb77071352ce5330773b892faccc355420d68
|
|
7
|
+
data.tar.gz: b392ac3a82514828f91f3eb59cc77699b8c3842cbdefca33969c9763ac528180e3396037420c390d50915936af9fa6f46c59fcdf9b47a0d7105854206b838111
|
data/bin/baid
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
class AgentDetector
|
|
5
|
+
AGENTS = [
|
|
6
|
+
{ name: "claude_code", marker: ".claude", config_path: ".mcp.json" },
|
|
7
|
+
{ name: "cursor", marker: ".cursor", config_path: ".cursor/mcp.json" },
|
|
8
|
+
{ name: "antigravity", marker: ".agent", config_path: ".agent/settings.json" },
|
|
9
|
+
{ name: "opencode", marker: ".opencode", config_path: ".opencode/config.json" },
|
|
10
|
+
{ name: "github_copilot", marker: ".vscode", config_path: ".vscode/mcp.json" },
|
|
11
|
+
{ name: "gemini", marker: ".gemini", config_path: ".gemini/settings.json" },
|
|
12
|
+
{ name: "codex", marker: ".codex", config_path: ".codex/config.toml" }
|
|
13
|
+
].freeze
|
|
14
|
+
|
|
15
|
+
def self.detect(project_dir = ".")
|
|
16
|
+
AGENTS.select do |agent|
|
|
17
|
+
Dir.exist?(File.join(project_dir, agent[:marker]))
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
class ApiClient
|
|
5
|
+
BASE_URL = ENV.fetch("BAID_API_URL", "https://baid.dev")
|
|
6
|
+
|
|
7
|
+
def initialize(token: nil)
|
|
8
|
+
@token = token || Config.load_token
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def get(path, params = {})
|
|
12
|
+
HTTParty.get("#{BASE_URL}/api#{path}",
|
|
13
|
+
query: params,
|
|
14
|
+
headers: auth_headers)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def post(path, body = {})
|
|
18
|
+
HTTParty.post("#{BASE_URL}/api#{path}",
|
|
19
|
+
body: body.to_json,
|
|
20
|
+
headers: auth_headers.merge("Content-Type" => "application/json"))
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def auth_headers
|
|
26
|
+
{ "Authorization" => "Bearer #{@token}" }
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
data/lib/baid/cli.rb
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
class CLI < Thor
|
|
5
|
+
desc "version", "Print the Baid CLI version"
|
|
6
|
+
def version
|
|
7
|
+
puts Baid::VERSION
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
desc "login", "Authenticate with BaidSkills"
|
|
11
|
+
def login
|
|
12
|
+
Commands::Login.new.execute
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "logout", "Remove stored credentials"
|
|
16
|
+
def logout
|
|
17
|
+
Commands::Logout.new.execute
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
desc "whoami", "Show current user and workspace info"
|
|
21
|
+
def whoami
|
|
22
|
+
Commands::Whoami.new.execute
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
desc "init", "Initialize Baid in the current project"
|
|
26
|
+
def init
|
|
27
|
+
Commands::Init.new.execute
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
desc "reconfigure", "Remove and re-initialize Baid configuration"
|
|
31
|
+
def reconfigure
|
|
32
|
+
Commands::Reconfigure.new.execute
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
desc "install SKILL_NAME", "Install a skill into your project"
|
|
36
|
+
def install(skill_name)
|
|
37
|
+
Commands::Install.new.execute(skill_name)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
desc "search QUERY", "Search for skills in the registry"
|
|
41
|
+
def search(query)
|
|
42
|
+
Commands::Search.new.execute(query)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
desc "update", "Update all installed skills to latest versions"
|
|
46
|
+
def update
|
|
47
|
+
Commands::Update.new.execute
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
desc "skills SUBCOMMAND", "Manage workspace skills"
|
|
51
|
+
subcommand "skills", Class.new(Thor) {
|
|
52
|
+
namespace "skills"
|
|
53
|
+
|
|
54
|
+
desc "list [WORKSPACE]", "List skills in a workspace (defaults to active workspace)"
|
|
55
|
+
def list(workspace_slug = nil)
|
|
56
|
+
Commands::Skills.new.list(workspace_slug)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
desc "show [WORKSPACE] SKILL_ID", "Show skill details"
|
|
60
|
+
def show(workspace_slug, skill_id = nil)
|
|
61
|
+
Commands::Skills.new.show(workspace_slug, skill_id)
|
|
62
|
+
end
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
desc "workspace SUBCOMMAND", "Manage workspaces"
|
|
66
|
+
subcommand "workspace", Class.new(Thor) {
|
|
67
|
+
namespace "workspace"
|
|
68
|
+
|
|
69
|
+
desc "list", "List all workspaces"
|
|
70
|
+
def list
|
|
71
|
+
Commands::Workspace.new.list
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
desc "switch SLUG", "Switch active workspace"
|
|
75
|
+
def switch(slug)
|
|
76
|
+
Commands::Workspace.new.switch(slug)
|
|
77
|
+
end
|
|
78
|
+
}
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Init
|
|
6
|
+
def initialize(project_dir: ".")
|
|
7
|
+
@project_dir = project_dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute
|
|
11
|
+
agents = AgentDetector.detect(@project_dir)
|
|
12
|
+
|
|
13
|
+
if agents.empty?
|
|
14
|
+
puts "No agents detected in this project."
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
agents.each do |agent|
|
|
19
|
+
McpConfigurator.configure(agent, @project_dir)
|
|
20
|
+
puts "Configured MCP for #{agent[:name]}"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
config_path = File.join(@project_dir, ".baid", "config.yml")
|
|
24
|
+
FileUtils.mkdir_p(File.dirname(config_path))
|
|
25
|
+
config = { "agents" => agents.map { |a| a[:name] }, "installed_skills" => [] }
|
|
26
|
+
File.write(config_path, config.to_yaml)
|
|
27
|
+
|
|
28
|
+
puts "Baid initialized with #{agents.length} agent(s)."
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Install
|
|
6
|
+
def initialize(project_dir: ".")
|
|
7
|
+
@project_dir = project_dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute(skill_name)
|
|
11
|
+
client = ApiClient.new
|
|
12
|
+
response = client.get("/skills/resolve", name: skill_name)
|
|
13
|
+
|
|
14
|
+
if response.code != 200
|
|
15
|
+
puts "Skill '#{skill_name}' not found."
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
data = JSON.parse(response.body)
|
|
20
|
+
skill = data["skill"]
|
|
21
|
+
|
|
22
|
+
config = Config.load_project_config
|
|
23
|
+
agents = (config["agents"] || []).map do |name|
|
|
24
|
+
AgentDetector::AGENTS.find { |a| a[:name] == name }
|
|
25
|
+
end.compact
|
|
26
|
+
|
|
27
|
+
SkillWriter.write(skill, agents, @project_dir)
|
|
28
|
+
|
|
29
|
+
# Update installed_skills in config
|
|
30
|
+
config["installed_skills"] ||= []
|
|
31
|
+
config["installed_skills"] << skill["name"] unless config["installed_skills"].include?(skill["name"])
|
|
32
|
+
Config.save_project_config(config)
|
|
33
|
+
|
|
34
|
+
puts "Installed #{skill['name']}@#{skill['version']}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Login
|
|
6
|
+
def execute
|
|
7
|
+
response = HTTParty.post("#{ApiClient::BASE_URL}/api/auth/device",
|
|
8
|
+
headers: { "Content-Type" => "application/json" })
|
|
9
|
+
|
|
10
|
+
data = JSON.parse(response.body)
|
|
11
|
+
device_code = data["device_code"]
|
|
12
|
+
user_code = data["user_code"]
|
|
13
|
+
verification_uri = data["verification_uri"]
|
|
14
|
+
interval = data["interval"] || 5
|
|
15
|
+
|
|
16
|
+
puts "Open #{verification_uri} and enter code: #{user_code}"
|
|
17
|
+
open_browser(verification_uri)
|
|
18
|
+
|
|
19
|
+
token = poll_for_token(device_code, interval)
|
|
20
|
+
Config.save_token(token)
|
|
21
|
+
puts "Logged in successfully!"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def poll_for_token(device_code, interval)
|
|
27
|
+
loop do
|
|
28
|
+
response = HTTParty.post("#{ApiClient::BASE_URL}/api/auth/device/token",
|
|
29
|
+
body: { device_code: device_code }.to_json,
|
|
30
|
+
headers: { "Content-Type" => "application/json" })
|
|
31
|
+
|
|
32
|
+
data = JSON.parse(response.body)
|
|
33
|
+
|
|
34
|
+
if data["access_token"]
|
|
35
|
+
return data["access_token"]
|
|
36
|
+
elsif data["error"] == "authorization_pending"
|
|
37
|
+
sleep(interval)
|
|
38
|
+
else
|
|
39
|
+
raise "Authentication failed: #{data['error']}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def open_browser(url)
|
|
45
|
+
system("open", url) || system("xdg-open", url)
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Logout
|
|
6
|
+
def execute
|
|
7
|
+
if File.exist?(Config::CREDENTIALS_FILE)
|
|
8
|
+
File.delete(Config::CREDENTIALS_FILE)
|
|
9
|
+
puts "Logged out successfully."
|
|
10
|
+
else
|
|
11
|
+
puts "Not logged in."
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Reconfigure
|
|
6
|
+
def initialize(project_dir: ".")
|
|
7
|
+
@project_dir = project_dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute
|
|
11
|
+
puts "Reconfiguring Baid..."
|
|
12
|
+
|
|
13
|
+
# Remove existing baid entries from all agent configs
|
|
14
|
+
agents = AgentDetector.detect(@project_dir)
|
|
15
|
+
agents.each do |agent|
|
|
16
|
+
McpConfigurator.remove(agent, @project_dir)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Remove .baid directory
|
|
20
|
+
baid_dir = File.join(@project_dir, ".baid")
|
|
21
|
+
FileUtils.rm_rf(baid_dir)
|
|
22
|
+
|
|
23
|
+
# Re-initialize
|
|
24
|
+
Init.new(project_dir: @project_dir).execute
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Search
|
|
6
|
+
def execute(query)
|
|
7
|
+
client = ApiClient.new
|
|
8
|
+
response = client.get("/skills/search", q: query)
|
|
9
|
+
data = JSON.parse(response.body)
|
|
10
|
+
skills = data["skills"] || []
|
|
11
|
+
|
|
12
|
+
if skills.empty?
|
|
13
|
+
puts "No skills found for '#{query}'."
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
puts format("%-25s %-40s %-10s %s", "NAME", "DESCRIPTION", "VERSION", "WORKSPACE")
|
|
18
|
+
puts "-" * 90
|
|
19
|
+
skills.each do |skill|
|
|
20
|
+
puts format("%-25s %-40s %-10s %s",
|
|
21
|
+
skill["name"],
|
|
22
|
+
(skill["description"] || "")[0..38],
|
|
23
|
+
skill["version"],
|
|
24
|
+
skill["workspace"])
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Skills
|
|
6
|
+
def list(workspace_slug = nil)
|
|
7
|
+
workspace_slug = resolve_workspace(workspace_slug)
|
|
8
|
+
return unless workspace_slug
|
|
9
|
+
|
|
10
|
+
client = ApiClient.new
|
|
11
|
+
response = client.get("/workspaces/#{workspace_slug}/skills")
|
|
12
|
+
|
|
13
|
+
unless response.code == 200
|
|
14
|
+
puts "Failed to fetch skills (#{response.code}). Check your token with `baid whoami`."
|
|
15
|
+
return
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
data = JSON.parse(response.body)
|
|
19
|
+
skills = data["skills"] || []
|
|
20
|
+
|
|
21
|
+
if skills.empty?
|
|
22
|
+
puts "No skills in workspace."
|
|
23
|
+
return
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
puts format("%-25s %-15s %s", "NAME", "SOURCE", "VERSION")
|
|
27
|
+
puts "-" * 50
|
|
28
|
+
skills.each do |skill|
|
|
29
|
+
puts format("%-25s %-15s %s", skill["name"], skill["source"], skill["latest_version"])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def show(workspace_slug, skill_id = nil)
|
|
34
|
+
# If called with one arg, treat it as skill_id and resolve workspace
|
|
35
|
+
if skill_id.nil?
|
|
36
|
+
skill_id = workspace_slug
|
|
37
|
+
workspace_slug = resolve_workspace(nil)
|
|
38
|
+
return unless workspace_slug
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
client = ApiClient.new
|
|
42
|
+
response = client.get("/workspaces/#{workspace_slug}/skills/#{skill_id}")
|
|
43
|
+
|
|
44
|
+
unless response.code == 200
|
|
45
|
+
puts "Failed to fetch skill (#{response.code}). Check your token with `baid whoami`."
|
|
46
|
+
return
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
data = JSON.parse(response.body)
|
|
50
|
+
|
|
51
|
+
puts "Name: #{data['name']}"
|
|
52
|
+
puts "Description: #{data['description']}"
|
|
53
|
+
puts "Source: #{data['source']}"
|
|
54
|
+
puts "Version: #{data['latest_version']}"
|
|
55
|
+
puts "Agents: #{(data['agents'] || []).join(', ')}"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
private
|
|
59
|
+
|
|
60
|
+
def resolve_workspace(workspace_slug)
|
|
61
|
+
return workspace_slug if workspace_slug
|
|
62
|
+
|
|
63
|
+
slug = Config.load_active_workspace
|
|
64
|
+
return slug if slug
|
|
65
|
+
|
|
66
|
+
token = Config.load_token
|
|
67
|
+
unless token
|
|
68
|
+
puts "Not logged in. Run `baid login` first."
|
|
69
|
+
return nil
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Fall back to first workspace from whoami
|
|
73
|
+
client = ApiClient.new
|
|
74
|
+
response = client.get("/whoami")
|
|
75
|
+
if response.code == 200
|
|
76
|
+
data = JSON.parse(response.body)
|
|
77
|
+
ws = data["workspace"]
|
|
78
|
+
if ws && ws["slug"]
|
|
79
|
+
Config.save_active_workspace(ws["slug"])
|
|
80
|
+
return ws["slug"]
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
puts "No active workspace. Run `baid workspace switch SLUG` first."
|
|
85
|
+
nil
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Update
|
|
6
|
+
def initialize(project_dir: ".")
|
|
7
|
+
@project_dir = project_dir
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def execute
|
|
11
|
+
config = Config.load_project_config
|
|
12
|
+
installed = config["installed_skills"] || []
|
|
13
|
+
|
|
14
|
+
if installed.empty?
|
|
15
|
+
puts "No skills installed."
|
|
16
|
+
return
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
client = ApiClient.new
|
|
20
|
+
agents = (config["agents"] || []).map do |name|
|
|
21
|
+
AgentDetector::AGENTS.find { |a| a[:name] == name }
|
|
22
|
+
end.compact
|
|
23
|
+
|
|
24
|
+
installed.each do |skill_name|
|
|
25
|
+
response = client.get("/skills/resolve", name: skill_name)
|
|
26
|
+
next if response.code != 200
|
|
27
|
+
|
|
28
|
+
data = JSON.parse(response.body)
|
|
29
|
+
SkillWriter.write(data["skill"], agents, @project_dir)
|
|
30
|
+
puts "Updated #{skill_name}@#{data['skill']['version']}"
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Whoami
|
|
6
|
+
def execute
|
|
7
|
+
token = Config.load_token
|
|
8
|
+
unless token
|
|
9
|
+
puts "Not logged in. Run `baid login` first."
|
|
10
|
+
return
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
client = ApiClient.new(token: token)
|
|
14
|
+
response = client.get("/whoami")
|
|
15
|
+
|
|
16
|
+
if response.code != 200
|
|
17
|
+
puts "Failed to fetch user info (#{response.code})."
|
|
18
|
+
return
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
data = JSON.parse(response.body)
|
|
22
|
+
user = data["user"]
|
|
23
|
+
workspace = data["workspace"]
|
|
24
|
+
|
|
25
|
+
puts "\e[32m✔\e[0m You are logged in to baid"
|
|
26
|
+
puts "User: #{user['name']} (#{user['email']})"
|
|
27
|
+
puts "Workspace: #{workspace['name']} (#{workspace['slug']})" if workspace
|
|
28
|
+
puts "Run \e[36mbaid logout\e[0m to log out"
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
module Commands
|
|
5
|
+
class Workspace
|
|
6
|
+
def list
|
|
7
|
+
client = ApiClient.new
|
|
8
|
+
response = client.get("/workspaces")
|
|
9
|
+
data = JSON.parse(response.body)
|
|
10
|
+
workspaces = data["workspaces"] || []
|
|
11
|
+
|
|
12
|
+
if workspaces.empty?
|
|
13
|
+
puts "No workspaces found."
|
|
14
|
+
return
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
puts format("%-25s %-25s %s", "NAME", "SLUG", "ROLE")
|
|
18
|
+
puts "-" * 60
|
|
19
|
+
workspaces.each do |ws|
|
|
20
|
+
puts format("%-25s %-25s %s", ws["name"], ws["slug"], ws["role"])
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def switch(slug)
|
|
25
|
+
FileUtils.mkdir_p(Config::CONFIG_DIR)
|
|
26
|
+
File.write(Config::ACTIVE_WORKSPACE_FILE, slug)
|
|
27
|
+
puts "Switched to workspace '#{slug}'."
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/baid/config.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
class Config
|
|
5
|
+
CONFIG_DIR = File.expand_path("~/.baid")
|
|
6
|
+
CREDENTIALS_FILE = File.join(CONFIG_DIR, "credentials")
|
|
7
|
+
ACTIVE_WORKSPACE_FILE = File.join(CONFIG_DIR, "active_workspace")
|
|
8
|
+
PROJECT_CONFIG = ".baid/config.yml"
|
|
9
|
+
|
|
10
|
+
def self.load_token
|
|
11
|
+
return nil unless File.exist?(CREDENTIALS_FILE)
|
|
12
|
+
|
|
13
|
+
File.read(CREDENTIALS_FILE).strip
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.save_token(token)
|
|
17
|
+
FileUtils.mkdir_p(CONFIG_DIR)
|
|
18
|
+
File.write(CREDENTIALS_FILE, token)
|
|
19
|
+
FileUtils.chmod(0o600, CREDENTIALS_FILE)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.load_active_workspace
|
|
23
|
+
return nil unless File.exist?(ACTIVE_WORKSPACE_FILE)
|
|
24
|
+
|
|
25
|
+
File.read(ACTIVE_WORKSPACE_FILE).strip.then { |s| s.empty? ? nil : s }
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def self.save_active_workspace(slug)
|
|
29
|
+
FileUtils.mkdir_p(CONFIG_DIR)
|
|
30
|
+
File.write(ACTIVE_WORKSPACE_FILE, slug)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def self.load_project_config
|
|
34
|
+
return {} unless File.exist?(PROJECT_CONFIG)
|
|
35
|
+
|
|
36
|
+
YAML.safe_load(File.read(PROJECT_CONFIG)) || {}
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def self.save_project_config(config)
|
|
40
|
+
FileUtils.mkdir_p(File.dirname(PROJECT_CONFIG))
|
|
41
|
+
File.write(PROJECT_CONFIG, config.to_yaml)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
class McpConfigurator
|
|
5
|
+
BAID_MCP_ENTRY = {
|
|
6
|
+
"command" => "npx",
|
|
7
|
+
"args" => ["-y", "@baid/mcp-server"],
|
|
8
|
+
"env" => {}
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
def self.configure(agent, project_dir = ".")
|
|
12
|
+
config_path = File.join(project_dir, agent[:config_path])
|
|
13
|
+
FileUtils.mkdir_p(File.dirname(config_path))
|
|
14
|
+
|
|
15
|
+
config = load_config(config_path)
|
|
16
|
+
config["mcpServers"] ||= {}
|
|
17
|
+
config["mcpServers"]["baid"] = BAID_MCP_ENTRY.dup
|
|
18
|
+
|
|
19
|
+
File.write(config_path, JSON.pretty_generate(config))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.remove(agent, project_dir = ".")
|
|
23
|
+
config_path = File.join(project_dir, agent[:config_path])
|
|
24
|
+
return unless File.exist?(config_path)
|
|
25
|
+
|
|
26
|
+
config = load_config(config_path)
|
|
27
|
+
config["mcpServers"]&.delete("baid")
|
|
28
|
+
|
|
29
|
+
File.write(config_path, JSON.pretty_generate(config))
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def self.load_config(path)
|
|
33
|
+
return {} unless File.exist?(path)
|
|
34
|
+
|
|
35
|
+
JSON.parse(File.read(path))
|
|
36
|
+
rescue JSON::ParserError
|
|
37
|
+
{}
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private_class_method :load_config
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Baid
|
|
4
|
+
class SkillWriter
|
|
5
|
+
AGENT_PATHS = {
|
|
6
|
+
"claude_code" => proc { |name| File.join(".claude", "skills", "#{name}.md") },
|
|
7
|
+
"cursor" => proc { |name| File.join(".cursor", "rules", "#{name}.md") },
|
|
8
|
+
"antigravity" => proc { |name| File.join(".agent", "skills", name, "SKILL.md") },
|
|
9
|
+
"opencode" => proc { |name| File.join(".opencode", "rules", "#{name}.md") }
|
|
10
|
+
}.freeze
|
|
11
|
+
|
|
12
|
+
def self.write(skill_data, agents, project_dir = ".")
|
|
13
|
+
name = skill_data["name"]
|
|
14
|
+
content = skill_data["content"]
|
|
15
|
+
|
|
16
|
+
agents.each do |agent|
|
|
17
|
+
path_proc = AGENT_PATHS[agent[:name]]
|
|
18
|
+
next unless path_proc
|
|
19
|
+
|
|
20
|
+
relative_path = path_proc.call(name)
|
|
21
|
+
full_path = File.join(project_dir, relative_path)
|
|
22
|
+
FileUtils.mkdir_p(File.dirname(full_path))
|
|
23
|
+
|
|
24
|
+
file_content = if agent[:name] == "antigravity"
|
|
25
|
+
frontmatter = { "name" => name, "version" => skill_data["version"] }.to_yaml
|
|
26
|
+
"#{frontmatter}---\n\n#{content}"
|
|
27
|
+
else
|
|
28
|
+
content
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
File.write(full_path, file_content)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def self.remove(name, agents, project_dir = ".")
|
|
36
|
+
agents.each do |agent|
|
|
37
|
+
path_proc = AGENT_PATHS[agent[:name]]
|
|
38
|
+
next unless path_proc
|
|
39
|
+
|
|
40
|
+
full_path = File.join(project_dir, path_proc.call(name))
|
|
41
|
+
FileUtils.rm_f(full_path)
|
|
42
|
+
|
|
43
|
+
# Clean up empty parent directories for antigravity
|
|
44
|
+
if agent[:name] == "antigravity"
|
|
45
|
+
parent = File.dirname(full_path)
|
|
46
|
+
FileUtils.rm_rf(parent) if Dir.exist?(parent) && Dir.empty?(parent)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/baid.rb
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "httparty"
|
|
5
|
+
require "json"
|
|
6
|
+
require "yaml"
|
|
7
|
+
require "fileutils"
|
|
8
|
+
|
|
9
|
+
module Baid
|
|
10
|
+
VERSION = "0.1.0"
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
require_relative "baid/config"
|
|
14
|
+
require_relative "baid/api_client"
|
|
15
|
+
require_relative "baid/agent_detector"
|
|
16
|
+
require_relative "baid/mcp_configurator"
|
|
17
|
+
require_relative "baid/commands/login"
|
|
18
|
+
require_relative "baid/commands/logout"
|
|
19
|
+
require_relative "baid/commands/whoami"
|
|
20
|
+
require_relative "baid/skill_writer"
|
|
21
|
+
require_relative "baid/commands/init"
|
|
22
|
+
require_relative "baid/commands/reconfigure"
|
|
23
|
+
require_relative "baid/commands/install"
|
|
24
|
+
require_relative "baid/commands/search"
|
|
25
|
+
require_relative "baid/commands/update"
|
|
26
|
+
require_relative "baid/commands/skills"
|
|
27
|
+
require_relative "baid/commands/workspace"
|
|
28
|
+
require_relative "baid/cli"
|
metadata
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: baid
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Baid Team
|
|
8
|
+
bindir: bin
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: thor
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - "~>"
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '1.3'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - "~>"
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '1.3'
|
|
26
|
+
- !ruby/object:Gem::Dependency
|
|
27
|
+
name: httparty
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
|
+
requirements:
|
|
30
|
+
- - "~>"
|
|
31
|
+
- !ruby/object:Gem::Version
|
|
32
|
+
version: '0.21'
|
|
33
|
+
type: :runtime
|
|
34
|
+
prerelease: false
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - "~>"
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0.21'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: tty-prompt
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - "~>"
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '0.23'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - "~>"
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0.23'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: tty-spinner
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - "~>"
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '0.9'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - "~>"
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0.9'
|
|
68
|
+
- !ruby/object:Gem::Dependency
|
|
69
|
+
name: rspec
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
71
|
+
requirements:
|
|
72
|
+
- - "~>"
|
|
73
|
+
- !ruby/object:Gem::Version
|
|
74
|
+
version: '3.12'
|
|
75
|
+
type: :development
|
|
76
|
+
prerelease: false
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - "~>"
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '3.12'
|
|
82
|
+
- !ruby/object:Gem::Dependency
|
|
83
|
+
name: webmock
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
85
|
+
requirements:
|
|
86
|
+
- - "~>"
|
|
87
|
+
- !ruby/object:Gem::Version
|
|
88
|
+
version: '3.18'
|
|
89
|
+
type: :development
|
|
90
|
+
prerelease: false
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - "~>"
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '3.18'
|
|
96
|
+
executables:
|
|
97
|
+
- baid
|
|
98
|
+
extensions: []
|
|
99
|
+
extra_rdoc_files: []
|
|
100
|
+
files:
|
|
101
|
+
- bin/baid
|
|
102
|
+
- lib/baid.rb
|
|
103
|
+
- lib/baid/agent_detector.rb
|
|
104
|
+
- lib/baid/api_client.rb
|
|
105
|
+
- lib/baid/cli.rb
|
|
106
|
+
- lib/baid/commands/init.rb
|
|
107
|
+
- lib/baid/commands/install.rb
|
|
108
|
+
- lib/baid/commands/login.rb
|
|
109
|
+
- lib/baid/commands/logout.rb
|
|
110
|
+
- lib/baid/commands/reconfigure.rb
|
|
111
|
+
- lib/baid/commands/search.rb
|
|
112
|
+
- lib/baid/commands/skills.rb
|
|
113
|
+
- lib/baid/commands/update.rb
|
|
114
|
+
- lib/baid/commands/whoami.rb
|
|
115
|
+
- lib/baid/commands/workspace.rb
|
|
116
|
+
- lib/baid/config.rb
|
|
117
|
+
- lib/baid/mcp_configurator.rb
|
|
118
|
+
- lib/baid/skill_writer.rb
|
|
119
|
+
homepage: https://baid.dev
|
|
120
|
+
licenses:
|
|
121
|
+
- MIT
|
|
122
|
+
metadata: {}
|
|
123
|
+
rdoc_options: []
|
|
124
|
+
require_paths:
|
|
125
|
+
- lib
|
|
126
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
127
|
+
requirements:
|
|
128
|
+
- - ">="
|
|
129
|
+
- !ruby/object:Gem::Version
|
|
130
|
+
version: 3.2.0
|
|
131
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
132
|
+
requirements:
|
|
133
|
+
- - ">="
|
|
134
|
+
- !ruby/object:Gem::Version
|
|
135
|
+
version: '0'
|
|
136
|
+
requirements: []
|
|
137
|
+
rubygems_version: 3.6.9
|
|
138
|
+
specification_version: 4
|
|
139
|
+
summary: CLI for BaidSkills — AI agent skill management
|
|
140
|
+
test_files: []
|