Skip to content

Commit 8c4b70d

Browse files
cosmicbuffaloNicholas deLannoyAntoineGS
authored
feat: expose is_authenticated() helper and add checkhealth support (#519)
This allows having Copilot Installed and enabled without having the constant nagging to log in, moving the log in status to the `:checkhealth` NeoVim process. Co-authored-by: Nicholas deLannoy <[email protected]> Co-authored-by: Antoine Gaudreau Simard <[email protected]>
1 parent d921204 commit 8c4b70d

File tree

4 files changed

+210
-1
lines changed

4 files changed

+210
-1
lines changed

lua/copilot/auth/init.lua

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ function M.signout()
132132
end)
133133
end
134134

135+
---@return string|nil
135136
function M.find_config_path()
136137
local config = vim.fn.expand("$XDG_CONFIG_HOME")
137138
if config and vim.fn.isdirectory(config) > 0 then
@@ -151,6 +152,7 @@ function M.find_config_path()
151152
logger.error("could not find config path")
152153
end
153154

155+
---@return table|nil
154156
M.get_creds = function()
155157
local filename = M.find_config_path() .. "/github-copilot/apps.json"
156158

@@ -180,4 +182,65 @@ function M.info()
180182
logger.notify("GitHub Copilot token information: ", info)
181183
end
182184

185+
---@class copilot_auth_cache
186+
---@field authenticated boolean|nil
187+
---@field timestamp number
188+
189+
---@type copilot_auth_cache
190+
local auth_cache = {
191+
authenticated = nil,
192+
timestamp = 0,
193+
}
194+
195+
---@param authenticated boolean
196+
---@return number
197+
local function get_cache_ttl(authenticated)
198+
if authenticated then
199+
return 300000 -- 5 minutes for true status
200+
else
201+
return 30000 -- 30 seconds for false status
202+
end
203+
end
204+
205+
---@param client vim.lsp.Client
206+
---@param callback? fun()
207+
local function check_status(client, callback)
208+
api.check_status(client, {}, function(err, status)
209+
auth_cache.timestamp = vim.loop.now()
210+
211+
if not err and status and status.user then
212+
auth_cache.authenticated = true
213+
else
214+
auth_cache.authenticated = false
215+
end
216+
217+
if callback then
218+
callback()
219+
end
220+
end)
221+
end
222+
223+
---@param callback? fun()
224+
function M.is_authenticated(callback)
225+
local current_time = vim.loop.now()
226+
227+
if auth_cache.authenticated ~= nil then
228+
local ttl = get_cache_ttl(auth_cache.authenticated)
229+
if (current_time - auth_cache.timestamp) < ttl then
230+
return auth_cache.authenticated
231+
end
232+
end
233+
234+
local client = c.get()
235+
if not client then
236+
auth_cache.authenticated = false
237+
auth_cache.timestamp = current_time
238+
return false
239+
end
240+
241+
check_status(client, callback)
242+
243+
return auth_cache.authenticated or false
244+
end
245+
183246
return M

