@@ -28,6 +28,7 @@ import (
2828 "github.com/kontext-security/kontext-cli/internal/auth"
2929 "github.com/kontext-security/kontext-cli/internal/backend"
3030 "github.com/kontext-security/kontext-cli/internal/credential"
31+ "github.com/kontext-security/kontext-cli/internal/diagnostic"
3132 "github.com/kontext-security/kontext-cli/internal/sidecar"
3233)
3334
@@ -37,15 +38,20 @@ type Options struct {
3738 TemplateFile string
3839 IssuerURL string
3940 ClientID string
41+ Verbose bool
4042 Args []string
4143}
4244
4345// Start is the main entry point for `kontext start`.
4446func Start (ctx context.Context , opts Options ) error {
47+ diagnostics := diagnostic .New (os .Stderr , opts .Verbose || diagnostic .EnabledFromEnv ())
48+ diagnostics .Printf ("start: agent=%s env_template=%s\n " , opts .Agent , opts .TemplateFile )
49+
4550 agentPath , err := preflightAgent (opts .Agent )
4651 if err != nil {
4752 return err
4853 }
54+ diagnostics .Printf ("agent preflight: %s -> %s\n " , opts .Agent , agentPath )
4955
5056 // 1. Auth
5157 session , err := ensureSession (ctx , opts .IssuerURL , opts .ClientID )
@@ -63,7 +69,7 @@ func Start(ctx context.Context, opts Options) error {
6369 }
6470
6571 // 2. Backend client — token source refreshes automatically on expiry
66- client := backend .NewClient (backend .BaseURL (), newSessionTokenSource (ctx , session ))
72+ client := backend .NewClient (backend .BaseURL (), newSessionTokenSource (ctx , session , diagnostics ))
6773
6874 // 3. Create session via ConnectRPC
6975 hostname , _ := os .Hostname ()
@@ -114,7 +120,8 @@ func Start(ctx context.Context, opts Options) error {
114120
115121 var templateDoc * credential.TemplateFile
116122 if bootstrapErr != nil {
117- fmt .Fprintf (os .Stderr , "⚠ Provider sync skipped (%v)\n " , bootstrapErr )
123+ diagnostics .Printf ("provider sync skipped: %v\n " , bootstrapErr )
124+ fmt .Fprintln (os .Stderr , "⚠ Provider sync skipped; using the local env template." )
118125 templateDoc , err = credential .LoadTemplateFile (opts .TemplateFile )
119126 if err != nil {
120127 return fmt .Errorf ("load env template: %w" , err )
@@ -177,7 +184,7 @@ func Start(ctx context.Context, opts Options) error {
177184 // so reading session fields is safe without synchronization)
178185 var resolved []credential.Resolved
179186 if len (templateDoc .Entries ) > 0 {
180- resolved , err = resolveCredentials (ctx , session , templateDoc .Entries , credentialClientID )
187+ resolved , err = resolveCredentials (ctx , session , templateDoc .Entries , credentialClientID , diagnostics )
181188 if err != nil {
182189 return err
183190 }
@@ -266,7 +273,7 @@ func ensureSession(ctx context.Context, issuerURL, clientID string) (*auth.Sessi
266273}
267274
268275// resolveCredentials exchanges each template entry for a live credential.
269- func resolveCredentials (ctx context.Context , session * auth.Session , entries []credential.Entry , credentialClientID string ) ([]credential.Resolved , error ) {
276+ func resolveCredentials (ctx context.Context , session * auth.Session , entries []credential.Entry , credentialClientID string , diagnostics diagnostic. Logger ) ([]credential.Resolved , error ) {
270277 fmt .Fprintln (os .Stderr , "\n Resolving credentials..." )
271278 resolved := make ([]credential.Resolved , 0 , len (entries ))
272279 failures := make (map [string ]error )
@@ -277,7 +284,8 @@ func resolveCredentials(ctx context.Context, session *auth.Session, entries []cr
277284 fmt .Fprintf (os .Stderr , " %s (%s)... " , entry .EnvVar , entry .Target ())
278285 value , err := exchangeCredential (ctx , session , entry , credentialClientID )
279286 if err != nil {
280- fmt .Fprintf (os .Stderr , "⚠ skipped (%v)\n " , err )
287+ diagnostics .Printf ("credential %s exchange failed: %v\n " , entry .EnvVar , err )
288+ fmt .Fprintf (os .Stderr , "⚠ skipped (%s)\n " , credentialFailureSummary (err ))
281289 failures [entry .EnvVar ] = err
282290 continue
283291 }
@@ -287,7 +295,7 @@ func resolveCredentials(ctx context.Context, session *auth.Session, entries []cr
287295
288296 connectable := unresolvedConnectableEntries (entryByEnvVar , failures )
289297 if len (connectable ) == 0 {
290- printLaunchWarnings (entryByEnvVar , failures )
298+ printLaunchWarnings (entryByEnvVar , failures , diagnostics )
291299 return resolved , nil
292300 }
293301
@@ -300,11 +308,12 @@ func resolveCredentials(ctx context.Context, session *auth.Session, entries []cr
300308 auth .Login ,
301309 )
302310 if connectErr != nil {
311+ diagnostics .Printf ("hosted connect session failed: %v\n " , connectErr )
303312 if ! interactive && needsGatewayAccessReauthentication (connectErr ) {
304313 fmt .Fprintln (os .Stderr , "⚠ Non-interactive session detected. Re-run `kontext start` in an interactive terminal to authorize hosted connect." )
305314 }
306- fmt .Fprintf (os .Stderr , "⚠ Could not create hosted connect session (%v )\n " , connectErr )
307- printLaunchWarnings (entryByEnvVar , failures )
315+ fmt .Fprintf (os .Stderr , "⚠ Could not create hosted connect session (%s )\n " , connectFailureSummary ( connectErr ) )
316+ printLaunchWarnings (entryByEnvVar , failures , diagnostics )
308317 return resolved , nil
309318 }
310319
@@ -314,13 +323,14 @@ func resolveCredentials(ctx context.Context, session *auth.Session, entries []cr
314323
315324 if ! interactive {
316325 fmt .Fprintln (os .Stderr , "⚠ Non-interactive session detected. Open this URL in a browser, then rerun `kontext start`." )
317- printLaunchWarnings (entryByEnvVar , failures )
326+ printLaunchWarnings (entryByEnvVar , failures , diagnostics )
318327 return resolved , nil
319328 }
320329
321330 fmt .Fprintf (os .Stderr , " Opening browser to connect %s...\n " , providerList )
322331 if err := browser .OpenURL (connectURL ); err != nil {
323- fmt .Fprintf (os .Stderr , " ⚠ Could not open browser automatically (%v)\n " , err )
332+ diagnostics .Printf ("hosted connect browser open failed: %v\n " , err )
333+ fmt .Fprintln (os .Stderr , " ⚠ Could not open browser automatically." )
324334 fmt .Fprintln (os .Stderr , " Open the URL above to continue." )
325335 }
326336 fmt .Fprint (os .Stderr , " Press Enter after connecting..." )
@@ -331,6 +341,7 @@ func resolveCredentials(ctx context.Context, session *auth.Session, entries []cr
331341 session ,
332342 connectable ,
333343 credentialClientID ,
344+ diagnostics ,
334345 )
335346 resolved = append (resolved , retriedResolved ... )
336347 for _ , entry := range connectable {
@@ -341,7 +352,7 @@ func resolveCredentials(ctx context.Context, session *auth.Session, entries []cr
341352 delete (failures , entry .EnvVar )
342353 }
343354
344- printLaunchWarnings (entryByEnvVar , failures )
355+ printLaunchWarnings (entryByEnvVar , failures , diagnostics )
345356 return resolved , nil
346357}
347358
@@ -369,6 +380,7 @@ func retryConnectableCredentials(
369380 session * auth.Session ,
370381 entries []credential.Entry ,
371382 credentialClientID string ,
383+ diagnostics diagnostic.Logger ,
372384) ([]credential.Resolved , map [string ]error ) {
373385 attemptDelays := []time.Duration {0 , 3 * time .Second , 7 * time .Second }
374386 pending := make (map [string ]credential.Entry , len (entries ))
@@ -396,7 +408,8 @@ func retryConnectableCredentials(
396408 )
397409 value , err := exchangeCredential (ctx , session , entry , credentialClientID )
398410 if err != nil {
399- fmt .Fprintf (os .Stderr , "⚠ skipped (%v)\n " , err )
411+ diagnostics .Printf ("credential %s retry failed: %v\n " , entry .EnvVar , err )
412+ fmt .Fprintf (os .Stderr , "⚠ skipped (%s)\n " , credentialFailureSummary (err ))
400413 failures [envVar ] = err
401414 continue
402415 }
@@ -410,7 +423,33 @@ func retryConnectableCredentials(
410423 return resolved , failures
411424}
412425
413- func printLaunchWarnings (entryByEnvVar map [string ]credential.Entry , failures map [string ]error ) {
426+ func credentialFailureSummary (err error ) string {
427+ var resolutionErr * credentialResolutionError
428+ if errors .As (err , & resolutionErr ) {
429+ switch resolutionErr .Reason {
430+ case failureDisconnected :
431+ return "provider needs connection"
432+ case failureNotAttached :
433+ return "provider is not attached"
434+ case failureUnknown :
435+ return "unknown provider"
436+ case failureInvalid :
437+ return "invalid placeholder"
438+ case failureTransient :
439+ return "temporary exchange error"
440+ }
441+ }
442+ return "run with --verbose for details"
443+ }
444+
445+ func connectFailureSummary (err error ) string {
446+ if needsGatewayAccessReauthentication (err ) {
447+ return "gateway access needs authorization"
448+ }
449+ return "run with --verbose for details"
450+ }
451+
452+ func printLaunchWarnings (entryByEnvVar map [string ]credential.Entry , failures map [string ]error , diagnostics diagnostic.Logger ) {
414453 if len (failures ) == 0 {
415454 return
416455 }
@@ -445,12 +484,14 @@ func printLaunchWarnings(entryByEnvVar map[string]credential.Entry, failures map
445484 )
446485 skipped = append (skipped , entry .Provider )
447486 default :
448- fmt .Fprintf (os .Stderr , "⚠ %s was skipped (%v)\n " , entry .EnvVar , err )
487+ diagnostics .Printf ("credential %s skipped: %v\n " , entry .EnvVar , err )
488+ fmt .Fprintf (os .Stderr , "⚠ %s was skipped (%s)\n " , entry .EnvVar , credentialFailureSummary (err ))
449489 }
450490 continue
451491 }
452492
453- fmt .Fprintf (os .Stderr , "⚠ %s was skipped (%v)\n " , entry .EnvVar , err )
493+ diagnostics .Printf ("credential %s skipped: %v\n " , entry .EnvVar , err )
494+ fmt .Fprintf (os .Stderr , "⚠ %s was skipped (%s)\n " , entry .EnvVar , credentialFailureSummary (err ))
454495 }
455496
456497 if len (skipped ) > 0 {
@@ -794,7 +835,7 @@ func supportedAgents() []string {
794835
795836// newSessionTokenSource returns a TokenSource that transparently refreshes
796837// the OIDC access token when it expires, so long-running sessions keep working.
797- func newSessionTokenSource (ctx context.Context , session * auth.Session ) backend.TokenSource {
838+ func newSessionTokenSource (ctx context.Context , session * auth.Session , diagnostics diagnostic. Logger ) backend.TokenSource {
798839 mu := & sync.Mutex {}
799840 return func () (string , error ) {
800841 mu .Lock ()
@@ -811,7 +852,8 @@ func newSessionTokenSource(ctx context.Context, session *auth.Session) backend.T
811852
812853 // Persist so other processes (and the next `kontext start`) see the new token
813854 if saveErr := auth .SaveSession (refreshed ); saveErr != nil {
814- fmt .Fprintf (os .Stderr , "⚠ Could not persist refreshed session: %v\n " , saveErr )
855+ diagnostics .Printf ("persist refreshed session failed: %v\n " , saveErr )
856+ fmt .Fprintln (os .Stderr , "⚠ Could not persist refreshed session." )
815857 }
816858
817859 // Update the shared session pointer for subsequent calls
0 commit comments