@@ -20,6 +20,7 @@ export interface InstallPaths {
2020 shimPath : string ;
2121 settingsPath : string ;
2222 profilePath : string ;
23+ profileKind : "posix" | "powershell" ;
2324}
2425
2526type JsonObject = Record < string , unknown > ;
@@ -28,6 +29,10 @@ function currentHome(): string {
2829 return process . env . HOME || homedir ( ) ;
2930}
3031
32+ function isWindows ( ) : boolean {
33+ return process . platform === "win32" ;
34+ }
35+
3136function pricingCachePath ( home = currentHome ( ) ) : string {
3237 return path . join ( home , ".copilot" , "cost-cache" , "pricing.yaml" ) ;
3338}
@@ -44,14 +49,23 @@ export function resolveInstallPaths(env: NodeJS.ProcessEnv = process.env): Insta
4449 const home = env . HOME || homedir ( ) ;
4550 const copilotDir = path . join ( home , ".copilot" ) ;
4651 const shell = path . basename ( env . SHELL || "" ) ;
52+ const profileKind = ! shell && isWindows ( ) ? "powershell" : "posix" ;
53+ const powerShellProfileDir =
54+ env . PSModulePath ?. split ( path . delimiter )
55+ . map ( ( entry ) => path . normalize ( entry ) )
56+ . find ( ( entry ) => path . basename ( entry ) . toLowerCase ( ) === "modules" && path . basename ( path . dirname ( entry ) ) . toLowerCase ( ) === "powershell" ) ;
4757 const profileName = shell . includes ( "zsh" ) ? ".zshrc" : shell . includes ( "bash" ) ? ".bashrc" : ".profile" ;
4858 return {
4959 home,
5060 copilotDir,
5161 binDir : path . join ( copilotDir , "bin" ) ,
52- shimPath : path . join ( copilotDir , "bin" , "copilot-cost" ) ,
62+ shimPath : path . join ( copilotDir , "bin" , isWindows ( ) ? "copilot-cost.cmd" : "copilot-cost" ) ,
5363 settingsPath : path . join ( copilotDir , "settings.json" ) ,
54- profilePath : path . join ( home , profileName ) ,
64+ profilePath :
65+ profileKind === "powershell"
66+ ? path . join ( powerShellProfileDir ? path . dirname ( powerShellProfileDir ) : path . join ( home , "Documents" , "PowerShell" ) , "Microsoft.PowerShell_profile.ps1" )
67+ : path . join ( home , profileName ) ,
68+ profileKind,
5569 } ;
5670}
5771
@@ -63,14 +77,24 @@ function shellQuote(value: string): string {
6377 return `'${ value . replaceAll ( "'" , `'\\''` ) } '` ;
6478}
6579
66- export function otelBlock ( ) : string {
67- return [
68- OTEL_BEGIN ,
69- "export COPILOT_OTEL_ENABLED=true" ,
70- "export COPILOT_OTEL_EXPORTER_TYPE=file" ,
71- 'export COPILOT_OTEL_FILE_EXPORTER_PATH="$HOME/.copilot/otel/copilot-otel.jsonl"' ,
72- OTEL_END ,
73- ] . join ( "\n" ) ;
80+ export function otelBlock ( profileKind : "posix" | "powershell" = resolveInstallPaths ( ) . profileKind ) : string {
81+ const lines =
82+ profileKind === "powershell"
83+ ? [
84+ OTEL_BEGIN ,
85+ "$env:COPILOT_OTEL_ENABLED = 'true'" ,
86+ "$env:COPILOT_OTEL_EXPORTER_TYPE = 'file'" ,
87+ "$env:COPILOT_OTEL_FILE_EXPORTER_PATH = Join-Path $HOME '.copilot/otel/copilot-otel.jsonl'" ,
88+ OTEL_END ,
89+ ]
90+ : [
91+ OTEL_BEGIN ,
92+ "export COPILOT_OTEL_ENABLED=true" ,
93+ "export COPILOT_OTEL_EXPORTER_TYPE=file" ,
94+ 'export COPILOT_OTEL_FILE_EXPORTER_PATH="$HOME/.copilot/otel/copilot-otel.jsonl"' ,
95+ OTEL_END ,
96+ ] ;
97+ return lines . join ( "\n" ) ;
7498}
7599
76100function readJsonObject ( filePath : string ) : JsonObject {
@@ -99,12 +123,12 @@ export function hasOtelBlock(profilePath: string): boolean {
99123 return existsSync ( profilePath ) && readFileSync ( profilePath , "utf-8" ) . includes ( OTEL_BEGIN ) ;
100124}
101125
102- export function appendOtelExporterBlock ( profilePath = resolveInstallPaths ( ) . profilePath ) : "appended" | "already-present" {
126+ export function appendOtelExporterBlock ( profilePath = resolveInstallPaths ( ) . profilePath , profileKind = resolveInstallPaths ( ) . profileKind ) : "appended" | "already-present" {
103127 mkdirSync ( path . dirname ( profilePath ) , { recursive : true } ) ;
104128 const existing = existsSync ( profilePath ) ? readFileSync ( profilePath , "utf-8" ) : "" ;
105129 if ( existing . includes ( OTEL_BEGIN ) ) return "already-present" ;
106130 const prefix = existing . length > 0 && ! existing . endsWith ( "\n" ) ? "\n" : "" ;
107- writeFileSync ( profilePath , `${ existing } ${ prefix } ${ otelBlock ( ) } \n` , "utf-8" ) ;
131+ writeFileSync ( profilePath , `${ existing } ${ prefix } ${ otelBlock ( profileKind ) } \n` , "utf-8" ) ;
108132 return "appended" ;
109133}
110134
@@ -120,9 +144,9 @@ export function removeOtelExporterBlock(profilePath = resolveInstallPaths().prof
120144function writeShim ( shimPath : string ) : void {
121145 mkdirSync ( path . dirname ( shimPath ) , { recursive : true } ) ;
122146 const target = cliPathFromInstallModule ( ) ;
123- const body = `#!/bin/sh\nexec node ${ shellQuote ( target ) } render "$@"\n` ;
147+ const body = isWindows ( ) ? `@echo off\r\nnode " ${ target } " render %*\r\n` : `#!/bin/sh\nexec node ${ shellQuote ( target ) } render "$@"\n` ;
124148 writeFileSync ( shimPath , body , "utf-8" ) ;
125- chmodSync ( shimPath , 0o755 ) ;
149+ if ( ! isWindows ( ) ) chmodSync ( shimPath , 0o755 ) ;
126150}
127151
128152function installSettings ( settingsPath : string , shimPath : string ) : "updated" | "already-configured" {
@@ -148,9 +172,9 @@ export async function cmdInstall(opts: { yes?: boolean; otelProfile?: boolean }
148172 let otelAction : "appended" | "already-present" | "skipped" = "skipped" ;
149173 const shouldOfferProfileEdit = opts . otelProfile !== false ;
150174 if ( ! shouldOfferProfileEdit ) {
151- console . log ( `OTel profile edit skipped. Add this block to your shell profile to enable capture:\n${ otelBlock ( ) } ` ) ;
175+ console . log ( `OTel profile edit skipped. Add this block to your shell profile to enable capture:\n${ otelBlock ( paths . profileKind ) } ` ) ;
152176 } else {
153- otelAction = appendOtelExporterBlock ( paths . profilePath ) ;
177+ otelAction = appendOtelExporterBlock ( paths . profilePath , paths . profileKind ) ;
154178 }
155179
156180 await refreshPricing ( { force : false , dest : pricingCachePath ( paths . home ) } ) ;
@@ -250,7 +274,7 @@ export async function cmdDoctor(): Promise<number> {
250274 okLine ( "sample render" , false , error instanceof Error ? error . message : String ( error ) ) ;
251275 }
252276
253- const executable = existsSync ( paths . shimPath ) && ( statSync ( paths . shimPath ) . mode & 0o111 ) !== 0 ;
277+ const executable = existsSync ( paths . shimPath ) && ( isWindows ( ) || ( statSync ( paths . shimPath ) . mode & 0o111 ) !== 0 ) ;
254278 if ( ! okLine ( "shim executable" , executable , paths . shimPath ) ) failed = true ;
255279 const dashboardDir = path . resolve ( packageRoot ( import . meta. url ) , "dashboard-ui" , "dist" ) ;
256280 const missingDashboardFiles = [ "index.html" , "app.js" , "styles.css" ] . filter ( ( file ) => ! existsSync ( path . join ( dashboardDir , file ) ) ) ;
0 commit comments