lua/copilot/health.lua

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
local M = {}
2+
3+
local api = require("copilot.api")
4+
local auth = require("copilot.auth")
5+
local c = require("copilot.client")
6+
local config = require("copilot.config")
7+
8+
local start = vim.health.start or vim.health.report_start
9+
local ok = vim.health.ok or vim.health.report_ok
10+
local warn = vim.health.warn or vim.health.report_warn
11+
local error = vim.health.error or vim.health.report_error
12+
local info = vim.health.info or vim.health.report_info
13+
14+
function M.check()
15+
start("{copilot.lua}")
16+
info("{copilot.lua} GitHub Copilot plugin for Neovim")
17+
18+
start("Copilot Dependencies")
19+
20+
if vim.fn.executable("node") == 1 then
21+
local node_version = vim.fn.system("node --version"):gsub("\n", "")
22+
ok("`node` found: " .. node_version)
23+
else
24+
error("`node` not found in PATH")
25+
info("Install Node.js from https://nodejs.org")
26+
end
27+
28+
start("Copilot Authentication")
29+
30+
local github_token = os.getenv("GITHUB_COPILOT_TOKEN")
31+
local gh_token = os.getenv("GH_COPILOT_TOKEN")
32+
if github_token or gh_token then
33+
ok("Environment token found: " .. (github_token and "`GITHUB_COPILOT_TOKEN`" or "`GH_COPILOT_TOKEN`"))
34+
else
35+
info("No environment token set (`GITHUB_COPILOT_TOKEN` or `GH_COPILOT_TOKEN`)")
36+
end
37+
38+
local config_path = auth.find_config_path()
39+
local creds_ok, creds = pcall(auth.get_creds)
40+
if creds_ok and creds then
41+
ok("Local credentials file found")
42+
info("Location: `" .. (config_path or "unknown") .. "/github-copilot/apps.json`")
43+
else
44+
info("No local credentials file found")
45+
info("Expected location: `" .. (config_path or "unknown") .. "/github-copilot/apps.json`")
46+
info("Run `:Copilot auth` to authenticate")
47+
end
48+
49+
local client = c.get()
50+
if not client then
51+
error("Copilot LSP client not available")
52+
info("Check that the plugin is properly loaded and configured")
53+
info("Or restart Neovim if the plugin was just installed")
54+
return
55+
end
56+
57+
start("Copilot LSP Status")
58+
ok("LSP client is available and running")
59+
info("Client ID: " .. tostring(client.id))
60+
61+
local lsp_authenticated = auth.is_authenticated()
62+
if lsp_authenticated then
63+
ok("LSP authentication status: authenticated")
64+
else
65+
warn("LSP authentication status: not authenticated")
66+
end
67+
info("For detailed authentication status, run `:Copilot status`")
68+
69+
start("Copilot Configuration")
70+
local suggestion_config = config.suggestion
71+
if suggestion_config and suggestion_config.enabled ~= false then
72+
ok("Suggestions enabled")
73+
if suggestion_config.auto_trigger ~= false then
74+
info("Auto-trigger: enabled")
75+
else
76+
info("Auto-trigger: disabled (manual trigger only)")
77+
end
78+
else
79+
warn("Suggestions disabled in configuration")
80+
info("Enable with `suggestion = { enabled = true }` in setup()")
81+
end
82+
83+
local panel_config = config.panel
84+
if panel_config and panel_config.enabled ~= false then
85+
ok("Panel enabled")
86+
info("Panel Keybinding: " .. (panel_config.keymap and panel_config.keymap.open or "<M-CR>"))
87+
else
88+
info("Panel disabled in configuration")
89+
info("Enable with `panel = { enabled = true }` in setup()")
90+
end
91+
end
92+
93+
return M

lua/copilot/suggestion/init.lua

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
local api = require("copilot.api")
2+
local auth = require("copilot.auth")
23
local c = require("copilot.client")
34
local config = require("copilot.config")
45
local hl_group = require("copilot.highlight").group
@@ -484,10 +485,17 @@ local function advance(count, ctx)
484485
end
485486

486487
local function schedule(ctx)
487-
if not is_enabled() or not c.initialized then
488+
-- in case we need to call the lsp API to know if we are authenticated,
489+
-- we want to retry this method after the authentication check
490+
local is_authenticated = auth.is_authenticated(function()
491+
schedule(ctx)
492+
end)
493+
494+
if not is_enabled() or not c.initialized or not is_authenticated then
488495
clear()
489496
return
490497
end
498+
491499
logger.trace("suggestion schedule", ctx)
492500

493501
if copilot._copilot_timer then

tests/test_auth.lua

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,4 +84,49 @@ T["auth()"]["auth issue replication"] = function()
8484
u.expect_match(messages, ".*Online.*Enabled.*")
8585
end
8686

87+
T["auth()"]["is_authenticated when not authed returns false"] = function()
88+
child.configure_copilot()
89+
90+
local result = child.lua([[
91+
local auth = require("copilot.auth")
92+
local auth_result = auth.is_authenticated()
93+
return tostring(auth_result)
94+
]])
95+
96+
u.expect_match(result, "false")
97+
end
98+
99+
T["auth()"]["is_authenticated when authed returns true"] = function()
100+
child.configure_copilot()
101+
child.cmd("Copilot auth")
102+
103+
child.lua([[
104+
local messages = ""
105+
local function has_passed()
106+
messages = vim.api.nvim_exec("messages", { output = true }) or ""
107+
return string.find(messages, ".*Authenticated as GitHub user.*") ~= nil
108+
end
109+
110+
vim.wait(30000, function()
111+
return has_passed()
112+
end, 50)
113+
]])
114+
115+
local result = child.lua([[
116+
local auth_result = ""
117+
local function has_passed()
118+
auth_result = require("copilot.auth").is_authenticated() or ""
119+
return auth_result == true
120+
end
121+
122+
vim.wait(30000, function()
123+
return has_passed()
124+
end, 50)
125+
126+
return tostring(auth_result)
127+
]])
128+
129+
u.expect_match(result, "true")
130+
end
131+
87132
return T

0 commit comments

Comments
 (0)