11import type { CanonicalEvent , MetricBag } from '@codetime/shared'
2- import type { AgentAdapter , InstallEntry } from './types.js'
2+ import type { AdapterEnv , AgentAdapter , InstallEntry } from './types.js'
33import os from 'node:os'
44import path from 'node:path'
55import {
@@ -422,10 +422,40 @@ function opencodeUsageFromInfo(info: Record<string, unknown>): OpenCodeUsage | u
422422 }
423423}
424424
425+ // ── Path resolution ──
426+
427+ // OpenCode follows XDG for its config (~/.config/opencode) and data
428+ // (~/.local/share/opencode), but also reads OPENCODE_CONFIG_DIR for the former.
429+ // Both can move independently — agents, plugins, and history.
430+
431+ function opencodeConfigDir ( home : string , env ?: AdapterEnv ) : string {
432+ const override = env ?. OPENCODE_CONFIG_DIR
433+ if ( override && override . trim ( ) ) {
434+ return path . resolve ( override )
435+ }
436+ const xdgConfig = env ?. XDG_CONFIG_HOME
437+ if ( xdgConfig && xdgConfig . trim ( ) ) {
438+ return path . join ( path . resolve ( xdgConfig ) , 'opencode' )
439+ }
440+ return path . join ( home , '.config' , 'opencode' )
441+ }
442+
443+ function opencodeDataCandidates ( home : string , env ?: AdapterEnv ) : string [ ] {
444+ const xdgData = env ?. XDG_DATA_HOME
445+ const primary = xdgData && xdgData . trim ( )
446+ ? path . join ( path . resolve ( xdgData ) , 'opencode' , 'opencode.db' )
447+ : path . join ( home , '.local' , 'share' , 'opencode' , 'opencode.db' )
448+ // Keep the legacy ~/.opencode/opencode.db location as a fallback for older
449+ // installs that haven't migrated to the XDG data dir.
450+ return [ primary , path . join ( home , '.opencode' , 'opencode.db' ) ]
451+ }
452+
425453// ── Backfill file discovery (special: OpenCode uses SQLite, not JSONL) ──
426454
427455export async function opencodeBackfillFiles (
428456 sourceRoot ?: string ,
457+ home : string = os . homedir ( ) ,
458+ env ?: AdapterEnv ,
429459) : Promise < Array < { path : string , modifiedAt : string } > > {
430460 const { stat } = await import ( 'node:fs/promises' )
431461 if ( sourceRoot ) {
@@ -439,11 +469,7 @@ export async function opencodeBackfillFiles(
439469 return [ { path : sourceRoot , modifiedAt : info . mtime . toISOString ( ) } ]
440470 }
441471
442- const candidates = [
443- path . join ( os . homedir ( ) , '.local' , 'share' , 'opencode' , 'opencode.db' ) ,
444- path . join ( os . homedir ( ) , '.opencode' , 'opencode.db' ) ,
445- ]
446- for ( const candidatePath of candidates ) {
472+ for ( const candidatePath of opencodeDataCandidates ( home , env ) ) {
447473 const info = await stat ( candidatePath ) . catch ( ( ) => null )
448474 if ( info ) {
449475 return [ { path : candidatePath , modifiedAt : info . mtime . toISOString ( ) } ]
@@ -503,7 +529,6 @@ export const AgentTime = async ({ $, directory }) => {
503529// ── Adapter factory ──
504530
505531export function createOpenCodeAdapter ( ) : AgentAdapter {
506- const OPENCODE_CONFIG = '.config/opencode'
507532 const PLUGIN_PATH = 'plugins/codetime.mjs'
508533
509534 return {
@@ -512,37 +537,34 @@ export function createOpenCodeAdapter(): AgentAdapter {
512537 agentName : 'opencode' ,
513538 kind : 'agent' ,
514539
515- detectPath ( home : string ) {
516- return path . join ( home , OPENCODE_CONFIG )
540+ detectPath ( home : string , env ?: AdapterEnv ) {
541+ return opencodeConfigDir ( home , env )
517542 } ,
518- installedPath ( home : string ) {
519- return path . join ( home , OPENCODE_CONFIG , PLUGIN_PATH )
543+ installedPath ( home : string , env ?: AdapterEnv ) {
544+ return path . join ( opencodeConfigDir ( home , env ) , PLUGIN_PATH )
520545 } ,
521546
522- async isInstalled ( home : string ) {
547+ async isInstalled ( home : string , env ?: AdapterEnv ) {
523548 try {
524549 const { pathExists } = await import ( '../lib/fs.js' )
525- return await pathExists ( path . join ( home , OPENCODE_CONFIG , PLUGIN_PATH ) )
550+ return await pathExists ( path . join ( opencodeConfigDir ( home , env ) , PLUGIN_PATH ) )
526551 || await pathExists ( path . join ( '.opencode' , PLUGIN_PATH ) )
527552 }
528553 catch {
529554 return false
530555 }
531556 } ,
532557
533- installEntries ( home : string ) : InstallEntry [ ] {
558+ installEntries ( home : string , env ?: AdapterEnv ) : InstallEntry [ ] {
534559 return [ {
535560 kind : 'file' ,
536- path : path . join ( home , OPENCODE_CONFIG , PLUGIN_PATH ) ,
561+ path : path . join ( opencodeConfigDir ( home , env ) , PLUGIN_PATH ) ,
537562 content : opencodePluginContent ( ) ,
538563 } ]
539564 } ,
540565
541- sourcePaths ( home : string ) : string [ ] {
542- return [
543- path . join ( home , '.local' , 'share' , 'opencode' , 'opencode.db' ) ,
544- path . join ( home , '.opencode' , 'opencode.db' ) ,
545- ]
566+ sourcePaths ( home : string , env ?: AdapterEnv ) : string [ ] {
567+ return opencodeDataCandidates ( home , env )
546568 } ,
547569
548570 parseSessionFile : parseOpenCodeSessionFile ,
0 commit comments