From faaa1692fb9a2af519fee06aca7e81bca92aa7ec Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 26 Oct 2026 13:23:52 -0700 Subject: [PATCH 1/2] Release Azure Storage 0.3.3 usage telemetry Import exact qualified artifact from source beb224f3910dc56b5d238d35fc92928b0073b4f0 and update only its marketplace version. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2eadb772-b466-49f1-bcd0-2e2c59791222 --- .github/plugin/marketplace.json | 2 +- .../azure-storage/.github/plugin/plugin.json | 2 +- canvases/azure-storage/SHA256SUMS | 12 ++++++------ canvases/azure-storage/checksums.json | 10 +++++----- .../extensions/azure-storage/extension.mjs | 6 +++--- .../extensions/azure-storage/studio-package.json | 2 +- canvases/azure-storage/package.json | 2 +- canvases/azure-storage/release.json | 16 +++++++++++++--- 8 files changed, 31 insertions(+), 21 deletions(-) diff --git a/.github/plugin/marketplace.json b/.github/plugin/marketplace.json index 258ce33..b28d429 100644 --- a/.github/plugin/marketplace.json +++ b/.github/plugin/marketplace.json @@ -41,7 +41,7 @@ { "name": "azure-storage", "description": "Azure Storage canvas — browse blobs and containers, bulk transfer with live progress, filter with aggregate statistics, mint and open SAS links, preview blobs, and plan account to account copies, all agent-drivable on one live surface.", - "version": "0.3.2", + "version": "0.3.3", "source": "canvases/azure-storage" }, { diff --git a/canvases/azure-storage/.github/plugin/plugin.json b/canvases/azure-storage/.github/plugin/plugin.json index 3c3e2ed..8b2ff7e 100644 --- a/canvases/azure-storage/.github/plugin/plugin.json +++ b/canvases/azure-storage/.github/plugin/plugin.json @@ -1,7 +1,7 @@ { "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json", "name": "azure-storage", - "version": "0.3.2", + "version": "0.3.3", "description": "Azure Storage canvas — browse blobs and containers, bulk transfer with live progress, filter with aggregate statistics, mint and open SAS links, preview blobs, and plan account to account copies, all agent-drivable on one live surface.", "extensions": { "com.github.copilot": { diff --git a/canvases/azure-storage/SHA256SUMS b/canvases/azure-storage/SHA256SUMS index 2d16ce9..2d99d93 100644 --- a/canvases/azure-storage/SHA256SUMS +++ b/canvases/azure-storage/SHA256SUMS @@ -1,14 +1,14 @@ -3a262e33c0e21381bd1a4f51edb8ffaf6d823d3a1516e4ef510ca3aff95da9b0 .github/plugin/plugin.json +396925277edf6f794be74d0cd02f64226dbfa26abe6b2453a8fd0b3a44a58917 .github/plugin/plugin.json a813071223c79d70046ffde6cd6de1d52eecf7b7b3ce174a3e1bcb2c2ebb283d THIRD_PARTY_NOTICES.txt 5fb6a9018aecd7aede2a8937f78d81867bf00c5550f30ab071d158b3852ccaca assets/preview.png -122366309ceaa49cae3a1fad71798502bb0587e31ad50e1746ad8af8e7608a77 checksums.json -45fcd18ae09de9b5542e7a113556482105da42419b1c603fcc69ace3be1a55b4 com.github.copilot/extensions/azure-storage/extension.mjs +602d715345e1d6a9416c37dbe5019c2ec21a044cdd7721d42075a6fa29a5e94a checksums.json +9f75b0541587faa2854f99718c5a028e13a4a2f7d2c6c46a0fd8f29bee548a2d com.github.copilot/extensions/azure-storage/extension.mjs f7d102239138f46038f9fbbc576d6a34e2f03370f27c282dc7b59dbe470f791d com.github.copilot/extensions/azure-storage/sdk/blob.mjs 88d23b0a1a226385486aade505b086c7bf524ef0bd5b031bac4df0351e8a20f6 com.github.copilot/extensions/azure-storage/sdk/file.mjs fa0526b9488b0e797bfa79044b632edb53483e9e2dc12fe672d093bfb9ace670 com.github.copilot/extensions/azure-storage/sdk/identity.mjs 6553c36dad97992722dd5d1320889ec09038847c31f2a04cf8756930f725c169 com.github.copilot/extensions/azure-storage/sdk/queue.mjs cf4b43961f200d9d73f1158aa28b628863274d33e66f53457afa7f6a16562202 com.github.copilot/extensions/azure-storage/sdk/tables.mjs -5520dc6cc7535a61cae0fe0c5764746837e96ecc5a431b360deca6d2534d0f3f com.github.copilot/extensions/azure-storage/studio-package.json -be4affe6bd79ceb663389d03dc47fc1e55a8223d7075b370dc0f78853825faaf package.json -e534abc17ed846674eeb89b13b1bf1da5efa3aad6e35fbc0fa9454b12eec118b release.json +c0865cfca79c90f8ebf75e11d0cf9c61bda757b4f03160b89fd3ebb6622b7725 com.github.copilot/extensions/azure-storage/studio-package.json +1434b589e284ae951a0ca236c237b936288959b6013af84119c8b0691c686aab package.json +5a3b3801d5ace7180420f28c2d4658046ca30da52fe4062dadb27980bd91d80f release.json 27a2fce06a96abfd4a76405a78ff80b3be2cacf50f9dd4bde49649cfea84942f skills/azure-storage/SKILL.md diff --git a/canvases/azure-storage/checksums.json b/canvases/azure-storage/checksums.json index d6af1d2..e46f136 100644 --- a/canvases/azure-storage/checksums.json +++ b/canvases/azure-storage/checksums.json @@ -1,15 +1,15 @@ { - ".github/plugin/plugin.json": "3a262e33c0e21381bd1a4f51edb8ffaf6d823d3a1516e4ef510ca3aff95da9b0", + ".github/plugin/plugin.json": "396925277edf6f794be74d0cd02f64226dbfa26abe6b2453a8fd0b3a44a58917", "assets/preview.png": "5fb6a9018aecd7aede2a8937f78d81867bf00c5550f30ab071d158b3852ccaca", - "com.github.copilot/extensions/azure-storage/extension.mjs": "45fcd18ae09de9b5542e7a113556482105da42419b1c603fcc69ace3be1a55b4", + "com.github.copilot/extensions/azure-storage/extension.mjs": "9f75b0541587faa2854f99718c5a028e13a4a2f7d2c6c46a0fd8f29bee548a2d", "com.github.copilot/extensions/azure-storage/sdk/blob.mjs": "f7d102239138f46038f9fbbc576d6a34e2f03370f27c282dc7b59dbe470f791d", "com.github.copilot/extensions/azure-storage/sdk/file.mjs": "88d23b0a1a226385486aade505b086c7bf524ef0bd5b031bac4df0351e8a20f6", "com.github.copilot/extensions/azure-storage/sdk/identity.mjs": "fa0526b9488b0e797bfa79044b632edb53483e9e2dc12fe672d093bfb9ace670", "com.github.copilot/extensions/azure-storage/sdk/queue.mjs": "6553c36dad97992722dd5d1320889ec09038847c31f2a04cf8756930f725c169", "com.github.copilot/extensions/azure-storage/sdk/tables.mjs": "cf4b43961f200d9d73f1158aa28b628863274d33e66f53457afa7f6a16562202", - "com.github.copilot/extensions/azure-storage/studio-package.json": "5520dc6cc7535a61cae0fe0c5764746837e96ecc5a431b360deca6d2534d0f3f", - "package.json": "be4affe6bd79ceb663389d03dc47fc1e55a8223d7075b370dc0f78853825faaf", - "release.json": "e534abc17ed846674eeb89b13b1bf1da5efa3aad6e35fbc0fa9454b12eec118b", + "com.github.copilot/extensions/azure-storage/studio-package.json": "c0865cfca79c90f8ebf75e11d0cf9c61bda757b4f03160b89fd3ebb6622b7725", + "package.json": "1434b589e284ae951a0ca236c237b936288959b6013af84119c8b0691c686aab", + "release.json": "5a3b3801d5ace7180420f28c2d4658046ca30da52fe4062dadb27980bd91d80f", "skills/azure-storage/SKILL.md": "27a2fce06a96abfd4a76405a78ff80b3be2cacf50f9dd4bde49649cfea84942f", "THIRD_PARTY_NOTICES.txt": "a813071223c79d70046ffde6cd6de1d52eecf7b7b3ce174a3e1bcb2c2ebb283d" } diff --git a/canvases/azure-storage/com.github.copilot/extensions/azure-storage/extension.mjs b/canvases/azure-storage/com.github.copilot/extensions/azure-storage/extension.mjs index 8751cae..a536a74 100644 --- a/canvases/azure-storage/com.github.copilot/extensions/azure-storage/extension.mjs +++ b/canvases/azure-storage/com.github.copilot/extensions/azure-storage/extension.mjs @@ -4,7 +4,7 @@ var __defProp=Object.defineProperty;var __export=(target,all)=>{for(var name in `;for(let res of clients)try{res.write(data)}catch{}}function setState(patch){let entitiesChanged=Object.hasOwn(patch,"s_entities");state={...state,...patch,s_entitiesRevision:(state.s_entitiesRevision||0)+Number(entitiesChanged)},broadcast(entitiesChanged)}function getState(){return state}function log(msg){let line=`[${new Date().toLocaleTimeString()}] ${msg}`,activityLog=[...state.activityLog||[],line].slice(-200);setState({activityLog})}let ctx={getState,setState,log},server2=http.createServer(async(req,res)=>{if(req.method==="GET"&&req.url==="/events"){res.writeHead(200,{"Content-Type":"text/event-stream","Cache-Control":"no-cache",Connection:"keep-alive"}),res.write(`data: ${JSON.stringify(state)} -`),clients.add(res),req.on("close",()=>clients.delete(res));return}if(req.method==="GET"&&(req.url==="/"||req.url.startsWith("/?"))){let nonce=randomBytes(16).toString("base64");res.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Content-Security-Policy":cspHeader(nonce),"X-Content-Type-Options":"nosniff","Referrer-Policy":"no-referrer"}),res.end(typeof html=="function"?html(nonce):String(html||""));return}if(req.method==="GET"&&media&&req.url!=="/"&&!req.url.startsWith("/?")){let u=new URL(req.url,"http://127.0.0.1"),out=null;try{out=await media(u.pathname,u.searchParams,ctx)}catch(err){res.writeHead(500,{"Content-Type":"text/plain"}),res.end(String(err?.message||err));return}if(out&&out.buffer){res.writeHead(200,{"Content-Type":out.contentType||"application/octet-stream","Cache-Control":"no-store"}),res.end(out.buffer);return}}if(req.method==="POST"){if(!sameOriginLoopback(req)){res.writeHead(403,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"cross-origin request blocked"}));return}let path3=req.url.split("?")[0],handler=routes[path3];if(!handler){res.writeHead(404,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"no route "+path3}));return}let body="",tooLarge=!1;req.on("data",c=>{tooLarge||(body+=c,body.length>MAX_BODY&&(tooLarge=!0,res.writeHead(413,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"request body too large"})),req.destroy()))}),req.on("end",async()=>{if(tooLarge)return;let payload;try{payload=body?JSON.parse(body):{}}catch{res.writeHead(400,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"malformed JSON body"}));return}try{let out=await handler(payload,ctx);res.writeHead(200,{"Content-Type":"application/json"}),res.end(JSON.stringify(out??{ok:!0}))}catch(err){res.writeHead(500,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:String(err?.message||err)}))}});return}res.writeHead(404),res.end()});return new Promise(resolve=>{server2.listen(port,"127.0.0.1",()=>{let port2=server2.address().port;resolve({port:port2,url:`http://127.0.0.1:${port2}`,getState,setState,log,close:()=>{for(let res of clients)try{res.end()}catch{}clients.clear(),server2.close()}})})})}async function safe(fn){try{return{ok:!0,data:await fn()}}catch(err){let msg=String(err?.message||err),code=String(err?.code||err?.statusCode||""),{needsAuth,kind}=classifyAuthError(msg,code);return{ok:!1,error:msg,needsAuth,authKind:kind}}}var azure_exports={};__export(azure_exports,{accountMeta:()=>accountMeta,blobSize:()=>blobSize,clearQueueMessages:()=>clearQueueMessages,collectBlobs:()=>collectBlobs,createContainer:()=>createContainer,createQueue:()=>createQueue,createShare:()=>createShare,createShareDirectory:()=>createShareDirectory,createTable:()=>createTable,credential:()=>credential,deleteBlob:()=>deleteBlob,deleteContainer:()=>deleteContainer,deleteEntity:()=>deleteEntity,deleteQueue:()=>deleteQueue,deleteQueueMessage:()=>deleteQueueMessage,deleteShare:()=>deleteShare,deleteShareItem:()=>deleteShareItem,deleteTable:()=>deleteTable,discoveryFailure:()=>discoveryFailure,downloadBlob:()=>downloadBlob,downloadSasBlob:()=>downloadSasBlob,downloadShareFile:()=>downloadShareFile,ensureAccountMeta:()=>ensureAccountMeta,generateBlobSas:()=>generateBlobSas,generateContainerSas:()=>generateContainerSas,getAccountConfig:()=>getAccountConfig,listBlobDirectory:()=>listBlobDirectory,listBlobs:()=>listBlobs,listContainers:()=>listContainers,listQueues:()=>listQueues,listSasBlobs:()=>listSasBlobs,listShareItems:()=>listShareItems,listShares:()=>listShares,listStorageAccounts:()=>listStorageAccounts,listStorageAccountsForSubs:()=>listStorageAccountsForSubs,listStoragePage:()=>listStoragePage,listSubscriptions:()=>listSubscriptions,listTables:()=>listTables,listTenantSubscriptions:()=>listTenantSubscriptions,listTenants:()=>listTenants,openInStorageExplorer:()=>openInStorageExplorer,peekQueueMessages:()=>peekQueueMessages,queryTableEntities:()=>queryTableEntities,registerAccountContext:()=>registerAccountContext,requireNewResource:()=>requireNewResource,resetTokenCache:()=>resetTokenCache,resolveBlobService:()=>resolveBlobService,resolveQueueService:()=>resolveQueueService,resolveShareService:()=>resolveShareService,resolveTableService:()=>resolveTableService,sendQueueMessage:()=>sendQueueMessage,setBlobTier:()=>setBlobTier,signIn:()=>signIn,storageCredential:()=>storageCredential,storageExplorerDeepLink:()=>storageExplorerDeepLink,storageExplorerInstalled:()=>storageExplorerInstalled,storagePage:()=>storagePage,tokenFor:()=>tokenFor,updateQueueMessage:()=>updateQueueMessage,uploadBlob:()=>uploadBlob,uploadShareFile:()=>uploadShareFile,upsertEntity:()=>upsertEntity,warmup:()=>warmup});import{execFile,spawn,spawnSync}from"node:child_process";import{createHash}from"node:crypto";import{accessSync,constants,readFileSync}from"node:fs";import{cp,lstat,mkdir,mkdtemp,readFile,readdir,rename,rm,stat,writeFile}from"node:fs/promises";import os from"node:os";import path from"node:path";import{fileURLToPath}from"node:url";function resolveStudioRoot(moduleUrl){let directory=path.dirname(fileURLToPath(moduleUrl)),candidates=path.basename(directory)==="src"?[directory,path.dirname(directory)]:[directory];for(let candidate of candidates)for(let metadata of["studio-package.json","package.json"])try{return accessSync(path.join(candidate,metadata)),candidate}catch(error){if(error.code!=="ENOENT")throw error}throw new Error(`Missing canvas package metadata for ${moduleUrl}`)}function resolveStudioBuildInfo(moduleUrl,fallbackVersion="unknown"){let version=fallbackVersion,revision="unknown";try{let manifest;try{manifest=readFileSync(new URL("./studio-package.json",moduleUrl),"utf8")}catch(error){if(error.code!=="ENOENT")throw error;manifest=readFileSync(path.join(resolveStudioRoot(moduleUrl),"package.json"),"utf8")}version=JSON.parse(manifest).version||fallbackVersion}catch{}try{revision=createHash("sha256").update(readFileSync(new URL(moduleUrl))).digest("hex").slice(0,10)}catch{}return{version,revision}}var ICONS={vscode:'',github:'',azure:''},COMMAND_BUTTONS=` +`),clients.add(res),req.on("close",()=>clients.delete(res));return}if(req.method==="GET"&&(req.url==="/"||req.url.startsWith("/?"))){let nonce=randomBytes(16).toString("base64");res.writeHead(200,{"Content-Type":"text/html; charset=utf-8","Content-Security-Policy":cspHeader(nonce),"X-Content-Type-Options":"nosniff","Referrer-Policy":"no-referrer"}),res.end(typeof html=="function"?html(nonce):String(html||""));return}if(req.method==="GET"&&media&&req.url!=="/"&&!req.url.startsWith("/?")){let u=new URL(req.url,"http://127.0.0.1"),out=null;try{out=await media(u.pathname,u.searchParams,ctx)}catch(err){res.writeHead(500,{"Content-Type":"text/plain"}),res.end(String(err?.message||err));return}if(out&&out.buffer){res.writeHead(200,{"Content-Type":out.contentType||"application/octet-stream","Cache-Control":"no-store"}),res.end(out.buffer);return}}if(req.method==="POST"){if(!sameOriginLoopback(req)){res.writeHead(403,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"cross-origin request blocked"}));return}let path3=req.url.split("?")[0],handler=routes[path3];if(!handler){res.writeHead(404,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"no route "+path3}));return}let body="",tooLarge=!1;req.on("data",c=>{tooLarge||(body+=c,body.length>MAX_BODY&&(tooLarge=!0,res.writeHead(413,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"request body too large"})),req.destroy()))}),req.on("end",async()=>{if(tooLarge)return;let payload;try{payload=body?JSON.parse(body):{}}catch{res.writeHead(400,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:"malformed JSON body"}));return}try{let source=req.headers["x-storage-invocation-source"]==="model"?"model":"panel",out=await handler(payload,ctx,source);res.writeHead(200,{"Content-Type":"application/json"}),res.end(JSON.stringify(out??{ok:!0}))}catch(err){res.writeHead(500,{"Content-Type":"application/json"}),res.end(JSON.stringify({ok:!1,error:String(err?.message||err)}))}});return}res.writeHead(404),res.end()});return new Promise(resolve=>{server2.listen(port,"127.0.0.1",()=>{let port2=server2.address().port;resolve({port:port2,url:`http://127.0.0.1:${port2}`,getState,setState,log,close:()=>{for(let res of clients)try{res.end()}catch{}clients.clear(),server2.close()}})})})}async function safe(fn){try{return{ok:!0,data:await fn()}}catch(err){let msg=String(err?.message||err),code=String(err?.code||err?.statusCode||""),{needsAuth,kind}=classifyAuthError(msg,code);return{ok:!1,error:msg,needsAuth,authKind:kind}}}var azure_exports={};__export(azure_exports,{accountMeta:()=>accountMeta,blobSize:()=>blobSize,clearQueueMessages:()=>clearQueueMessages,collectBlobs:()=>collectBlobs,createContainer:()=>createContainer,createQueue:()=>createQueue,createShare:()=>createShare,createShareDirectory:()=>createShareDirectory,createTable:()=>createTable,credential:()=>credential,deleteBlob:()=>deleteBlob,deleteContainer:()=>deleteContainer,deleteEntity:()=>deleteEntity,deleteQueue:()=>deleteQueue,deleteQueueMessage:()=>deleteQueueMessage,deleteShare:()=>deleteShare,deleteShareItem:()=>deleteShareItem,deleteTable:()=>deleteTable,discoveryFailure:()=>discoveryFailure,downloadBlob:()=>downloadBlob,downloadSasBlob:()=>downloadSasBlob,downloadShareFile:()=>downloadShareFile,ensureAccountMeta:()=>ensureAccountMeta,generateBlobSas:()=>generateBlobSas,generateContainerSas:()=>generateContainerSas,getAccountConfig:()=>getAccountConfig,listBlobDirectory:()=>listBlobDirectory,listBlobs:()=>listBlobs,listContainers:()=>listContainers,listQueues:()=>listQueues,listSasBlobs:()=>listSasBlobs,listShareItems:()=>listShareItems,listShares:()=>listShares,listStorageAccounts:()=>listStorageAccounts,listStorageAccountsForSubs:()=>listStorageAccountsForSubs,listStoragePage:()=>listStoragePage,listSubscriptions:()=>listSubscriptions,listTables:()=>listTables,listTenantSubscriptions:()=>listTenantSubscriptions,listTenants:()=>listTenants,openInStorageExplorer:()=>openInStorageExplorer,peekQueueMessages:()=>peekQueueMessages,queryTableEntities:()=>queryTableEntities,registerAccountContext:()=>registerAccountContext,requireNewResource:()=>requireNewResource,resetTokenCache:()=>resetTokenCache,resolveBlobService:()=>resolveBlobService,resolveQueueService:()=>resolveQueueService,resolveShareService:()=>resolveShareService,resolveTableService:()=>resolveTableService,sendQueueMessage:()=>sendQueueMessage,setBlobTier:()=>setBlobTier,signIn:()=>signIn,storageCredential:()=>storageCredential,storageExplorerDeepLink:()=>storageExplorerDeepLink,storageExplorerInstalled:()=>storageExplorerInstalled,storagePage:()=>storagePage,tokenFor:()=>tokenFor,updateQueueMessage:()=>updateQueueMessage,uploadBlob:()=>uploadBlob,uploadShareFile:()=>uploadShareFile,upsertEntity:()=>upsertEntity,warmup:()=>warmup});import{execFile,spawn,spawnSync}from"node:child_process";import{createHash}from"node:crypto";import{accessSync,constants,readFileSync}from"node:fs";import{cp,lstat,mkdir,mkdtemp,readFile,readdir,rename,rm,stat,writeFile}from"node:fs/promises";import os from"node:os";import path from"node:path";import{fileURLToPath}from"node:url";function resolveStudioRoot(moduleUrl){let directory=path.dirname(fileURLToPath(moduleUrl)),candidates=path.basename(directory)==="src"?[directory,path.dirname(directory)]:[directory];for(let candidate of candidates)for(let metadata of["studio-package.json","package.json"])try{return accessSync(path.join(candidate,metadata)),candidate}catch(error){if(error.code!=="ENOENT")throw error}throw new Error(`Missing canvas package metadata for ${moduleUrl}`)}function resolveStudioBuildInfo(moduleUrl,fallbackVersion="unknown"){let version=fallbackVersion,revision="unknown";try{let manifest;try{manifest=readFileSync(new URL("./studio-package.json",moduleUrl),"utf8")}catch(error){if(error.code!=="ENOENT")throw error;manifest=readFileSync(path.join(resolveStudioRoot(moduleUrl),"package.json"),"utf8")}version=JSON.parse(manifest).version||fallbackVersion}catch{}try{revision=createHash("sha256").update(readFileSync(new URL(moduleUrl))).digest("hex").slice(0,10)}catch{}return{version,revision}}var ICONS={vscode:'',github:'',azure:''},COMMAND_BUTTONS=` `;function canExecuteCommand(file,osName=os.platform()){try{return accessSync(file,osName==="win32"?constants.F_OK:constants.X_OK),!0}catch{return!1}}var WINDOWS_COMMAND_META_CHARACTERS=/([()\][%!^"`<>&|;, *?])/g;function escapeWindowsCommandMetaCharacters(value){return value.replace(WINDOWS_COMMAND_META_CHARACTERS,"^$1")}function assertWindowsCommandValue(value){let text=String(value);if(/[\r\n]/.test(text))throw new Error("Command arguments contain unsupported Windows command characters.");return text}function escapeWindowsCommand(value){return escapeWindowsCommandMetaCharacters(assertWindowsCommandValue(value))}function quoteWindowsCommandArgument(value){let text=assertWindowsCommandValue(value);return text=text.replace(/(?=(\\+?)?)\1"/g,'$1$1\\"'),text=text.replace(/(?=(\\+?)?)\1$/,"$1$1"),escapeWindowsCommandMetaCharacters(`"${text}"`)}function windowsCommandShellLine(command,args=[]){return`"${[escapeWindowsCommand(command),...args.map(quoteWindowsCommandArgument)].join(" ")}"`}function spawnSpecForLocated(located,args,env,osName){return osName==="win32"&&/\.(?:cmd|bat)$/i.test(located.path)?{file:env.ComSpec||env.COMSPEC||path.win32.join(env.SystemRoot||env.SYSTEMROOT||"C:\\Windows","System32","cmd.exe"),args:["/d","/s","/c",windowsCommandShellLine(located.path,args)],env,located,windowsVerbatimArguments:!0}:{file:located.path,args:[...args],env,located,windowsVerbatimArguments:!1}}var AZURE_CLI_ENV_KEYS=["HOME","USER","LOGNAME","TMPDIR","LANG","LC_ALL","SHELL","USERPROFILE","HOMEDRIVE","HOMEPATH","SystemRoot","SYSTEMROOT","TEMP","TMP","ComSpec","COMSPEC","PATHEXT","AZURE_CONFIG_DIR","XDG_CONFIG_HOME","HTTP_PROXY","HTTPS_PROXY","ALL_PROXY","NO_PROXY","http_proxy","https_proxy","all_proxy","no_proxy","REQUESTS_CA_BUNDLE","CURL_CA_BUNDLE","SSL_CERT_FILE","SSL_CERT_DIR","AZURE_CLI_DISABLE_CONNECTION_VERIFICATION"];function sourcePath(source){return source.PATH||source.Path||source.path||""}function locateAzureCli(source=process.env,osName=os.platform(),exists=candidate=>canExecuteCommand(candidate,osName)){let paths=osName==="win32"?path.win32:path.posix,inherited=sourcePath(source).split(paths.delimiter).filter(Boolean),home=source.HOME||source.USERPROFILE||os.homedir(),known=osName==="win32"?[paths.join(source.ProgramFiles||"C:\\Program Files","Microsoft SDKs","Azure","CLI2","wbin"),paths.join(source["ProgramFiles(x86)"]||"C:\\Program Files (x86)","Microsoft SDKs","Azure","CLI2","wbin")]:["/opt/homebrew/bin","/usr/local/bin","/opt/local/bin",paths.join(home,".local","bin"),paths.join(home,"bin"),"/usr/bin","/bin","/snap/bin"],directories=[...new Set([...inherited,...known])],searched=[];if(source.AZURE_CLI_PATH&&(searched.push(source.AZURE_CLI_PATH),exists(source.AZURE_CLI_PATH)))return{found:!0,path:source.AZURE_CLI_PATH,directory:paths.dirname(source.AZURE_CLI_PATH),searched};for(let directory of directories)for(let name of osName==="win32"?["az.cmd","az.exe","az"]:["az"]){let candidate=paths.join(directory,name);if(searched.push(candidate),exists(candidate))return{found:!0,path:candidate,directory,searched}}return{found:!1,path:"",directory:"",searched}}var AzureCliNotFoundError=class extends Error{constructor(searched=[]){super("Azure CLI executable was not found. Install Azure CLI or set AZURE_CLI_PATH to its absolute path, then retry."),this.name="AzureCliNotFoundError",this.code="AZURE_CLI_NOT_FOUND",this.searched=searched}};function azureCliChildEnv(source=process.env,located=locateAzureCli(source),osName=os.platform()){let paths=osName==="win32"?path.win32:path.posix,env={},copied=new Set;for(let key of AZURE_CLI_ENV_KEYS){let identity=osName==="win32"?key.toLowerCase():key;source[key]!=null&&!copied.has(identity)&&(env[key]=source[key],copied.add(identity))}let inherited=sourcePath(source).split(paths.delimiter).filter(Boolean),fallbacks=osName==="win32"?[paths.join(source.SystemRoot||source.SYSTEMROOT||"C:\\Windows","System32"),source.SystemRoot||source.SYSTEMROOT||"C:\\Windows"]:["/opt/homebrew/bin","/usr/local/bin","/usr/bin","/bin"];return env.PATH=[...new Set([...located.found?[located.directory]:[],...inherited,...fallbacks])].join(paths.delimiter),osName==="win32"&&!env.ComSpec&&!env.COMSPEC&&(env.ComSpec=paths.join(source.SystemRoot||source.SYSTEMROOT||"C:\\Windows","System32","cmd.exe")),env}function azureCliSpawnSpec(args,{env=process.env,osName=os.platform(),locate=locateAzureCli}={}){assertFixtureAuthenticationDenied("az");let located=locate(env,osName);if(!located.found)throw new AzureCliNotFoundError(located.searched);let childEnv=azureCliChildEnv(env,located,osName);return spawnSpecForLocated(located,args,childEnv,osName)}async function runAzureCliJson(args,subscription,{timeout=3e4,maxBuffer=1024*1024,env=process.env,execute=execFileText,locate=locateAzureCli,osName=os.platform(),signal}={}){let full=subscription?[...args,"--subscription",subscription]:[...args],withFlag=full.includes("--only-show-errors")?full:[...full,"--only-show-errors"],{stdout}=await runAzureCliText(withFlag,{env,maxBuffer,timeout,execute,locate,osName,...signal?{signal}:{}}),text=stdout.trim();return text?JSON.parse(text):null}async function runAzureCliText(args,{env=process.env,execute=execFileText,locate=locateAzureCli,osName=os.platform(),...options}={}){let command=azureCliSpawnSpec(args,{env,locate,osName});return execute(command.file,command.args,{maxBuffer:1024*1024,...options,env:command.env,windowsVerbatimArguments:command.windowsVerbatimArguments})}var fixtureAuthCommands=[];function assertFixtureAuthenticationDenied(command,args=[]){if(process.env.FUNCTION_STUDIO_TEST_MODE!=="1")return;let name=path.win32.basename(String(command)).replace(/\.(?:cmd|bat|exe)$/i,"").toLowerCase(),gitRemote=name==="git"&&args.some(arg=>["clone","fetch","pull","push","ls-remote","credential"].includes(arg)),packageInstall=["npm","npx","pip","pip3","uv"].includes(name)&&args.some(arg=>["install","exec","add","run"].includes(arg))||/^python(?:\d+(?:\.\d+)?)?$/.test(name)&&args.includes("pip")&&args.includes("install");if(!(!gitRemote&&!packageInstall&&!["az","azd","gh","azureauth","security","codesign","npx"].includes(name)))throw fixtureAuthCommands.push(name),new Error(`Live authentication command '${name}' is disabled in Hosted Skills fixture mode. Inject a fake service instead.`)}function execFileText(file,args,options={}){return new Promise((resolve,reject)=>{assertFixtureAuthenticationDenied(file,args),execFile(file,args,{maxBuffer:1024*1024,...options},(error,stdout,stderr)=>{if(error){error.stdout=stdout,error.stderr=stderr,reject(error);return}resolve({stdout,stderr})})})}import{BlobServiceClient,StorageSharedKeyCredential,ContainerClient,generateBlobSASQueryParameters,BlobSASPermissions,ContainerSASPermissions,SASProtocol}from"./sdk/blob.mjs";import{QueueServiceClient,StorageSharedKeyCredential as QueueSharedKey}from"./sdk/queue.mjs";import{ShareServiceClient,StorageSharedKeyCredential as FileSharedKey}from"./sdk/file.mjs";import{TableServiceClient,TableClient,AzureNamedKeyCredential}from"./sdk/tables.mjs";import{spawn as spawn2,spawnSync as spawnSync2}from"node:child_process";import{existsSync}from"node:fs";var ARM="https://management.azure.com",API="2023-01-01";scrubHostSessionEnv();var _credentials=new Map;function credential(tenantId=""){return _credentials.has(tenantId)||_credentials.set(tenantId,{async getToken(scopes,options){if(options?.claims)throw new Error("Azure CLI cannot satisfy this claims challenge. Sign in to the account's tenant and retry.");let args=["account","get-access-token","--scope",...Array.isArray(scopes)?scopes:[scopes],"--output","json"];tenantId&&args.push("--tenant",tenantId);let result=await runAzureCliJson(args,void 0,{env:inheritedCliEnvironment()}),expiresOnTimestamp=result?.expires_on?Number(result.expires_on)*1e3:Date.parse(result?.expiresOn);if(!result?.accessToken||!Number.isFinite(expiresOnTimestamp))throw new Error("Azure CLI returned an invalid access token response. Run `az login` and retry.");return{token:result.accessToken,expiresOnTimestamp}}}),_credentials.get(tenantId)}var _tokens=new Map;async function tokenFor(scope,tenantId="",acquire=credential){let key=JSON.stringify([tenantId,scope]),now=Date.now(),hit=_tokens.get(key);if(hit?.token&&hit.exp-12e4>now)return hit.token;if(hit?.inflight)return hit.inflight;let inflight=acquire(tenantId).getToken(scope).then(t=>{if(!t?.token)throw new Error(`Could not acquire a token for ${scope}. Run \`az login\`.`);return _tokens.set(key,{token:t.token,exp:t.expiresOnTimestamp||now+33e5}),t.token}).catch(e=>{throw _tokens.delete(key),e});return _tokens.set(key,{...hit||{},inflight}),inflight}function storageCredential(account){if(!accountMeta(account)&&!knownAccount(account))throw new Error(`Storage account '${account}' has no known subscription or tenant context. Select its subscription before accessing storage.`);return{getToken:async(scopes,options)=>{let{tenantId}=await ensureAccountMeta(account);if(options?.claims)return credential(tenantId).getToken(scopes,options);let scope=Array.isArray(scopes)?scopes[0]:scopes;return{token:await tokenFor(scope,tenantId),expiresOnTimestamp:_tokens.get(JSON.stringify([tenantId,scope]))?.exp??Date.now()+33e5}}}}var _warmed=null;function warmup(){return _warmed||(_warmed=Promise.allSettled([tokenFor("https://management.azure.com/.default"),tokenFor("https://storage.azure.com/.default")]),_warmed)}function resetTokenCache(){_tokens.clear(),_credentials.clear(),svcCache.clear(),queueCache.clear(),tableCache.clear(),shareCache.clear(),_warmed=null}function azMissingMsg(e){let m=String(e&&e.message||e);return/ENOENT/i.test(m)?"Azure CLI (az) was not found on PATH. Install it from aka.ms/azure-cli, then try again, or run `az login` manually.":"Could not start az login: "+m}var _signInInflight=null;function signIn(opts={}){return _signInInflight||(_signInInflight=_doSignIn(opts).finally(()=>{_signInInflight=null}),_signInInflight)}function _doSignIn({tenantId,timeoutMs=18e4,_spawn=spawn2}={}){return new Promise(resolve=>{let isWin=process.platform==="win32",env={...inheritedCliEnvironment(),AZURE_CORE_LOGIN_EXPERIENCE_V2:"off"},child;try{child=_spawn("az",["login","--only-show-errors",...tenantId?["--tenant",tenantId]:[]],{env,windowsHide:!0,shell:isWin,stdio:["ignore","ignore","pipe"]})}catch(e){resolve({ok:!1,error:azMissingMsg(e)});return}let err="";child.stderr&&child.stderr.on("data",d=>{err+=String(d)});let timer=setTimeout(()=>{try{child.kill()}catch{}resolve({ok:!1,error:"Sign-in timed out after 3 minutes. Finish az login in the browser, then click Refresh."})},timeoutMs);child.on("error",e=>{clearTimeout(timer),resolve({ok:!1,error:azMissingMsg(e)})}),child.on("close",code=>{if(clearTimeout(timer),code===0){resetTokenCache(),resolve({ok:!0});return}resolve({ok:!1,error:err.trim()||"az login exited with code "+code})})})}var SE_RESOURCE_TYPE={blob:"Azure.BlobContainer",file:"Azure.FileShare",queue:"Azure.Queue",table:"Azure.Table"};function storageExplorerDeepLink({account,kind,item}={}){let m=account?accountMeta(account):null;if(!m||!m.subscriptionId||!m.resourceGroup)return null;let accountId=`/subscriptions/${m.subscriptionId}/resourceGroups/${m.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${m.name}`,link="storageexplorer://v=1&accountid="+encodeURIComponent(accountId)+"&subscriptionid="+encodeURIComponent(m.subscriptionId),rt=kind?SE_RESOURCE_TYPE[kind]:null;return rt&&item&&(link+="&resourcetype="+encodeURIComponent(rt)+"&resourcename="+encodeURIComponent(item)),link}function storageExplorerExe(){if(process.platform!=="win32")return null;let candidates=[process.env.LOCALAPPDATA&&process.env.LOCALAPPDATA+"\\Programs\\Microsoft Azure Storage Explorer\\StorageExplorer.exe",process.env.ProgramFiles&&process.env.ProgramFiles+"\\Microsoft Azure Storage Explorer\\StorageExplorer.exe",process.env["ProgramFiles(x86)"]&&process.env["ProgramFiles(x86)"]+"\\Microsoft Azure Storage Explorer\\StorageExplorer.exe"].filter(Boolean);for(let p of candidates)try{if(existsSync(p))return p}catch{}return null}var SE_DOWNLOAD="https://aka.ms/storageexplorer",_seInstalled;function storageExplorerInstalled(){if(_seInstalled!==void 0)return _seInstalled;if(storageExplorerExe())return _seInstalled=!0;if(process.platform==="win32"){for(let key of["HKCU\\Software\\Classes\\storageexplorer","HKCR\\storageexplorer"])try{let r=spawnSync2("reg",["query",key],{stdio:"ignore",windowsHide:!0});if(r&&r.status===0)return _seInstalled=!0}catch{}return _seInstalled=!1}return _seInstalled=null}function openBrowser(url){let opts={detached:!0,stdio:"ignore"},file,args;process.platform==="win32"?(file=process.env.ComSpec||"cmd.exe",args=["/c","start","",url]):process.platform==="darwin"?(file="open",args=[url]):(file="xdg-open",args=[url]);try{let c=spawn2(file,args,opts);c.on("error",()=>{}),c.unref&&c.unref()}catch{}}function seMissingMsg(e){let m=String(e&&e.message||e);return/ENOENT/i.test(m)?"Azure Storage Explorer isn't installed. Get the free desktop app at "+SE_DOWNLOAD+".":"Could not launch Azure Storage Explorer: "+m}function openInStorageExplorer({account,kind,item}={}){return new Promise(resolve=>{if(storageExplorerInstalled()===!1){openBrowser(SE_DOWNLOAD),resolve({ok:!1,notInstalled:!0,install:SE_DOWNLOAD});return}let link=storageExplorerDeepLink({account,kind,item}),exe=storageExplorerExe(),opts={detached:!0,stdio:"ignore"},file,args;if(exe)file=exe,args=link?[link]:[];else if(process.platform==="win32"){if(!link){openBrowser(SE_DOWNLOAD),resolve({ok:!1,notInstalled:!0,install:SE_DOWNLOAD});return}file=process.env.ComSpec||"cmd.exe",args=["/c","start","",link]}else process.platform==="darwin"?(file="open",args=link?[link]:["-a","Microsoft Azure Storage Explorer"]):(file="StorageExplorer",args=link?[link]:[]);let onFail=e=>{if(e&&/ENOENT/i.test(String(e.message||e))){openBrowser(SE_DOWNLOAD),resolve({ok:!1,notInstalled:!0,install:SE_DOWNLOAD});return}resolve({ok:!1,error:seMissingMsg(e)})},child;try{child=spawn2(file,args,opts)}catch(e){onFail(e);return}child.on("error",onFail),child.on("spawn",()=>{try{child.unref()}catch{}resolve({ok:!0,deepLink:!!link})})})}async function armToken(tenantId){return tokenFor("https://management.azure.com/.default",tenantId)}async function armGet(path3,tenantId){let res=await fetch(`${ARM}${path3}`,{headers:{Authorization:`Bearer ${await armToken(tenantId)}`}});if(!res.ok){let e=new Error(`ARM GET ${path3} \u2192 HTTP ${res.status}`);throw e.statusCode=res.status,e}return res.json()}async function armPost(path3,tenantId){let res=await fetch(`${ARM}${path3}`,{method:"POST",headers:{Authorization:`Bearer ${await armToken(tenantId)}`}});if(!res.ok){let e=new Error(`ARM POST ${path3} \u2192 HTTP ${res.status}`);throw e.statusCode=res.status,e}return res.json()}async function armGetAll(path3,tenantId){let token=await armToken(tenantId),url=path3.startsWith("http")?path3:`${ARM}${path3}`,items=[];for(;url;){let res=await fetch(url,{headers:{Authorization:`Bearer ${token}`}});if(!res.ok){let e=new Error(`ARM GET ${url} \u2192 HTTP ${res.status}`);throw e.statusCode=res.status,e}let j=await res.json();if(Array.isArray(j.value))for(let v of j.value)items.push(v);url=j.nextLink||j["@odata.nextLink"]||null}return items}async function listSubscriptions({runCli=runAzureCliJson}={}){let value=await runCli(["account","list","--all","--output","json"],void 0,{env:inheritedCliEnvironment()});if(!Array.isArray(value))throw new Error("Azure CLI returned an invalid subscription list.");if(value.some(s=>s.id&&!s.tenantId))throw new Error("Azure CLI returned a subscription without a tenant. Refresh your Azure CLI login.");return value.filter(s=>s.id).map(s=>({subscriptionId:s.id,displayName:s.name,tenantId:s.tenantId,state:s.state}))}async function listTenants({read=armGetAll}={}){return(await read("/tenants?api-version=2022-12-01")).map(t=>({tenantId:t.tenantId,displayName:t.displayName||t.defaultDomain||"Unnamed directory",defaultDomain:t.defaultDomain||"",current:!0}))}async function listTenantSubscriptions(tenantId,{read=armGetAll}={}){if(!tenantId)throw new Error("A tenant is required.");return(await read("/subscriptions?api-version=2022-12-01",tenantId)).map(s=>({subscriptionId:s.subscriptionId,displayName:s.displayName,tenantId,state:s.state}))}function discoveryFailure(error){let text=String(error?.message||error),auth=classifyAuthError(text),status=error?.statusCode||/\bHTTP (\d{3})\b/.exec(text)?.[1];return{error:text,needsAuth:auth.needsAuth,message:humanErrorSummary(text,auth.needsAuth,status)}}var accountIndex=new Map,accountContexts=new Set,accountResolutions=new Map;function registerAccountContext(getContext){return accountContexts.add(getContext),()=>accountContexts.delete(getContext)}function knownAccount(account){for(let getContext of[...accountContexts].reverse()){let{accounts=[],subscriptions=[]}=getContext(),metadata=accounts.find(a=>a.name===account);if(metadata)return{metadata,subscription:subscriptions.find(s=>s.subscriptionId===metadata.subscriptionId)}}return null}function completeAccount(metadata){return!!(metadata?.tenantId&&metadata.subscriptionId&&metadata.resourceGroup)}function rgFromId(id){let m=/\/resourceGroups\/([^/]+)\//i.exec(id||"");return m?m[1]:""}async function mapLimit(items,limit,fn){let results=[],i=0,workers=Array.from({length:Math.min(limit,items.length)},async()=>{for(;i`${e.subscriptionName}: ${e.error}`).join("; "));return accounts}async function listStorageAccountsForSubs(subs,{concurrency=16,readAccounts=armGetAll}={}){let out=[],errors=[];return await mapLimit(subs,concurrency,async sub=>{let accts;try{if(!sub.tenantId)throw new Error("Subscription tenant is unknown. Refresh subscriptions.");accts=await readAccounts(`/subscriptions/${sub.subscriptionId}/providers/Microsoft.Storage/storageAccounts?api-version=${API}`,sub.tenantId)}catch(e){errors.push({subscriptionName:sub.displayName||sub.subscriptionId,subscriptionId:sub.subscriptionId,tenantId:sub.tenantId,...discoveryFailure(e)});return}for(let a of accts){let acct={name:a.name,subscriptionId:sub.subscriptionId,subscriptionName:sub.displayName,tenantId:sub.tenantId,resourceGroup:rgFromId(a.id),location:a.location||"",sku:a.sku?.name||"",kind:a.kind||""};accountIndex.set(a.name,acct),out.push(acct)}}),out.sort((x,y)=>x.name.localeCompare(y.name)),{accounts:out,errors}}function accountMeta(account){let cached=accountIndex.get(account);if(completeAccount(cached))return cached;let known=knownAccount(account);if(!known)return cached||null;let metadata={...known.metadata,tenantId:known.metadata.tenantId||known.subscription?.tenantId,resourceGroup:known.metadata.resourceGroup||rgFromId(known.metadata.id)};return completeAccount(metadata)?(accountIndex.set(account,metadata),metadata):cached||null}async function ensureAccountMeta(account,{readAccounts=armGetAll}={}){let cached=accountMeta(account);if(completeAccount(cached))return cached;if(accountResolutions.has(account))return accountResolutions.get(account);let known=knownAccount(account),subscriptionId=known?.metadata.subscriptionId,tenantId=known?.metadata.tenantId||known?.subscription?.tenantId;if(!subscriptionId||!tenantId)throw new Error(`Storage account '${account}' has no known subscription or tenant context. Select its subscription before accessing storage.`);let pending=(async()=>{let result=await listStorageAccountsForSubs([{...known.subscription,subscriptionId,tenantId}],{readAccounts});if(result.errors.length)throw new Error(`Could not resolve storage account '${account}': ${result.errors[0].error}`);let resolved=result.accounts.find(a=>a.name===account);if(!completeAccount(resolved))throw new Error(`Storage account '${account}' was not returned with complete metadata in subscription '${subscriptionId}'. Refresh that subscription's accounts.`);return resolved})();accountResolutions.set(account,pending);try{return await pending}finally{accountResolutions.get(account)===pending&&accountResolutions.delete(account)}}async function getAccountConfig(account){try{let m=await ensureAccountMeta(account),j=await armGet(`/subscriptions/${m.subscriptionId}/resourceGroups/${m.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${account}?api-version=${API}`,m.tenantId),p=j.properties||{};return{ok:!0,exists:!0,allowSharedKeyAccess:p.allowSharedKeyAccess!==!1,publicNetworkAccess:p.publicNetworkAccess||"Enabled",defaultAction:p.networkAcls?.defaultAction||"Allow",provisioningState:p.provisioningState||"",sku:j.sku?.name||"",location:j.location||""}}catch(err){return err.statusCode===404?{ok:!0,exists:!1}:{ok:!1,error:String(err?.message||err)}}}async function fetchAccountKey(account){let m=await ensureAccountMeta(account),key=(await armPost(`/subscriptions/${m.subscriptionId}/resourceGroups/${m.resourceGroup}/providers/Microsoft.Storage/storageAccounts/${account}/listKeys?api-version=${API}`,m.tenantId))?.keys?.[0]?.value;if(!key)throw new Error("listKeys returned no key");return key}var svcCache=new Map;function accountUrl(account){return`https://${account}.blob.core.windows.net`}async function probe(svc){await svc.listContainers().byPage({maxPageSize:1})[Symbol.asyncIterator]().next()}function isAuthError(e){let s=e?.statusCode??e?.response?.status;if(s===401||s===403)return!0;let txt=`${e?.code||e?.name||""} ${e?.message||""}`;return/Authoriz|Authentic|KeyBasedAuthenticationNotPermitted|InvalidAuthenticationInfo|NoAuthenticationInformation/i.test(txt)}async function buildEntry(account){await ensureAccountMeta(account);try{let key=await fetchAccountKey(account);return{svc:new BlobServiceClient(accountUrl(account),new StorageSharedKeyCredential(account,key)),mode:"sharedKey",key}}catch{return{svc:new BlobServiceClient(accountUrl(account),storageCredential(account)),mode:"aad",key:null}}}async function withBlob(account,fn){let entry=svcCache.get(account);entry||(entry=await buildEntry(account),svcCache.set(account,entry));try{return await fn(entry.svc)}catch(e){if(entry.mode==="sharedKey"&&isAuthError(e)){let aad={svc:new BlobServiceClient(accountUrl(account),storageCredential(account)),mode:"aad",key:null};return svcCache.set(account,aad),await fn(aad.svc)}throw e}}async function resolveBlobService(account){if(await ensureAccountMeta(account),svcCache.has(account))return svcCache.get(account);let keyErr=null,aadErr=null;try{let key=await fetchAccountKey(account),cred=new StorageSharedKeyCredential(account,key),svc=new BlobServiceClient(accountUrl(account),cred);await probe(svc);let entry={svc,mode:"sharedKey",key};return svcCache.set(account,entry),entry}catch(err){keyErr=err}try{let svc=new BlobServiceClient(accountUrl(account),storageCredential(account));await probe(svc);let entry={svc,mode:"aad",key:null};return svcCache.set(account,entry),entry}catch(err){aadErr=err}throw new Error(`Could not access '${account}': key fallback ${keyErr?.code||keyErr?.message||"n/a"}; AAD ${aadErr?.code||aadErr?.message||"n/a"}.`)}async function listContainers(account){return withBlob(account,async svc=>{let out=[];for await(let c of svc.listContainers())out.push({name:c.name,lastModified:c.properties?.lastModified||null});return out})}function mapBlob(b){return{name:b.name,size:b.properties?.contentLength??0,lastModified:b.properties?.lastModified?new Date(b.properties.lastModified).toISOString():null,contentType:b.properties?.contentType||"",accessTier:b.properties?.accessTier||"",blobType:b.properties?.blobType||"",tags:b.tags||null}}async function collectBlobs(cc,max,{prefix,onProgress,requireTags=!1}={}){let out=[],truncated=!1,read=async includeTags=>{for await(let b of cc.listBlobsFlat({prefix,includeTags})){if(out.length>=max){truncated=!0;break}out.push(mapBlob(b)),out.length%1e3===0&&onProgress?.(out.length)}onProgress?.(out.length)};try{await read(!0)}catch(error){if(error?.statusCode!==403||requireTags)throw error;out.length=0,truncated=!1,onProgress?.(0),await read(!1)}return out.truncated=truncated,out.limit=max,out}async function listBlobs(account,container,{max=5e3,complete=!1,...options}={}){return withBlob(account,svc=>collectBlobs(svc.getContainerClient(container),complete?1/0:max,options))}async function listBlobDirectory(account,container,{prefix="",max=5e3,list}={}){let collect=async(cc,includeTags)=>{let blobs=[],prefixes=[],truncated=!1;for await(let item of cc.listBlobsByHierarchy("/",{prefix,includeTags})){if(blobs.length+prefixes.length>=max){truncated=!0;break}item.kind==="prefix"?prefixes.push(item.name):blobs.push(mapBlob(item))}return{blobs,prefixes,truncated,limit:max}},read=async cc=>{try{return await collect(cc,!0)}catch(error){if(error?.statusCode!==403)throw error;return collect(cc,!1)}};return list?read({listBlobsByHierarchy:list}):withBlob(account,svc=>read(svc.getContainerClient(container)))}async function storagePage(iterator,continuationToken,maxPageSize=100){let result=await iterator.byPage({continuationToken:continuationToken||void 0,maxPageSize}).next();return result.done?{page:[],nextToken:null}:{page:result.value,nextToken:result.value.continuationToken||null}}async function listStoragePage({account,kind,resource,dir="",prefix="",token,sasUrl,resolve,tableClient}){let read=iterator=>storagePage(iterator,token);if(kind==="blob"||kind==="sas"){let fromClient=async svc=>{let cc=kind==="sas"?svc:resource?svc.getContainerClient(resource):null;if(!cc){let{page,nextToken}=await read(svc.listContainers());return{items:(page.containerItems||[]).map(c=>({name:c.name,lastModified:c.properties?.lastModified||null})),nextToken}}let result;try{result=await read(kind==="sas"?cc.listBlobsFlat({includeTags:!0}):cc.listBlobsByHierarchy("/",{prefix,includeTags:!0}))}catch(error){if(error?.statusCode!==403)throw error;result=await read(kind==="sas"?cc.listBlobsFlat():cc.listBlobsByHierarchy("/",{prefix}))}return{items:(result.page.segment?.blobItems||[]).map(mapBlob),prefixes:(result.page.segment?.blobPrefixes||[]).map(p=>p.name),nextToken:result.nextToken}};return kind==="sas"?fromClient(new ContainerClient(sasUrl)):resolve?fromClient((await resolve(account)).svc):withBlob(account,fromClient)}if(kind==="file"){let{svc}=await(resolve||resolveShareService)(account),{page,nextToken}=await read(resource?svc.getShareClient(resource).getDirectoryClient(dir).listFilesAndDirectories({includeTimestamps:!0}):svc.listShares());return{items:resource?[...(page.segment?.directoryItems||[]).map(x=>({name:x.name,kind:"directory",size:null,lastModified:null})),...(page.segment?.fileItems||[]).map(x=>({name:x.name,kind:"file",size:x.properties?.contentLength??0,lastModified:x.properties?.lastModified?new Date(x.properties.lastModified).toISOString():null}))]:(page.shareItems||[]).map(x=>({name:x.name,quotaGiB:x.properties?.quota??null})),nextToken}}if(kind==="queue"){let{svc}=await(resolve||resolveQueueService)(account),{page,nextToken}=await read(svc.listQueues());return{items:(page.queueItems||[]).map(x=>({name:x.name,approximateMessagesCount:null})),nextToken}}if(kind==="table"){let{svc,cred}=await(resolve||resolveTableService)(account),{page,nextToken}=await read(resource?(tableClient||new TableClient(tableUrl(account),resource,cred)).listEntities():svc.listTables());if(!resource)return{items:page.map(x=>({name:x.name})),nextToken};let columns=new Set(["partitionKey","rowKey","timestamp"]);return{entities:{rows:page.map(ent=>{let row={};for(let[key,value]of Object.entries(ent))key.startsWith("odata.")||key==="etag"||(columns.add(key),row[key]=value&&typeof value.toISOString=="function"?value.toISOString():value);return row}),columns:[...columns]},nextToken}}throw new Error("This storage view does not support continuation pages.")}async function createContainer(account,container,{resolve=resolveBlobService}={}){let{svc}=await resolve(account);return await svc.getContainerClient(container).create(),{ok:!0,container}}async function deleteContainer(account,container){let{svc}=await resolveBlobService(account);return await svc.getContainerClient(container).delete(),{ok:!0}}async function deleteBlob(account,container,name){let{svc}=await resolveBlobService(account);return await svc.getContainerClient(container).getBlockBlobClient(name).delete(),{ok:!0}}async function setBlobTier(account,container,name,tier){let{svc}=await resolveBlobService(account);return await svc.getContainerClient(container).getBlockBlobClient(name).setAccessTier(tier),{ok:!0}}async function uploadBlob(account,container,name,buffer,contentType){let{svc}=await resolveBlobService(account),cc=svc.getContainerClient(container);return await cc.createIfNotExists(),await cc.getBlockBlobClient(name).uploadData(buffer,contentType?{blobHTTPHeaders:{blobContentType:contentType}}:void 0),{ok:!0,name,size:buffer.length}}async function downloadBlob(account,container,name,{maxBytes=null}={}){let{svc}=await resolveBlobService(account),bc=svc.getContainerClient(container).getBlockBlobClient(name);if(Number.isFinite(maxBytes)&&maxBytes>0){let resp=await bc.download(0,maxBytes);return await streamToBuffer(resp.readableStreamBody)}return await bc.downloadToBuffer()}async function blobSize(account,container,name){let{svc}=await resolveBlobService(account),props=await svc.getContainerClient(container).getBlockBlobClient(name).getProperties();return Number(props.contentLength)||0}async function streamToBuffer(stream){let chunks=[];for await(let chunk of stream)chunks.push(Buffer.isBuffer(chunk)?chunk:Buffer.from(chunk));return Buffer.concat(chunks)}async function generateBlobSas(account,container,name,{minutes=60,permissions="r"}={}){let entry=await resolveBlobService(account),perms=BlobSASPermissions.parse(permissions||"r"),startsOn=new Date(Date.now()-5*6e4),expiresOn=new Date(Date.now()+Math.max(1,minutes)*6e4),common={containerName:container,blobName:name,permissions:perms,startsOn,expiresOn,protocol:SASProtocol.Https},sas,mode;if(entry.mode==="sharedKey"&&entry.key){let cred=new StorageSharedKeyCredential(account,entry.key);sas=generateBlobSASQueryParameters(common,cred).toString(),mode="account-key"}else{let udk=await entry.svc.getUserDelegationKey(startsOn,expiresOn);sas=generateBlobSASQueryParameters(common,udk,account).toString(),mode="user-delegation"}let encName=name.split("/").map(encodeURIComponent).join("/");return{url:`${accountUrl(account)}/${container}/${encName}?${sas}`,expiresOn:expiresOn.toISOString(),permissions:permissions||"r",mode}}async function generateContainerSas(account,container,{minutes=1440,permissions="rl"}={}){let entry=await resolveBlobService(account),perms=ContainerSASPermissions.parse(permissions||"rl"),startsOn=new Date(Date.now()-5*6e4),expiresOn=new Date(Date.now()+Math.max(1,minutes)*6e4),common={containerName:container,permissions:perms,startsOn,expiresOn,protocol:SASProtocol.Https},sas,mode;if(entry.mode==="sharedKey"&&entry.key){let cred=new StorageSharedKeyCredential(account,entry.key);sas=generateBlobSASQueryParameters(common,cred).toString(),mode="account-key"}else{let udk=await entry.svc.getUserDelegationKey(startsOn,expiresOn);sas=generateBlobSASQueryParameters(common,udk,account).toString(),mode="user-delegation"}return{url:`${accountUrl(account)}/${container}?${sas}`,expiresOn:expiresOn.toISOString(),permissions:permissions||"rl",mode}}async function listSasBlobs(sasUrl,{max=5e3,complete=!1,...options}={}){let cc=new ContainerClient(sasUrl);return collectBlobs(cc,complete?1/0:max,options)}async function downloadSasBlob(sasUrl,name){return await new ContainerClient(sasUrl).getBlockBlobClient(name).downloadToBuffer()}var queueCache=new Map;function queueUrl(account){return`https://${account}.queue.core.windows.net`}async function resolveQueueService(account){if(await ensureAccountMeta(account),queueCache.has(account))return queueCache.get(account);let keyErr=null;try{let key=await fetchAccountKey(account),svc=new QueueServiceClient(queueUrl(account),new QueueSharedKey(account,key));await svc.listQueues().byPage({maxPageSize:1})[Symbol.asyncIterator]().next();let e={svc,mode:"sharedKey"};return queueCache.set(account,e),e}catch(err){keyErr=err}try{let svc=new QueueServiceClient(queueUrl(account),storageCredential(account));await svc.listQueues().byPage({maxPageSize:1})[Symbol.asyncIterator]().next();let e={svc,mode:"aad"};return queueCache.set(account,e),e}catch(err){throw new Error(`Could not access queues for '${account}': key ${keyErr?.code||keyErr?.message||"n/a"}; AAD ${err?.code||err?.message||"n/a"}.`)}}async function listQueues(account){let{svc}=await resolveQueueService(account),out=[];for await(let q of svc.listQueues()){let count=null;try{count=(await svc.getQueueClient(q.name).getProperties()).approximateMessagesCount??null}catch{}out.push({name:q.name,approximateMessagesCount:count})}return out}async function peekQueueMessages(account,queue,{max=32}={}){let{svc}=await resolveQueueService(account);return((await svc.getQueueClient(queue).peekMessages({numberOfMessages:Math.min(max,32)})).peekedMessageItems||[]).map(m=>({messageId:m.messageId,messageText:m.messageText,insertedOn:m.insertedOn?new Date(m.insertedOn).toISOString():null,dequeueCount:m.dequeueCount??0}))}function requireNewResource(items,name,kind){if(items.some(item=>item.name.toLowerCase()===name.toLowerCase()))throw new Error(`A ${kind} named "${name}" already exists. Choose a different name.`)}async function createQueue(account,queue){requireNewResource(await listQueues(account),queue,"queue");let{svc}=await resolveQueueService(account);return await svc.getQueueClient(queue).create(),{ok:!0}}async function deleteQueue(account,queue){let{svc}=await resolveQueueService(account);return await svc.getQueueClient(queue).delete(),{ok:!0}}async function sendQueueMessage(account,queue,text){let{svc}=await resolveQueueService(account);return{ok:!0,messageId:(await svc.getQueueClient(queue).sendMessage(String(text??""))).messageId}}async function clearQueueMessages(account,queue){let{svc}=await resolveQueueService(account);return await svc.getQueueClient(queue).clearMessages(),{ok:!0}}async function withReceivedMessage(account,queue,messageId,act){let{svc}=await resolveQueueService(account),qc=svc.getQueueClient(queue),msgs=(await qc.receiveMessages({numberOfMessages:32,visibilityTimeout:30})).receivedMessageItems||[],target=msgs.find(m=>m.messageId===messageId);for(let m of msgs)if(m.messageId!==messageId)try{await qc.updateMessage(m.messageId,m.popReceipt,void 0,0)}catch{}if(!target)throw new Error("That message isn't among the first 32 in the queue, so it can't be changed directly.");return await act(qc,target),{ok:!0}}async function updateQueueMessage(account,queue,messageId,text){return withReceivedMessage(account,queue,messageId,(qc,target)=>qc.updateMessage(messageId,target.popReceipt,String(text??""),0))}async function deleteQueueMessage(account,queue,messageId){return withReceivedMessage(account,queue,messageId,(qc,target)=>qc.deleteMessage(messageId,target.popReceipt))}var tableCache=new Map;function tableUrl(account){return`https://${account}.table.core.windows.net`}async function resolveTableService(account){if(await ensureAccountMeta(account),tableCache.has(account))return tableCache.get(account);let keyErr=null;try{let key=await fetchAccountKey(account),cred=new AzureNamedKeyCredential(account,key),svc=new TableServiceClient(tableUrl(account),cred);await svc.listTables().byPage({maxPageSize:1})[Symbol.asyncIterator]().next();let e={svc,mode:"sharedKey",cred};return tableCache.set(account,e),e}catch(err){keyErr=err}try{let cred=storageCredential(account),svc=new TableServiceClient(tableUrl(account),cred);await svc.listTables().byPage({maxPageSize:1})[Symbol.asyncIterator]().next();let e={svc,mode:"aad",cred};return tableCache.set(account,e),e}catch(err){throw new Error(`Could not access tables for '${account}': key ${keyErr?.code||keyErr?.message||"n/a"}; AAD ${err?.code||err?.message||"n/a"}.`)}}async function listTables(account){let{svc}=await resolveTableService(account),out=[];for await(let t of svc.listTables())out.push({name:t.name});return out}async function queryTableEntities(account,table,{max=100}={}){let{cred}=await resolveTableService(account),tc=new TableClient(tableUrl(account),table,cred),rows=[],cols=new Set(["partitionKey","rowKey","timestamp"]);for await(let ent of tc.listEntities()){let row={};for(let k of Object.keys(ent)){if(k.startsWith("odata.")||k==="etag")continue;cols.add(k);let v=ent[k];row[k]=v&&typeof v=="object"&&typeof v.toISOString=="function"?v.toISOString():v}if(rows.push(row),rows.length>=max)break}return{columns:Array.from(cols),rows}}async function createTable(account,table){requireNewResource(await listTables(account),table,"table");let{svc}=await resolveTableService(account);return await svc.createTable(table),{ok:!0}}async function deleteTable(account,table){let{svc}=await resolveTableService(account);return await svc.deleteTable(table),{ok:!0}}async function upsertEntity(account,table,entity){if(!entity||!entity.partitionKey||!entity.rowKey)throw new Error("entity requires partitionKey and rowKey");let{cred}=await resolveTableService(account);return await new TableClient(tableUrl(account),table,cred).upsertEntity(entity,"Merge"),{ok:!0}}async function deleteEntity(account,table,partitionKey,rowKey){let{cred}=await resolveTableService(account);return await new TableClient(tableUrl(account),table,cred).deleteEntity(partitionKey,rowKey),{ok:!0}}var shareCache=new Map;function fileUrl(account){return`https://${account}.file.core.windows.net`}async function resolveShareService(account){if(await ensureAccountMeta(account),shareCache.has(account))return shareCache.get(account);let keyErr=null;try{let key=await fetchAccountKey(account),svc=new ShareServiceClient(fileUrl(account),new FileSharedKey(account,key));await svc.listShares().byPage({maxPageSize:1})[Symbol.asyncIterator]().next();let e={svc,mode:"sharedKey"};return shareCache.set(account,e),e}catch(err){keyErr=err}try{let svc=new ShareServiceClient(fileUrl(account),storageCredential(account),{fileRequestIntent:"backup"});await svc.listShares().byPage({maxPageSize:1})[Symbol.asyncIterator]().next();let e={svc,mode:"aad"};return shareCache.set(account,e),e}catch(err){throw new Error(`Could not access file shares for '${account}': key ${keyErr?.code||keyErr?.message||"n/a"}; AAD ${err?.code||err?.message||"n/a"}.`)}}async function listShares(account){let{svc}=await resolveShareService(account),out=[];for await(let s of svc.listShares())out.push({name:s.name,quotaGiB:s.properties?.quota??null});return out}async function listShareItems(account,share,dir="",{resolve=resolveShareService}={}){let{svc}=await resolve(account),dc=svc.getShareClient(share).getDirectoryClient(dir||""),out=[];for await(let item of dc.listFilesAndDirectories({includeTimestamps:!0}))out.push({name:item.name,kind:item.kind,size:item.kind==="file"?item.properties?.contentLength??0:null,lastModified:item.kind==="file"&&item.properties?.lastModified?new Date(item.properties.lastModified).toISOString():null});return out}async function createShare(account,share){requireNewResource(await listShares(account),share,"file share");let{svc}=await resolveShareService(account);return await svc.getShareClient(share).create(),{ok:!0}}async function deleteShare(account,share){let{svc}=await resolveShareService(account);return await svc.getShareClient(share).delete(),{ok:!0}}async function createShareDirectory(account,share,dir){let{svc}=await resolveShareService(account);return await svc.getShareClient(share).getDirectoryClient(dir).create(),{ok:!0}}async function uploadShareFile(account,share,name,buffer,dir="",{resolve=resolveShareService}={}){let{svc}=await resolve(account),fc=svc.getShareClient(share).getDirectoryClient(dir).getFileClient(name);return await fc.create(buffer.length),buffer.length&&await fc.uploadData(buffer),{ok:!0,size:buffer.length}}async function downloadShareFile(account,share,name,dir="",{resolve=resolveShareService}={}){let{svc}=await resolve(account);return await svc.getShareClient(share).getDirectoryClient(dir).getFileClient(name).downloadToBuffer()}async function deleteShareItem(account,share,name,kind,dir="",{resolve=resolveShareService}={}){let{svc}=await resolve(account),sc=svc.getShareClient(share);return kind==="directory"?await sc.getDirectoryClient(dir?`${dir}/${name}`:name).delete():await sc.getDirectoryClient(dir).getFileClient(name).delete(),{ok:!0}}function fmtBytes(n){if(n=Number(n)||0,n<1024)return`${n} B`;let u=["KB","MB","GB","TB","PB"],i=-1;do n/=1024,i++;while(n>=1024&&i=10?0:1)} ${u[i]}`}function globToRe(glob){let body=String(glob).replace(/[.+^${}()|[\]\\]/g,"\\$&").replace(/\*/g,".*").replace(/\?/g,".");return new RegExp("^"+body+"$","i")}function typeFamily(contentType,name){let ct=String(contentType||"").toLowerCase(),ext=String(name||"").toLowerCase().split(".").pop();return/^image\//.test(ct)||/^(png|jpe?g|gif|webp|bmp|svg|tiff?)$/.test(ext)?"image":/^video\//.test(ct)||/^(mp4|mov|avi|mkv|webm)$/.test(ext)?"video":/^audio\//.test(ct)||/^(mp3|wav|flac|ogg|m4a)$/.test(ext)?"audio":/json|xml|csv|text\/|javascript|yaml/.test(ct)||/^(json|xml|csv|txt|log|md|yaml|yml|js|ts|html?|css)$/.test(ext)?"text/data":/pdf|word|excel|powerpoint|officedocument|msword/.test(ct)||/^(pdf|docx?|xlsx?|pptx?)$/.test(ext)?"document":/zip|gzip|tar|compress|x-7z|rar/.test(ct)||/^(zip|gz|tar|7z|rar|bz2)$/.test(ext)?"archive":/octet-stream/.test(ct)||/^(bin|dat|iso|vhd|img)$/.test(ext)?"binary":ct.split("/")[0]||"other"}function applyBlobFilter(blobs,c={}){let list=Array.isArray(blobs)?blobs:[],nowMs=c.now?Date.parse(c.now):Date.now(),re=c.namePattern&&String(c.namePattern).trim()?globToRe(String(c.namePattern).trim()):null,prefix=c.prefix!=null&&String(c.prefix)!==""?String(c.prefix).toLowerCase():null,suffix=c.suffix!=null&&String(c.suffix)!==""?String(c.suffix).toLowerCase():null,contains=c.contains!=null&&String(c.contains)!==""?String(c.contains).toLowerCase():null,min=c.minSize!=null&&c.minSize!==""?Number(c.minSize):null,max=c.maxSize!=null&&c.maxSize!==""?Number(c.maxSize):null,older=c.olderThanDays!=null&&c.olderThanDays!==""?Number(c.olderThanDays):null,newer=c.newerThanDays!=null&&c.newerThanDays!==""?Number(c.newerThanDays):null,type=c.type&&String(c.type).trim()&&c.type!=="all"?String(c.type).trim():null,tiers=normList(c.tiers).map(t=>t.toLowerCase()),blobTypes=normList(c.blobTypes).map(t=>t.toLowerCase()),tagKeys=normList(c.tagKeys),tags=c.tags&&typeof c.tags=="object"?c.tags:null,DAY=864e5;return list.filter(b=>{let nm=String(b.name||""),nml=nm.toLowerCase();if(re&&!re.test(nm)||prefix&&!nml.startsWith(prefix)||suffix&&!nml.endsWith(suffix)||contains&&nml.indexOf(contains)===-1)return!1;let sz=Number(b.size)||0;if(min!=null&&szmax)return!1;if((older!=null||newer!=null)&&b.lastModified){let ageMs=nowMs-Date.parse(b.lastModified);if(older!=null&&!(ageMs>older*DAY)||newer!=null&&!(ageMs<=newer*DAY))return!1}else if(older!=null||newer!=null)return!1;if(type&&typeFamily(b.contentType,b.name)!==type||tiers.length&&tiers.indexOf(String(b.accessTier||"").toLowerCase())===-1||blobTypes.length&&blobTypes.indexOf(String(b.blobType||"").toLowerCase())===-1)return!1;if(tagKeys.length){let bt=b.tags||{};for(let k of tagKeys)if(!(k in bt))return!1}if(tags){let bt=b.tags||{};for(let k of Object.keys(tags))if(String(bt[k])!==String(tags[k]))return!1}return!0})}function normList(v){return v==null||v===""?[]:Array.isArray(v)?v.map(x=>String(x).trim()).filter(Boolean):String(v).split(",").map(x=>x.trim()).filter(Boolean)}function computeBlobStats(blobs){let list=Array.isArray(blobs)?blobs:[],n=list.length,sizes=list.map(b=>Number(b.size)||0),total=sizes.reduce((a,s)=>a+s,0),max=n?Math.max(...sizes):0,avg=n?Math.round(total/n):0,buckets=[{label:"0\u20131 KB",lo:0,hi:1024},{label:"1 KB\u20131 MB",lo:1024,hi:1048576},{label:"1\u2013100 MB",lo:1048576,hi:104857600},{label:"100 MB\u20131 GB",lo:104857600,hi:1073741824},{label:">1 GB",lo:1073741824,hi:1/0}].map(bk=>({label:bk.label,n:sizes.filter(s=>s>=bk.lo&&s({label,n:v.n,bytes:v.bytes})).sort((a,b)=>b.bytes-a.bytes),byTierMap=new Map;for(let b of list){let tier=String(b.accessTier||"").trim()||"\u2014",cur=byTierMap.get(tier)||{n:0,bytes:0};cur.n++,cur.bytes+=Number(b.size)||0,byTierMap.set(tier,cur)}let byTier=[...byTierMap.entries()].map(([label,v])=>({label,n:v.n,bytes:v.bytes})).sort((a,b)=>b.bytes-a.bytes),hasTier=byTier.some(t=>t.label!=="\u2014"),byExtMap=new Map;for(let b of list){let parts=String(b.name||"").toLowerCase().split("."),ext=parts.length>1?"."+parts.pop():"(none)",cur=byExtMap.get(ext)||{n:0,bytes:0};cur.n++,cur.bytes+=Number(b.size)||0,byExtMap.set(ext,cur)}let byExt=[...byExtMap.entries()].map(([label,v])=>({label,n:v.n,bytes:v.bytes})).sort((a,b)=>b.bytes-a.bytes).slice(0,8),topLargest=[...list].sort((a,b)=>(Number(b.size)||0)-(Number(a.size)||0)).slice(0,10).map(b=>({name:b.name,size:Number(b.size)||0,sizeh:fmtBytes(b.size),lastModified:b.lastModified||null}));return{count:n,totalBytes:total,totalh:fmtBytes(total),avgBytes:avg,avgh:fmtBytes(avg),maxBytes:max,maxh:fmtBytes(max),buckets,byType,byTier:hasTier?byTier:[],byExt,topLargest}}function filterAndStat(blobs,criteria={}){let filtered=applyBlobFilter(blobs,criteria);return{filtered,stats:computeBlobStats(filtered),criteria}}var OVERWRITE={overwrite:"true",skip:"false","if-newer":"ifSourceNewer"};function resolutionFlag(resolution){return OVERWRITE[resolution]||"true"}function planTransfer(sourceBlobs,destBlobs,resolution="overwrite"){let src=Array.isArray(sourceBlobs)?sourceBlobs:[],dstByName=new Map((Array.isArray(destBlobs)?destBlobs:[]).map(b=>[b.name,b])),conflicts=[],newFiles=[];for(let b of src)dstByName.has(b.name)?conflicts.push(b):newFiles.push(b);let toTransfer;if(resolution==="skip")toTransfer=newFiles.slice();else if(resolution==="if-newer"){let newerConflicts=conflicts.filter(b=>{let d=dstByName.get(b.name),s=Date.parse(b.lastModified||""),dm=Date.parse(d?.lastModified||"");return Number.isFinite(s)&&Number.isFinite(dm)?s>dm:!0});toTransfer=newFiles.concat(newerConflicts)}else toTransfer=src.slice();let bytes=toTransfer.reduce((a,b)=>a+(Number(b.size)||0),0),skipped=src.length-toTransfer.length;return{resolution,sourceCount:src.length,destCount:dstByName.size,conflictCount:conflicts.length,newCount:newFiles.length,transferCount:toTransfer.length,skippedCount:skipped,transferBytes:bytes,transferBytesh:fmtBytes(bytes),conflicts:conflicts.slice(0,100).map(b=>({name:b.name,size:Number(b.size)||0,sizeh:fmtBytes(b.size),lastModified:b.lastModified||null})),toTransfer:toTransfer.map(b=>b.name)}}function azcopyCommand({sourceUrl,destUrl,resolution="overwrite",includeNames=null,allSource=!1}={}){let q=s=>`"${String(s||"").replace(/[\\"$`]/g,"\\$&")}"`,flags=[`--overwrite=${resolutionFlag(resolution)}`];if(allSource)flags.push("--recursive");else if(Array.isArray(includeNames)){if(!includeNames.length)return{command:null,needsLogin:!1,empty:!0};flags.push(`--include-path ${q(includeNames.join(";"))}`),flags.push("--recursive")}else flags.push("--recursive");let needsLogin=!/[?&]sig=/.test(String(sourceUrl||""))||!/[?&]sig=/.test(String(destUrl||""));return{command:`azcopy copy ${q(sourceUrl)} ${q(destUrl)} ${flags.join(" ")}`,needsLogin}}function sniffText(bytes){let buf=bytes instanceof Uint8Array?bytes:new Uint8Array(bytes||[]);if(!buf.length)return{isText:!0,text:""};let control=0;for(let i=0;i13&&c<32||c===127)&&control++}if(control/buf.length>.1)return{isText:!1,text:null};try{let text=new TextDecoder("utf-8",{fatal:!1}).decode(buf);return(text.match(/\uFFFD/g)||[]).length/Math.max(text.length,1)>.1?{isText:!1,text:null}:{isText:!0,text}}catch{return{isText:!1,text:null}}}var MAGIC=[{off:0,hex:"67 6C 54 46",format:"glTF binary (GLB) 3D model",category:"3D model"},{off:0,hex:"89 50 4E 47 0D 0A 1A 0A",format:"PNG image",category:"image"},{off:0,hex:"FF D8 FF",format:"JPEG image",category:"image"},{off:0,hex:"47 49 46 38",format:"GIF image",category:"image"},{off:0,hex:"42 4D",format:"BMP image",category:"image"},{off:0,hex:"49 49 2A 00",format:"TIFF image (little-endian)",category:"image"},{off:0,hex:"4D 4D 00 2A",format:"TIFF image (big-endian)",category:"image"},{off:0,hex:"00 00 01 00",format:"ICO icon",category:"image"},{off:0,hex:"25 50 44 46",format:"PDF document",category:"document"},{off:0,hex:"D0 CF 11 E0 A1 B1 1A E1",format:"Legacy MS Office (OLE2: .doc/.xls/.ppt)",category:"document"},{off:0,hex:"50 4B 03 04",format:"ZIP archive (or OOXML .docx/.xlsx/.pptx, JAR, APK)",category:"archive"},{off:0,hex:"50 4B 05 06",format:"ZIP archive (empty)",category:"archive"},{off:0,hex:"1F 8B",format:"gzip archive",category:"archive"},{off:0,hex:"42 5A 68",format:"bzip2 archive",category:"archive"},{off:0,hex:"37 7A BC AF 27 1C",format:"7-Zip archive",category:"archive"},{off:0,hex:"52 61 72 21 1A 07",format:"RAR archive",category:"archive"},{off:0,hex:"FD 37 7A 58 5A 00",format:"XZ archive",category:"archive"},{off:257,hex:"75 73 74 61 72",format:"TAR archive",category:"archive"},{off:0,hex:"7F 45 4C 46",format:"ELF executable / shared object",category:"executable"},{off:0,hex:"4D 5A",format:"Windows PE executable (.exe/.dll)",category:"executable"},{off:0,hex:"CA FE BA BE",format:"Java class file",category:"executable"},{off:0,hex:"00 61 73 6D",format:"WebAssembly (WASM) module",category:"executable"},{off:0,hex:"4F 67 67 53",format:"Ogg media",category:"audio"},{off:0,hex:"49 44 33",format:"MP3 audio (ID3)",category:"audio"},{off:0,hex:"66 4C 61 43",format:"FLAC audio",category:"audio"},{off:0,hex:"53 51 4C 69 74 65 20 66 6F 72 6D 61 74 20 33 00",format:"SQLite database",category:"database"},{off:0,hex:"50 41 52 31",format:"Apache Parquet",category:"data"},{off:0,hex:"4F 62 6A 01",format:"Apache Avro",category:"data"}],EXT_FORMAT={glb:["glTF binary (GLB) 3D model","3D model"],gltf:["glTF 3D model","3D model"],obj:["Wavefront OBJ 3D model","3D model"],fbx:["Autodesk FBX 3D model","3D model"],stl:["STL 3D model","3D model"],usdz:["USDZ 3D scene","3D model"],ply:["PLY 3D model","3D model"],png:["PNG image","image"],jpg:["JPEG image","image"],jpeg:["JPEG image","image"],gif:["GIF image","image"],webp:["WebP image","image"],bmp:["BMP image","image"],tiff:["TIFF image","image"],ico:["ICO icon","image"],heic:["HEIC image","image"],avif:["AVIF image","image"],svg:["SVG vector image","image"],psd:["Photoshop document","image"],pdf:["PDF document","document"],docx:["Word document (OOXML)","document"],xlsx:["Excel workbook (OOXML)","document"],pptx:["PowerPoint deck (OOXML)","document"],doc:["Legacy Word document","document"],xls:["Legacy Excel workbook","document"],ppt:["Legacy PowerPoint deck","document"],zip:["ZIP archive","archive"],gz:["gzip archive","archive"],tgz:["gzip TAR archive","archive"],tar:["TAR archive","archive"],rar:["RAR archive","archive"],"7z":["7-Zip archive","archive"],bz2:["bzip2 archive","archive"],xz:["XZ archive","archive"],zst:["Zstandard archive","archive"],mp3:["MP3 audio","audio"],wav:["WAV audio","audio"],flac:["FLAC audio","audio"],ogg:["Ogg audio","audio"],m4a:["AAC audio","audio"],mp4:["MP4 video","video"],mov:["QuickTime video","video"],avi:["AVI video","video"],mkv:["Matroska video","video"],webm:["WebM video","video"],exe:["Windows executable","executable"],dll:["Windows library","executable"],so:["Shared object","executable"],wasm:["WebAssembly module","executable"],parquet:["Apache Parquet","data"],avro:["Apache Avro","data"],orc:["Apache ORC","data"],db:["SQLite database","database"],sqlite:["SQLite database","database"],bin:["Raw binary","binary"],dat:["Raw data","binary"]};function hexHead(buf,n=16){let out=[];for(let i=0;i{let sig=hex.split(" ").map(h=>parseInt(h,16));if(off+sig.length>buf.length)return!1;for(let i=0;ibuf.length)return{version,truncated:!0};json=new TextDecoder("utf-8").decode(buf.subarray(dstart,dend));break}off=dend}if(json==null)return{version,truncated:!0};let g;try{g=JSON.parse(json)}catch{return{version,truncated:!0}}let n=a=>Array.isArray(a)?a.length:0,accessors=g.accessors||[],vertices=0,triangles=0;for(let mesh of g.meshes||[])for(let prim of mesh.primitives||[]){let posIdx=prim.attributes&&prim.attributes.POSITION;posIdx!=null&&accessors[posIdx]&&(vertices+=accessors[posIdx].count||0),prim.indices!=null&&accessors[prim.indices]&&(triangles+=Math.floor((accessors[prim.indices].count||0)/3))}return{version,truncated:!1,meshes:n(g.meshes),materials:n(g.materials),textures:n(g.textures),images:n(g.images),animations:n(g.animations),nodes:n(g.nodes),accessors:n(g.accessors),vertices,triangles,generator:g.asset&&g.asset.generator||null,gltfVersion:g.asset&&g.asset.version||null}}function previewLang(name){let m=/\.([a-z0-9]+)$/i.exec(String(name||"")),ext=m?m[1].toLowerCase():"";return{json:"json",ndjson:"json",js:"javascript",mjs:"javascript",ts:"typescript",csv:"csv",tsv:"csv",xml:"xml",html:"html",htm:"html",md:"markdown",yml:"yaml",yaml:"yaml",log:"log",txt:"text",ini:"ini",conf:"ini",sql:"sql",sh:"shell",py:"python",cs:"csharp"}[ext]||ext||"text"}function parseSasUrl(rawUrl){let u;try{u=new URL(String(rawUrl||"").trim())}catch{return{ok:!1,error:"That doesn't look like a URL."}}if(u.protocol!=="https:"){let emulator=/^(127\.0\.0\.1|localhost|\[::1\])$/i.test(u.hostname);if(!(u.protocol==="http:"&&emulator))return{ok:!1,error:"SAS URL must use HTTPS \u2014 a SAS signature is a bearer credential and must not travel over plain HTTP."}}let q=u.searchParams;if(!q.get("sig"))return{ok:!1,error:"No SAS signature (sig=) in the URL \u2014 that's a plain URL, not a SAS."};let host=u.hostname,accountMatch=/^([^.]+)\.blob\./.exec(host),account=accountMatch?accountMatch[1]:host,segs=u.pathname.replace(/^\/+/,"").split("/"),container=segs[0]||"",blobPath=segs.slice(1).join("/"),perms=q.get("sp")||"",expiry=q.get("se")||"",resource=q.get("sr")||(blobPath?"b":container?"c":""),now=Date.now(),expMs=expiry?Date.parse(expiry):NaN,expired=Number.isFinite(expMs)&&expMs{let done=!1,finish=value=>{done||(done=!0,resolve(value))};execFile2("azcopy",["--version"],{timeout:4e3,windowsHide:!0},(err,stdout)=>{if(err){let missing=err.code==="ENOENT";finish({installed:!1,version:null,reason:missing?"notFound":"error",hint:missing?`AzCopy isn't on your PATH. Install it from ${AZCOPY_INSTALL_URL}, then re-open the transfer plan.`:`AzCopy was found but couldn't be queried (${String(err.message||err).slice(0,120)}).`,installUrl:AZCOPY_INSTALL_URL});return}let version=(String(stdout||"").match(/\d+\.\d+\.\d+/)||[])[0]||null;finish({installed:!0,version,reason:"ok",hint:null,installUrl:AZCOPY_INSTALL_URL})}).on("error",()=>finish({installed:!1,version:null,reason:"notFound",hint:`AzCopy isn't on your PATH. Install it from ${AZCOPY_INSTALL_URL}, then re-open the transfer plan.`,installUrl:AZCOPY_INSTALL_URL}))})),azcopyProbe}import"node:fs";var BUILD_INFO=resolveStudioBuildInfo(new URL("./extension.mjs",import.meta.url));import"node:fs";var TENANT_ICON=` @@ -2665,10 +2665,10 @@ Revision: `+info.revision+` ## Feedback -`;return(FEEDBACK_BASE+"?title="+encodeURIComponent(title)+"&body="+encodeURIComponent(body)).replace(/&/g,"&")}function escAttr(s){return String(s??"").replace(/[&<>"]/g,function(c){return{"&":"&","<":"<",">":">",'"':"""}[c]})}var BUILD_STAMP_HTML='v'+escAttr(BUILD_INFO.version)+"+"+escAttr(BUILD_INFO.revision)+"",FEEDBACK_HTML='',BODY_WITH_IDENTITY=BODY.replace("","").replace("",BUILD_STAMP_HTML).replace("",FEEDBACK_HTML);function pageHtml(nonce){let n=nonce?' nonce="'+nonce+'"':"";return'Azure Storage"+BODY_WITH_IDENTITY+'"}var DL_ROOT=path2.join(os2.homedir(),"Downloads","azure-storage");function redactEndpointError(message,endpoints){let msg=String(message).replace(/([?&]sig=)[^&\s"'<>]*/gi,"$1[redacted]");for(let raw of endpoints)try{let url=new URL(raw);msg=msg.replaceAll(raw,`${url.origin}${url.pathname}?[redacted]`);let signature=url.searchParams.get("sig");if(signature)for(let value of[signature,encodeURIComponent(signature)])msg=msg.replaceAll(value,"[redacted]")}catch{}return msg}var PREFS_FILE=path2.join(os2.homedir(),".azure-storage","prefs.json");async function readPrefs(){try{let raw=await fs.readFile(PREFS_FILE,"utf8"),obj=JSON.parse(raw)||{};return{pinned:obj.pinned&&typeof obj.pinned=="object"?obj.pinned:{},hidden:obj.hidden&&typeof obj.hidden=="object"?obj.hidden:{},hiddenTenants:obj.hiddenTenants||{},hiddenSubscriptions:obj.hiddenSubscriptions||{}}}catch{return{pinned:{},hidden:{}}}}async function writePrefs({pinned,hidden,hiddenTenants,hiddenSubscriptions}={}){let safePinned=pinned&&typeof pinned=="object"?pinned:{},safeHidden=hidden&&typeof hidden=="object"?hidden:{};await fs.mkdir(path2.dirname(PREFS_FILE),{recursive:!0});let previous=await readPrefs(),prefs={pinned:safePinned,hidden:safeHidden,hiddenTenants:hiddenTenants||previous.hiddenTenants||{},hiddenSubscriptions:hiddenSubscriptions||previous.hiddenSubscriptions||{}};return await fs.writeFile(PREFS_FILE,JSON.stringify(prefs)),prefs}async function saveDownload(container,name,buffer){let root=path2.resolve(DL_ROOT),full=path2.resolve(root,String(container||""),String(name||"").replace(/[\\/]+/g,path2.sep));if(full!==root&&!full.startsWith(root+path2.sep))throw new Error(`refusing to write outside the download folder: ${container}/${name}`);return await fs.mkdir(path2.dirname(full),{recursive:!0}),await fs.writeFile(full,buffer),full}async function openFolder(dir){let target=path2.resolve(dir||DL_ROOT),isDir=!1;try{isDir=(await fs.stat(target)).isDirectory()}catch{isDir=!1}if(!isDir){if(dir)return{ok:!1,target,error:"folder not found"};await fs.mkdir(target,{recursive:!0}).catch(()=>{}),isDir=!0}let attempts=process.platform==="win32"?[["explorer.exe",[target]],[process.env.ComSpec||"cmd.exe",["/c","start","",target]]]:process.platform==="darwin"?[["open",[target]]]:[["xdg-open",[target]]];for(let[cmd,args]of attempts)if(await new Promise(resolve=>{try{let child=spawn3(cmd,args,{detached:!0,stdio:"ignore"}),settled=!1;child.on("error",()=>{settled||(settled=!0,resolve(!1))}),child.unref(),setTimeout(()=>{settled||(settled=!0,resolve(!0))},200)}catch{resolve(!1)}}))return{ok:!0,target};return{ok:!1,target,error:"no file manager available"}}async function start({agent:agent2,port,az=azure_exports,listTransferSas=listSasBlobs2,transferDoctor=azcopyDoctor}={}){let subscriptionGeneration=0,navigationGeneration=0,discoveredSubscriptions=new Map,pageKey=t=>JSON.stringify([t.account||"",t.kind,t.resource||"",t.dir||t.prefix||""]),pageRequests=new Map,pageResults=new Map,serviceViews=new Map,viewFields={blob:["s_container","s_blobPrefix","s_blobPrefixes","s_blobs","s_blobsMore","s_stats","s_filtered","s_criteria","s_insight"],file:["s_fileDir","s_files"],queue:["s_messages"],table:["s_entities"]};function saveServiceView(st){st.s_account&&viewFields[st.s_kind]&&serviceViews.set(JSON.stringify([st.s_account,st.s_kind]),{...Object.fromEntries(["s_kind","s_item",...viewFields[st.s_kind]].map(key=>[key,st[key]])),incomplete:!!st.s_loading})}async function loadStoragePage(target,ctx,direction){let{getState,setState,log}=ctx,key=pageKey(target),old=getState().s_pages?.[key],cachedPages=direction?(pageResults.get(key)||[]).slice():[],index=direction==="last"?Math.max(0,cachedPages.length-1):direction==="next"?(old?.index||0)+1:direction==="previous"?(old?.index||0)-1:0;if(direction&&(!old||index<0||direction==="next"&&!old.nextToken&&!cachedPages[index]))return{ok:!1,error:"That page is not available. Refresh the current view."};let tokens=direction?old.tokens.slice():[null];direction==="next"&&!cachedPages[index]&&(tokens[index]=old.nextToken);let generation=target.resource&&target.kind!=="sas"?++navigationGeneration:null,request=(pageRequests.get(key)||0)+1;pageRequests.set(key,request);let sasUrl=target.kind==="sas"?getState().s_sasUrl:null;setState({s_pageLoading:key,s_error:null});let result=cachedPages[index]?{ok:!0,data:cachedPages[index]}:await safe(()=>az.listStoragePage({...target,token:tokens[index],...sasUrl?{sasUrl}:{}}));if(pageRequests.get(key)!==request||sasUrl&&sasUrl!==getState().s_sasUrl||generation!=null&&(generation!==navigationGeneration||target.account!==getState().s_account||target.kind!==getState().s_kind||target.resource!==getState().s_item))return{ok:!1,error:"Resource navigation changed; older page discarded."};if(!result.ok){let message=redactEndpointError(result.error,[sasUrl||""]);return setState({s_pageLoading:null,s_pageProgress:null,...target.resource?{s_loading:!1}:{},s_error:message,s_needsAuth:result.needsAuth}),log(`Read ${target.kind} page failed: ${message}`),{ok:!1,error:message}}let data=result.data;for(let scope of pageResults.keys()){let[account,kind,resource]=JSON.parse(scope);scope!==key&&!direction&&account===target.account&&kind===target.kind&&!!resource==!!target.resource&&pageResults.delete(scope)}cachedPages[index]=data,pageResults.set(key,cachedPages);let columns;target.kind==="table"&&target.resource&&(columns=[...new Set(cachedPages.flatMap(page=>page?.entities?.columns||[]))]);let patch={s_pages:{...getState().s_pages,[key]:{index,tokens,nextToken:data.nextToken||null,cachedPageCount:cachedPages.length,cachedItemCount:cachedPages.reduce((count,page)=>count+(page?.items?.length||page?.entities?.rows.length||0),0)}},s_pageProgress:null,s_pageLoading:getState().s_pageLoading===key?null:getState().s_pageLoading,...target.resource?{s_loading:!1}:{}};if(target.kind==="sas"){let parsed=parseSasUrl(getState().s_sasUrl);patch.s_sasBlobs=parsed.canRead?data.items.map(b=>({...b,url:blobSasUrl(getState().s_sasUrl,b.name)})):data.items,patch.s_sasStats=filterAndStat(data.items,{}).stats}else if(target.resource)if(target.kind==="blob"){let criteria=getState().s_criteria,filtered=criteria?filterAndStat(data.items,criteria):null;Object.assign(patch,{s_blobs:data.items,s_blobPrefixes:data.prefixes||[],s_blobsMore:!!data.nextToken,s_stats:filtered?.stats||null,s_filtered:filtered?filtered.filtered.map(b=>b.name):[]})}else target.kind==="file"?patch.s_files=data.items:target.kind==="table"&&(patch.s_entities={...data.entities,columns});else{let field={blob:"containers",file:"shares",queue:"queues",table:"tables"}[target.kind];setTree(ctx,target.account,{[field]:data.items},{[target.kind]:!1})}return setState(patch),{ok:!0,...target.kind==="sas"?{blobs:data.items.length}:target.resource?target.kind==="blob"?{blobs:data.items.length,directories:(data.prefixes||[]).length,truncated:!!data.nextToken}:target.kind==="file"?{items:data.items.length}:{entities:data.entities.rows.length}:{count:data.items.length,...target.kind==="blob"?{containers:data.items.map(item=>item.name)}:{}},page:index+1,hasNext:!!data.nextToken}}async function loadAccounts(_p,{setState,log}){setState({s_loading:!0,s_accountsLoading:!0,s_error:null});let r=await safe(()=>az.listStorageAccounts());return r.ok?(setState({s_loading:!1,s_accountsLoading:!1,s_accounts:r.data}),log(`Loaded ${r.data.length} storage accounts.`),{ok:!0,count:r.data.length}):(setState({s_loading:!1,s_accountsLoading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Load accounts failed: ${r.error}`),{ok:!1,error:r.error})}async function loadSubscriptions(_p,{setState,log}){setState({s_subsLoading:!0,s_error:null,s_seInstalled:az.storageExplorerInstalled()});let r=await safe(()=>az.listSubscriptions());if(!r.ok)return setState({s_subsLoading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Load subscriptions failed: ${r.error}`),{ok:!1,error:r.error};let inventory=await safe(()=>az.listTenants()),subs=[...r.data.filter(s=>!discoveredSubscriptions.has(s.tenantId)),...[...discoveredSubscriptions.values()].flat()].sort((a,b)=>(a.displayName||"").localeCompare(b.displayName||"")),tenants=inventory.ok?inventory.data:[];for(let sub of subs)tenants.some(t=>t.tenantId===sub.tenantId)||tenants.push({tenantId:sub.tenantId,displayName:"Cached directory",current:!1});return setState({s_subsLoading:!1,s_subscriptions:subs,s_tenants:tenants,s_tenantInventoryError:inventory.ok?null:inventory.error}),inventory.ok||log(`Current tenant inventory failed: ${inventory.error}`),log(`Found ${tenants.length} directories and ${subs.length} cached subscriptions.`),{ok:!0,count:subs.length}}async function selectSubscriptions({subscriptionIds},{getState,setState,log}){let generation=++subscriptionGeneration,requested=Array.isArray(subscriptionIds)?subscriptionIds:[],chosen=(getState().s_subscriptions||[]).filter(s=>requested.includes(s.subscriptionId)),ids=chosen.map(s=>s.subscriptionId),clearResource={s_account:null,s_container:null,s_kind:null,s_item:null,s_tree:{},s_containers:[],s_blobs:[],s_stats:null,s_filtered:[],s_shares:[],s_queues:[],s_tables:[],s_files:[],s_messages:[],s_entities:null},st=getState(),currentAccount=(st.s_accounts||[]).find(a=>a.name===st.s_account),keepResource=currentAccount&&ids.includes(currentAccount.subscriptionId);if(setState({s_selectedSubs:ids,s_loading:!0,s_accountsLoading:!0,s_error:null,s_needsAuth:!1,s_subscriptionErrors:[],...keepResource?{}:{s_accounts:[],...clearResource}}),!chosen.length)return setState({s_loading:!1,s_accountsLoading:!1}),log("Scope cleared. No subscriptions selected."),{ok:!0,count:0};log(`Loading storage accounts for ${chosen.length} subscription(s)\u2026`);let r=await safe(()=>az.listStorageAccountsForSubs(chosen));if(generation!==subscriptionGeneration)return{ok:!1,error:"Subscription selection changed while accounts were loading. Older results were discarded."};if(!r.ok)return setState({s_loading:!1,s_accountsLoading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Load accounts failed: ${r.error}`),{ok:!1,error:r.error};let{accounts,errors}=r.data,patch={s_loading:!1,s_accountsLoading:!1,s_accounts:accounts};if(getState().s_account&&!accounts.some(a=>a.name===getState().s_account)&&Object.assign(patch,clearResource),patch.s_subscriptionErrors=errors||[],errors&&errors.length){patch.s_error=`${errors.length} subscription(s) could not be read. ${accounts.length} storage account(s) loaded successfully.`;for(let e of errors)log(`Subscription ${e.subscriptionName} (${e.subscriptionId}, tenant ${e.tenantId||"unknown"}) failed: ${e.error}`)}return setState(patch),log(`Loaded ${accounts.length} accounts from ${chosen.length} subscription(s).`),{ok:!errors.length,count:accounts.length,errors}}function setTree({getState,setState},account,patch,loadingPatch){let tree={...getState().s_tree||{}},cur=tree[account]||{containers:null,shares:null,queues:null,tables:null,loading:{}};tree[account]={...cur,...patch||{},loading:{...cur.loading,...loadingPatch||{}}},setState({s_tree:tree})}async function openAccount({account},ctx){++navigationGeneration;let{getState,setState,log}=ctx;if(!account)return{ok:!1,error:"account required"};if(saveServiceView(getState()),setState({s_error:null,s_account:account,s_container:null,s_blobs:[],s_stats:null,s_filtered:[],s_criteria:null,s_insight:null,s_kind:null,s_item:null,s_files:[],s_messages:[],s_entities:null}),getState().s_tree&&getState().s_tree[account]&&getState().s_tree[account].containers!=null)return log(`Focused account ${account}.`),{ok:!0,cached:!0};if(az.listStoragePage)return loadStoragePage({account,kind:"blob"},ctx);setTree(ctx,account,{},{blob:!0}),log(`Opening account ${account}\u2026`);let r=await safe(()=>az.listContainers(account));return r.ok?(setTree(ctx,account,{containers:r.data},{blob:!1}),log(`${account}: ${r.data.length} containers.`),{ok:!0,containers:r.data.map(c=>c.name)}):(setTree(ctx,account,{},{blob:!1}),setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`Open ${account} failed: ${r.error}`),{ok:!1,error:r.error})}async function openContainer({container,account,prefix=""},ctx){let{getState,setState,log}=ctx,generation=++navigationGeneration;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(!container)return{ok:!1,error:"container required"};if(typeof prefix!="string"||prefix&&!prefix.endsWith("/"))throw new Error("Invalid blob directory prefix.");if(setState({s_loading:!0,s_error:null,s_account:account,s_container:container,s_kind:"blob",s_item:container,s_blobPrefix:prefix,s_blobPrefixes:[],s_blobs:[],s_blobsMore:!1,s_stats:null,s_filtered:[],s_criteria:null,s_insight:null}),az.listStoragePage)return loadStoragePage({account,kind:"blob",resource:container,prefix},ctx);log(`Listing blobs in ${account}/${container}\u2026`);let r=await safe(()=>az.listBlobDirectory(account,container,{prefix}));return generation!==navigationGeneration||getState().s_account!==account||getState().s_kind!=="blob"?{ok:!1,error:"Resource navigation changed; older blob listing discarded."}:r.ok?(setState({s_loading:!1,s_blobs:r.data.blobs,s_blobPrefixes:r.data.prefixes,s_blobsMore:!!r.data.truncated}),log(`${container}/${prefix}: ${r.data.blobs.length} blobs, ${r.data.prefixes.length} directories${r.data.truncated?" (listing capped at "+r.data.limit+" items)":""}.`),{ok:!0,blobs:r.data.blobs.length,directories:r.data.prefixes.length,truncated:r.data.truncated}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`List ${container} failed: ${r.error}`),{ok:!1,error:r.error})}async function listShares2({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(az.listStoragePage)return loadStoragePage({account,kind:"file"},ctx);setTree(ctx,account,{},{file:!0});let r=await safe(()=>az.listShares(account));return r.ok?(setTree(ctx,account,{shares:r.data},{file:!1}),log(`${account}: ${r.data.length} file shares.`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{file:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List shares failed: ${r.error}`),{ok:!1,error:r.error})}async function listQueues2({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(az.listStoragePage)return loadStoragePage({account,kind:"queue"},ctx);setTree(ctx,account,{},{queue:!0});let r=await safe(()=>az.listQueues(account));return r.ok?(setTree(ctx,account,{queues:r.data},{queue:!1}),log(`${account}: ${r.data.length} queues.`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{queue:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List queues failed: ${r.error}`),{ok:!1,error:r.error})}async function listTables2({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(az.listStoragePage)return loadStoragePage({account,kind:"table"},ctx);setTree(ctx,account,{},{table:!0});let r=await safe(()=>az.listTables(account));return r.ok?(setTree(ctx,account,{tables:r.data},{table:!1}),log(`${account}: ${r.data.length} tables.`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{table:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List tables failed: ${r.error}`),{ok:!1,error:r.error})}async function listContainersTree({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"account required"};if(getState().s_tree&&getState().s_tree[account]&&getState().s_tree[account].containers!=null)return{ok:!0,cached:!0};if(az.listStoragePage)return loadStoragePage({account,kind:"blob"},ctx);setTree(ctx,account,{},{blob:!0});let r=await safe(()=>az.listContainers(account));return r.ok?(setTree(ctx,account,{containers:r.data},{blob:!1}),log(`${account}: ${r.data.length} containers (tree).`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{blob:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List containers failed: ${r.error}`),{ok:!1,error:r.error})}async function openQueue({queue,account},{getState,setState,log}){let generation=++navigationGeneration;if(account=account||getState().s_account,!account||!queue)return{ok:!1,error:"open an account first"};setState({s_loading:!0,s_error:null,s_account:account,s_kind:"queue",s_item:queue,s_messages:[]}),log(`Peeking messages in ${account} \xB7 queue ${queue}\u2026`);let r=await safe(()=>az.peekQueueMessages(account,queue));return generation!==navigationGeneration||getState().s_account!==account||getState().s_kind!=="queue"||getState().s_item!==queue?{ok:!1,error:"Resource navigation changed; older queue listing discarded."}:r.ok?(setState({s_loading:!1,s_messages:r.data}),log(`${queue}: peeked ${r.data.length} message(s).`),{ok:!0,messages:r.data.length}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Peek ${queue} failed: ${r.error}`),{ok:!1,error:r.error})}async function openTable({table,account},ctx){let{getState,setState,log}=ctx;if(++navigationGeneration,account=account||getState().s_account,!account||!table)return{ok:!1,error:"open an account first"};if(setState({s_loading:!0,s_error:null,s_account:account,s_kind:"table",s_item:table,s_entities:null}),az.listStoragePage)return loadStoragePage({account,kind:"table",resource:table},ctx);log(`Querying entities in ${account} \xB7 table ${table}\u2026`);let r=await safe(()=>az.queryTableEntities(account,table));return r.ok?(setState({s_loading:!1,s_entities:r.data}),log(`${table}: ${r.data.rows.length} entities.`),{ok:!0,entities:r.data.rows.length}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Query ${table} failed: ${r.error}`),{ok:!1,error:r.error})}async function openShare({share,account,dir=""},ctx){let{getState,setState,log}=ctx,generation=++navigationGeneration;if(account=account||getState().s_account,!account||!share)return{ok:!1,error:"open an account first"};if(dir=shareDirectory(dir),setState({s_loading:!0,s_error:null,s_account:account,s_kind:"file",s_item:share,s_fileDir:dir,s_files:[]}),az.listStoragePage)return loadStoragePage({account,kind:"file",resource:share,dir},ctx);log(`Listing files in ${account} \xB7 share ${share}\u2026`);let r=await safe(()=>az.listShareItems(account,share,dir));return generation!==navigationGeneration||getState().s_account!==account||getState().s_kind!=="file"?{ok:!1,error:"Resource navigation changed; older file listing discarded."}:r.ok?(setState({s_loading:!1,s_files:r.data}),log(`${share}: ${r.data.length} item(s).`),{ok:!0,items:r.data.length}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`List ${share} failed: ${r.error}`),{ok:!1,error:r.error})}async function refreshCategory(account,kind,ctx){if(az.listStoragePage)return loadStoragePage({account,kind},ctx);kind==="blob"?setTree(ctx,account,{containers:await az.listContainers(account)}):kind==="queue"?setTree(ctx,account,{queues:await az.listQueues(account)}):kind==="table"?setTree(ctx,account,{tables:await az.listTables(account)}):kind==="file"&&setTree(ctx,account,{shares:await az.listShares(account)})}async function afterMutation(read,ctx){let result=await safe(read);if(result.ok&&result.data?.ok!==!1)return{ok:!0};let cause=result.ok?result.data.error:result.error,error="Operation completed, but the listing could not be refreshed. Refresh it before retrying any operation.";return ctx.setState({s_error:error}),ctx.log(`Post-operation refresh failed: ${cause}`),{ok:!1,error,operationCompleted:!0}}function mutate(fn,{refresh,reopen}={}){return async(payload,ctx)=>{let account=payload&&payload.account||ctx.getState().s_account;if(!account)return{ok:!1,error:"open an account first"};let r=await safe(()=>fn(account,payload,ctx));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),ctx.log(`Operation failed: ${r.error}`),{ok:!1,error:r.error};if(refresh){let result=await afterMutation(()=>refreshCategory(account,refresh,ctx),ctx);if(!result.ok)return result}if(reopen){let result=await afterMutation(()=>reopen(account,payload,ctx),ctx);if(result?.ok===!1)return result}return r.data??{ok:!0}}}let reopenQueue=(account,{queue},ctx)=>openQueue({account,queue},ctx),reopenTable=(account,{table},ctx)=>openTable({account,table},ctx),reopenShare=(account,{share,dir=""},ctx)=>{let st=ctx.getState();return st.s_account!==account||st.s_kind!=="file"||st.s_item!==share||(st.s_fileDir||"")!==dir?{ok:!0}:openShare({share,account,dir},ctx)};function shareDirectory(dir){if(typeof dir!="string"||dir.split("/").some(p=>p==="."||p==="..")||dir.includes("\\"))throw new Error("Invalid file-share directory path.");return dir.split("/").filter(Boolean).join("/")}async function runBatchDownload(names,{getState,setState,log}){let st=getState(),account=st.s_account,container=st.s_container;if(!account||!container)return{ok:!1,error:"open a container first"};let list=(names||[]).filter(Boolean);if(!list.length)return{ok:!1,error:"no blobs given"};let dir=path2.join(DL_ROOT,container),done=0,okc=0,failed=[];setState({s_batch:{op:"download",total:list.length,done,ok:okc,failed:[],running:!0,dir}}),log(`Downloading ${list.length} blob(s) from ${container}\u2026`);for(let name of list){let r=await safe(()=>az.downloadBlob(account,container,name));if(done++,r.ok)try{await saveDownload(container,name,r.data),okc++}catch(e){failed.push({name,error:"could not save to disk: "+(e&&e.message?e.message:String(e))})}else failed.push({name,error:r.error});setState({s_batch:{op:"download",total:list.length,done,ok:okc,failed:[...failed],running:donei&&i.name&&i.contentBase64);if(!list.length)return{ok:!1,error:"no items given"};let done=0,okc=0,failed=[];setState({s_batch:{op:"upload",total:list.length,done,ok:okc,failed:[],running:!0}}),log(`Uploading ${list.length} file(s) to ${container}\u2026`);for(let it of list){let buf=Buffer.from(it.contentBase64,"base64"),r=await safe(()=>az.uploadBlob(account,container,(st.s_blobPrefix||"")+it.name,buf,it.contentType));done++,r.ok?okc++:failed.push({name:it.name,error:r.error}),setState({s_batch:{op:"upload",total:list.length,done,ok:okc,failed:[...failed],running:doneopenContainer({account,container,prefix:st.s_blobPrefix||""},ctx),ctx):{ok:!0};return log(`Upload complete: ${okc} ok, ${failed.length} failed.`),{ok:refreshed.ok&&!failed.length,uploaded:okc,failed,...refreshed.ok?{}:{error:"Upload finished, but listing refresh failed.",operationCompleted:okc>0}}}async function runBatchCleanup(action,ctx){let{getState,setState,log}=ctx,st=getState(),account=st.s_account,container=st.s_container;if(!account||!container)return{ok:!1,error:"open a container first"};let list=(st.s_filtered||[]).filter(Boolean);if(!list.length)return{ok:!1,error:"filter some blobs first, nothing matched"};let op=action==="delete"?"delete":"archive",done=0,okc=0,failed=[];setState({s_batch:{op,total:list.length,done,ok:okc,failed:[],running:!0}}),log(`${op==="delete"?"Deleting":"Archiving"} ${list.length} matched blob(s) in ${container}\u2026`);for(let name of list){let r=op==="delete"?await safe(()=>az.deleteBlob(account,container,name)):await safe(()=>az.setBlobTier(account,container,name,"Archive"));done++,r.ok?okc++:failed.push({name,error:r.error}),setState({s_batch:{op,total:list.length,done,ok:okc,failed:[...failed],running:doneopenContainer({account,container,prefix:st.s_blobPrefix||""},ctx),ctx):{ok:!0};return log(`${op==="delete"?"Delete":"Archive"} complete: ${okc} ok, ${failed.length} failed.`),{ok:refreshed.ok&&!failed.length,op,cleaned:okc,failed,...refreshed.ok?{}:{error:"Cleanup finished, but listing refresh failed.",operationCompleted:okc>0}}}let routes={"/load-accounts":loadAccounts,"/load-subscriptions":loadSubscriptions,"/tenant-subscriptions":async({tenantId},ctx)=>{if(!(ctx.getState().s_tenants||[]).some(t=>t.tenantId===tenantId))throw new Error("Tenant is not in the discovered inventory.");ctx.setState({s_tenantLoading:tenantId});try{let subscriptions=await az.listTenantSubscriptions(tenantId);return discoveredSubscriptions.set(tenantId,subscriptions),ctx.setState({s_subscriptions:[...(ctx.getState().s_subscriptions||[]).filter(s=>s.tenantId!==tenantId),...subscriptions],s_tenantErrors:{...ctx.getState().s_tenantErrors||{},[tenantId]:null}}),ctx.log(`Refreshed ${subscriptions.length} subscriptions for tenant ${tenantId}.`),{ok:!0}}catch(error){let failure=az.discoveryFailure(error);return ctx.setState({s_tenantErrors:{...ctx.getState().s_tenantErrors||{},[tenantId]:failure}}),ctx.log(`Tenant subscription discovery failed: ${failure.error}`),{ok:!1,error:failure.message}}finally{ctx.setState({s_tenantLoading:null})}},"/select-subscriptions":selectSubscriptions,"/account":openAccount,"/activate-service":async({account,kind},ctx)=>{let st=ctx.getState();if(account!==st.s_account||!["blob","file","queue","table"].includes(kind))throw new Error("Service selection changed.");saveServiceView(st),++navigationGeneration;let saved=serviceViews.get(JSON.stringify([account,kind])),{incomplete,...cached}=saved||{};return ctx.setState({s_entities:null,s_blobs:[],s_blobPrefixes:[],s_files:[],s_messages:[],s_stats:null,s_filtered:[],s_criteria:null,s_container:null,...saved?cached:{s_kind:kind,s_item:null},s_loading:!1,s_error:null,s_pageLoading:null,s_pageProgress:null}),incomplete&&cached.s_item?kind==="blob"?openContainer({account,container:cached.s_item,prefix:cached.s_blobPrefix||""},ctx):kind==="file"?openShare({account,share:cached.s_item,dir:cached.s_fileDir||""},ctx):kind==="table"?openTable({account,table:cached.s_item},ctx):openQueue({account,queue:cached.s_item},ctx):{ok:!0,cached:!!saved}},"/container":openContainer,"/list-shares":listShares2,"/list-queues":listQueues2,"/list-tables":listTables2,"/storage-page":async({account,kind,resource,prefix="",dir="",direction},ctx)=>{let st=ctx.getState();if(!["first","last","next","previous","refresh","cancel"].includes(direction))throw new Error("Invalid page direction.");if(kind==="sas"){if(!st.s_sasInfo||account!==st.s_sasInfo.account||resource!==st.s_sasInfo.container)throw new Error("SAS connection changed; refresh its page.")}else if(account!==st.s_account||resource&&(kind!==st.s_kind||resource!==st.s_item||kind==="blob"&&prefix!==(st.s_blobPrefix||"")||kind==="file"&&dir!==(st.s_fileDir||"")))throw new Error("Resource navigation changed; page request discarded.");if(direction==="cancel"){let key=pageKey({account,kind,resource,prefix,dir});return pageRequests.set(key,(pageRequests.get(key)||0)+1),ctx.setState({s_pageLoading:null,s_pageProgress:null,...resource&&kind!=="sas"?{s_loading:!1}:{}}),{ok:!0,cancelled:!0}}return loadStoragePage({account,kind,resource,prefix,dir},ctx,direction==="refresh"?void 0:direction)},"/tree-containers":listContainersTree,"/open-queue":openQueue,"/open-table":openTable,"/open-share":openShare,"/download-many":({blobs},ctx)=>runBatchDownload(blobs,ctx),"/open-folder":async({dir},_ctx)=>await openFolder(dir),"/dismiss-batch":(_p,{setState})=>(setState({s_batch:null}),{ok:!0}),"/get-prefs":async()=>({ok:!0,prefs:await readPrefs()}),"/save-prefs":async(input={})=>({ok:!0,prefs:await writePrefs(input)}),"/upload-many":({items,account,container,prefix},ctx)=>{let st=ctx.getState();if(account!==void 0&&(account!==st.s_account||container!==st.s_container||prefix!==(st.s_blobPrefix||""))){let error="The upload destination changed. Choose files again in the intended directory.";return ctx.setState({s_error:error}),ctx.log(error),{ok:!1,error}}return runBatchUpload(items,ctx)},"/container-create":mutate((a,{container})=>az.createContainer(a,container),{refresh:"blob"}),"/container-delete":async({container,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteContainer(a,container));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"blob",ctx),ctx);return ctx.getState().s_kind==="blob"&&ctx.getState().s_item===container&&ctx.setState({s_kind:null,s_item:null,s_container:null,s_blobs:[]}),ctx.log(`Deleted container ${container}.`),refreshed},"/blob-delete":async({container,name,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteBlob(a,container,name));return r.ok?(await afterMutation(()=>openContainer({account:a,container,prefix:ctx.getState().s_blobPrefix||""},ctx),ctx)).ok?(ctx.log(`Deleted blob ${name}.`),{ok:!0}):{ok:!1,operationCompleted:!0,error:"Blob deleted, but listing refresh failed."}:(ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error})},"/generate-sas":async({container,name,account,minutes,permissions},{getState,log})=>{let a=account||getState().s_account,c=container||getState().s_container;if(!c||!name)return{ok:!1,error:"no blob selected"};let r=await safe(()=>az.generateBlobSas(a,c,name,{minutes:Number(minutes)||60,permissions:permissions||"r"}));return r.ok?(log(`Generated ${r.data.mode} SAS for ${name} (${r.data.permissions}, expires ${r.data.expiresOn}).`),{ok:!0,name,...r.data}):{ok:!1,error:r.error,needsAuth:r.needsAuth}},"/generate-container-sas":async({container,account,minutes,permissions},{getState,log})=>{let a=account||getState().s_account,c=container||getState().s_container;if(!c)return{ok:!1,error:"no container selected"};let r=await safe(()=>az.generateContainerSas(a,c,{minutes:Number(minutes)||1440,permissions:permissions||"rl"}));return r.ok?(log(`Generated ${r.data.mode} container SAS for ${c} (${r.data.permissions}, expires ${r.data.expiresOn}).`),{ok:!0,container:c,...r.data}):{ok:!1,error:r.error,needsAuth:r.needsAuth}},"/queue-create":mutate((a,{queue})=>az.createQueue(a,queue),{refresh:"queue"}),"/queue-delete":async({queue,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteQueue(a,queue));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"queue",ctx),ctx);return ctx.getState().s_kind==="queue"&&ctx.getState().s_item===queue&&ctx.setState({s_kind:null,s_item:null,s_messages:[]}),ctx.log(`Deleted queue ${queue}.`),refreshed},"/queue-send":mutate((a,{queue,text})=>az.sendQueueMessage(a,queue,text),{reopen:reopenQueue}),"/queue-clear":mutate((a,{queue})=>az.clearQueueMessages(a,queue),{reopen:reopenQueue}),"/queue-msg-update":mutate((a,{queue,messageId,text})=>az.updateQueueMessage(a,queue,messageId,text),{reopen:reopenQueue}),"/queue-msg-delete":mutate((a,{queue,messageId})=>az.deleteQueueMessage(a,queue,messageId),{reopen:reopenQueue}),"/table-create":mutate((a,{table})=>az.createTable(a,table),{refresh:"table"}),"/table-delete":async({table,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteTable(a,table));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"table",ctx),ctx);return ctx.getState().s_kind==="table"&&ctx.getState().s_item===table&&ctx.setState({s_kind:null,s_item:null,s_entities:null}),ctx.log(`Deleted table ${table}.`),refreshed},"/entity-upsert":mutate((a,{table,entity})=>az.upsertEntity(a,table,entity),{reopen:reopenTable}),"/entity-delete":mutate((a,{table,partitionKey,rowKey})=>az.deleteEntity(a,table,partitionKey,rowKey),{reopen:reopenTable}),"/share-create":mutate((a,{share})=>az.createShare(a,share),{refresh:"file"}),"/share-delete":async({share,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteShare(a,share));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"file",ctx),ctx);return ctx.getState().s_kind==="file"&&ctx.getState().s_item===share&&ctx.setState({s_kind:null,s_item:null,s_files:[]}),ctx.log(`Deleted file share ${share}.`),refreshed},"/share-mkdir":mutate((a,{share,dir="",name})=>az.createShareDirectory(a,share,shareDirectory(name?[dir,name].filter(Boolean).join("/"):dir)),{reopen:reopenShare}),"/share-delete-item":mutate((a,{share,name,kind,dir=""})=>az.deleteShareItem(a,share,name,kind,shareDirectory(dir)),{reopen:reopenShare}),"/share-upload":mutate((a,{share,name,contentBase64,dir=""})=>az.uploadShareFile(a,share,name,Buffer.from(contentBase64||"","base64"),shareDirectory(dir)),{reopen:reopenShare}),"/share-download":async({account,share,name,dir:directory=""},ctx)=>{let a=account||ctx.getState().s_account;directory=shareDirectory(directory);let r=await safe(()=>az.downloadShareFile(a,share,name,directory));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let dir=path2.join(DL_ROOT,share);try{await saveDownload(share,[directory,name].filter(Boolean).join("/"),r.data)}catch(e){let msg="could not save to disk: "+(e&&e.message?e.message:String(e));return ctx.setState({s_error:msg}),{ok:!1,error:msg}}return ctx.setState({s_batch:{op:"download",total:1,done:1,ok:1,failed:[],running:!1,dir}}),ctx.log(`Downloaded ${name} from share ${share} to ${dir}.`),{ok:!0,dir}},"/delete-selected":async({account,kind,resource,dir="",prefix="",names},ctx)=>{let st=ctx.getState();dir=shareDirectory(dir);let fail=error=>(ctx.setState({s_error:error}),ctx.log(`Delete selected failed: ${error}`),{ok:!1,error});if(!["blob","file"].includes(kind)||st.s_account!==account||st.s_kind!==kind||st.s_item!==resource||kind==="file"&&(st.s_fileDir||"")!==dir||kind==="blob"&&(st.s_blobPrefix||"")!==prefix)return fail("The open resource changed. Select items again before deleting.");let items=kind==="blob"?st.s_blobs||[]:st.s_files||[];if(!Array.isArray(names)||!names.length||names.some(n=>!items.some(i=>i.name===n)))return fail("Select items from the current listing before deleting.");let failures=[],deleted=0;for(let name of new Set(names)){let item=items.find(i=>i.name===name),result=await safe(()=>kind==="blob"?az.deleteBlob(account,resource,name):az.deleteShareItem(account,resource,name,item.kind,dir));result.ok?deleted++:(failures.push({name,error:result.error}),ctx.log(`Delete ${name} failed: ${result.error}`))}let refreshed=ctx.getState().s_account===account&&ctx.getState().s_kind===kind&&ctx.getState().s_item===resource&&(kind!=="file"||(ctx.getState().s_fileDir||"")===dir)&&(kind!=="blob"||(ctx.getState().s_blobPrefix||"")===prefix)?await(kind==="blob"?openContainer({account,container:resource,prefix},ctx):openShare({account,share:resource,dir},ctx)):{ok:!0};if(ctx.log(`Delete selected: ${deleted} deleted, ${failures.length} failed.`),failures.length||!refreshed.ok){let error=`${deleted} item(s) deleted; ${failures.length} failed.${refreshed.ok?"":" Listing refresh also failed."} Open diagnostics for details.`;return ctx.setState({s_error:error}),{ok:!1,deleted,failures,error,operationCompleted:deleted>0}}return{ok:!0,deleted}},"/diagnose":(_p,_ctx)=>({ok:!1,error:"diagnosis removed"}),"/filter":async(criteria,{getState,setState,log})=>{let st=getState();if(!(st.s_kind==="blob"&&st.s_container))return{ok:!1,error:"Open a container first to filter its blobs."};let blobs=st.s_blobs||[],{filtered,stats}=filterAndStat(blobs,criteria||{});return setState({s_criteria:criteria||{},s_filtered:filtered.map(b=>b.name),s_stats:stats}),log(`Filter matched ${filtered.length} of ${blobs.length} blobs.`),{ok:!0,matched:filtered.length,stats}},"/download-filtered":async(_p,ctx)=>{let names=ctx.getState().s_filtered||[];return runBatchDownload(names,ctx)},"/cleanup-filtered":({action},ctx)=>runBatchCleanup(action,ctx),"/transfer-plan":async({dest,resolution,useFiltered,source:srcContainer,sourceSas},{getState,setState,log})=>{let st=getState(),account=st.s_account,container=String(srcContainer||st.s_container||"").trim(),sourceSasUrl=String(sourceSas||"").trim(),fail=(message,severity="error")=>{let msg=redactEndpointError(message,[sourceSasUrl,String(dest||"")]);return setState({s_transferErr:msg,s_transferSeverity:severity,s_transferProgress:null,s_transfer:null}),log(`Transfer plan ${severity==="warning"?"blocked":"failed"}: ${msg}`),{ok:!1,error:msg,severity}},parsedSource;if(sourceSasUrl){if(parsedSource=parseSasUrl(sourceSasUrl),!parsedSource.ok)return fail(parsedSource.error);if(!parsedSource.container||parsedSource.blobPath||parsedSource.resource!=="c")return fail("Source must be a container SAS URL, not an account or blob SAS.");if(parsedSource.expired)return fail("Source SAS has expired. Provide a current container SAS.");if(!parsedSource.canRead||!parsedSource.canList)return fail("Source SAS needs Read and List permissions to plan and copy the container.");if(useFiltered)return fail("Filtered transfer applies only to the open account/container source, not a SAS source.")}else{if(!account)return fail("Open a storage account first. Pick one in the Explorer.");if(!container)return fail("Choose a source container.")}let isOpen=!sourceSasUrl&&st.s_kind==="blob"&&st.s_item===container;if(useFiltered&&(!isOpen||!st.s_criteria))return fail("Apply filter criteria to the open source container before planning a filtered transfer.");let progress=side=>count=>setState({s_transferProgress:{side,count}});setState({s_transfer:null,s_transferErr:null,s_transferSeverity:null,s_transferProgress:{side:"source",count:0}});let criteria=useFiltered?{...st.s_criteria,now:st.s_criteria.now||new Date().toISOString()}:null,options={complete:!0,onProgress:progress("source"),...criteria?{prefix:st.s_blobPrefix||"",requireTags:!!(criteria.tags&&Object.keys(criteria.tags).length||criteria.tagKeys?.length)}:{}},allBlobs;if(parsedSource){let rs=await safe(()=>listTransferSas(sourceSasUrl,options));if(!rs.ok)return fail(`Couldn't read the source SAS container: ${rs.error}`);allBlobs=rs.data.blobs}else{let rs=await safe(()=>az.listBlobs(account,container,options));if(!rs.ok)return fail(`Couldn't read source container '${container}': ${rs.error}`);allBlobs=rs.data}if(allBlobs.truncated)return fail("Source inventory is incomplete. No transfer plan was generated.");let prefix=st.s_blobPrefix||"",source=criteria?applyBlobFilter(allBlobs.filter(b=>b.name.startsWith(prefix)&&!b.name.slice(prefix.length).includes("/")),criteria):allBlobs,names=criteria?new Set(source.map(b=>b.name)):null,destStr=String(dest||"").trim();if(!destStr)return fail("Enter a destination: 'account/container' (e.g. myaccount/backups) or a container SAS URL.");if(!/[?&]sig=/.test(destStr)&&destStr.replace(/^https?:\/\//,"").split("/").filter(Boolean).length<2)return fail(`Destination must be 'account/container', not just '${destStr}'. Example: ${account}/${destStr||"backups"}, or paste a container SAS URL.`);if(`${account}/${container}`===destStr.replace(/^https?:\/\//,"").replace(".blob.core.windows.net",""))return fail("Source and destination are the same container. Pick a different destination.");let res=["overwrite","skip","if-newer"].includes(resolution)?resolution:"overwrite",destBlobs=[],destUrl="",destSas=!1;setState({s_transferProgress:{side:"destination",count:0}});let r=await safe(async()=>{if(/[?&]sig=/.test(destStr)){let p=parseSasUrl(destStr);if(!p.ok)throw new Error(p.error);if(!p.container||p.blobPath||p.resource!=="c")throw new Error("Destination must be a container SAS URL, not an account or blob SAS.");if(p.expired)throw new Error("Destination SAS has expired.");if(!p.canList)throw new Error("Destination SAS needs List permission to calculate conflicts.");if(!/w/i.test(p.permissions))throw new Error("Destination SAS needs Write permission for the planned copy.");return destUrl=p.containerUrl,destSas=!0,await listTransferSas(destStr,{complete:!0,onProgress:progress("destination")}).then(x=>x.blobs)}let m=destStr.replace(/^https?:\/\//,"").split("/"),dAcct=m[0].split(".")[0],dCont=m[1];if(!dAcct||!dCont)throw new Error("Destination must be 'account/container' or a container SAS URL.");return destUrl=`https://${dAcct}.blob.core.windows.net/${dCont}`,await az.listBlobs(dAcct,dCont,{complete:!0,onProgress:progress("destination")})});if(!r.ok)return fail(`Couldn't read the destination: ${r.error}`);if(destBlobs=r.data,destBlobs.truncated)return fail("Destination inventory is incomplete. No transfer plan was generated.");let sourceUrl=parsedSource?parsedSource.containerUrl:`https://${account}.blob.core.windows.net/${container}`;if(new URL(sourceUrl).origin+new URL(sourceUrl).pathname===new URL(destUrl).origin+new URL(destUrl).pathname)return fail("Source and destination are the same container. Pick a different destination.");if(res==="if-newer"){let byName=new Map(destBlobs.map(b=>[b.name,b]));if(source.some(b=>byName.has(b.name)&&(!Number.isFinite(Date.parse(b.lastModified))||!Number.isFinite(Date.parse(byName.get(b.name).lastModified)))))return fail("Last modified timestamps are missing for conflicting blobs. Choose Overwrite or Skip existing instead of If source newer.")}let plan=planTransfer(source,destBlobs,res),allSource=!names,{command,needsLogin,empty}=azcopyCommand({sourceUrl,destUrl,resolution:res,includeNames:names?plan.toTransfer:null,allSource});if(empty)return fail("Nothing to transfer \u2014 every selected blob already exists at the destination under the current conflict rule.");let azcopy=await transferDoctor(),summary={...plan,toTransfer:plan.toTransfer.slice(0,100),toTransferTruncated:plan.toTransfer.length>100};return setState({s_transferErr:null,s_transferSeverity:null,s_transferProgress:null,s_transfer:{plan:summary,command,needsLogin,azcopy,sourceLabel:parsedSource?`${parsedSource.account}/${parsedSource.container} (SAS)`:`${account}/${container}`,destLabel:destSas?`${plan.destCount} blobs (SAS)`:destStr,source:allSource?"all blobs":`${source.length} filtered blobs`}}),log(`Transfer plan: ${plan.transferCount} to transfer, ${plan.conflictCount} conflicts (${res}).${azcopy.installed?` AzCopy ${azcopy.version||"detected"}.`:" AzCopy not found \u2014 install it to run the command."}`),{ok:!0,plan:summary,...!parsedSource&&!destSas?{command}:{},needsLogin,azcopy}},"/azcopy-doctor":async(_p,_ctx)=>({ok:!0,azcopy:await azcopyDoctor({refresh:!0})}),"/sas-connect":async({url},ctx)=>{let{setState,log}=ctx;setState({s_loading:!0,s_error:null});let parsed=parseSasUrl(url);if(!parsed.ok)return setState({s_loading:!1,s_error:parsed.error}),{ok:!1,error:parsed.error};if(az.listStoragePage){if(parsed.expired||!parsed.canList||!parsed.container)return setState({s_loading:!1,s_error:"Provide an unexpired container SAS with List permission."}),{ok:!1,error:"Provide an unexpired container SAS with List permission."};let{containerUrl,...info}=parsed;return setState({s_sasUrl:url,s_sasInfo:info,s_sasBlobs:[]}),loadStoragePage({account:parsed.account,kind:"sas",resource:parsed.container},ctx)}log(`Connecting to SAS: ${describeSas(parsed)}`);let r=await safe(()=>listSasBlobs2(url));if(!r.ok)return setState({s_loading:!1,s_error:r.error}),log(`SAS connect failed: ${r.error}`),{ok:!1,error:r.error};let{stats}=filterAndStat(r.data.blobs,{}),withUrls=parsed.canRead?r.data.blobs.map(b=>({...b,url:blobSasUrl(url,b.name)})):r.data.blobs;return setState({s_loading:!1,s_sasUrl:url,s_sasInfo:{account:r.data.account,container:r.data.container,permissions:r.data.permissions,expiresOn:r.data.expiresOn,canRead:parsed.canRead,canList:parsed.canList,canWrite:parsed.canWrite,canDelete:parsed.canDelete,expired:parsed.expired,resource:parsed.resource},s_sasBlobs:withUrls,s_sasStats:stats}),log(`SAS connected: ${r.data.blobs.length} blobs in ${r.data.container}.`),{ok:!0,blobs:r.data.blobs.length}},"/sas-download":async({name},{getState,setState,log})=>{let url=getState().s_sasUrl;if(!url)return{ok:!1,error:"connect a SAS first"};let r=await safe(()=>downloadSasBlob2(url,name));if(!r.ok)return setState({s_error:r.error}),{ok:!1,error:r.error};let container=getState().s_sasInfo?.container||"sas",savedPath;try{savedPath=await saveDownload(container,name,Buffer.from(r.data.contentBase64,"base64"))}catch(e){let msg="could not save to disk: "+(e&&e.message?e.message:String(e));return setState({s_error:msg}),log(`SAS download failed: ${msg}`),{ok:!1,name,error:msg}}return log(`Downloaded '${name}' (${r.data.size} bytes) from SAS.`),{ok:!0,name,size:r.data.size,path:savedPath,dir:path2.dirname(savedPath)}},"/read-state":(_p,{getState})=>{let st=getState(),blobPage=st.s_pages?.[pageKey({account:st.s_account,kind:"blob",resource:st.s_container,prefix:st.s_blobPrefix||""})];return{ok:!0,state:{account:st.s_account,container:st.s_container,subscriptions:(st.s_subscriptions||[]).length,selectedSubs:st.s_selectedSubs||[],selectedSubscriptions:(st.s_subscriptions||[]).filter(s=>(st.s_selectedSubs||[]).includes(s.subscriptionId)),tenants:[...new Set((st.s_subscriptions||[]).map(s=>s.tenantId))],tenantInventory:st.s_tenants||[],tenantInventoryError:st.s_tenantInventoryError||null,loading:!!st.s_loading,error:st.s_error||null,subscriptionErrors:st.s_subscriptionErrors||[],accounts:(st.s_accounts||[]).map(a=>a.name),tree:st.s_tree||{},containers:Object.values(st.s_tree||{}).flatMap(t=>(t.containers||[]).map(c=>c.name)),blobs:st.s_blobs||[],blobsTruncated:blobPage?blobPage.index>0||!!blobPage.nextToken:!!st.s_blobs?.truncated,stats:st.s_stats||null,criteria:st.s_kind==="blob"&&st.s_criteria||null,filtered:st.s_filtered||[],batch:st.s_batch||null,listingPages:Object.entries(st.s_pages||{}).map(([scope,page])=>({scope:JSON.parse(scope),page:page.index+1,hasNext:!!page.nextToken})),sasInfo:st.s_sasInfo||null,sasBlobs:(st.s_sasBlobs||[]).map(b=>({name:b.name,size:b.size,lastModified:b.lastModified||null,contentType:b.contentType||null})),transfer:st.s_transfer?(()=>{let{command,sourceUrl,destUrl,...rest}=st.s_transfer;return rest})():null,insight:st.s_insight||null,preview:st.s_preview?{name:st.s_preview.name,isText:st.s_preview.isText,size:st.s_preview.size,truncated:st.s_preview.truncated,lang:st.s_preview.lang,format:st.s_preview.format||null,category:st.s_preview.category||null,magicHex:st.s_preview.magicHex||null,glb:st.s_preview.glb||null,isImage:!!st.s_preview.image,explaining:!!st.s_preview.explaining,explained:!!st.s_preview.explanation,text:st.s_preview.text||null}:null,s_queues:(st.s_queues||[]).map(q=>({name:q.name})),s_tables:(st.s_tables||[]).map(t=>({name:t.name})),s_shares:(st.s_shares||[]).map(x=>({name:x.name})),s_messages:(st.s_messages||[]).map(m=>({messageId:m.messageId||null,messageText:m.messageText})),s_entities:st.s_entities||null,s_files:(st.s_files||[]).map(f=>({name:f.name,kind:f.kind,size:f.size??null,lastModified:f.lastModified||null})),s_kind:st.s_kind||null,s_item:st.s_item||null,s_fileDir:st.s_fileDir||"",s_blobPrefix:st.s_blobPrefix||"",s_blobPrefixes:st.s_blobPrefixes||[]}}},"/canvas-answer":({answer,visual,target,suggestedFilter},{getState,setState})=>{let payload={pending:!1,answer:String(answer||""),visual:visual||null},st=getState(),t=target==="insights"||target==="preview"||target==="ask"?target:null;if(st.s_preview&&st.s_preview.explaining?t="preview":st.s_insight&&st.s_insight.pending?t="insights":t||(t="ask"),t==="insights")setState({s_insight:{...payload,suggestedFilter:suggestedFilter||null}});else if(t==="preview"){let p=st.s_preview||{};setState({s_preview:{...p,explaining:!1,explanation:payload}})}else setState({s_ask:payload});return{ok:!0}},"/insights":async(_p,{getState,setState,log})=>{let st=getState();return st.s_stats?(setState({s_insight:{pending:!0,answer:null,visual:null,suggestedFilter:null}}),log("Asked Copilot to explain the statistics."),await agent2?.requestInsight?.({account:st.s_account,container:st.s_container}),{ok:!0}):{ok:!1,error:"Filter or open a container first so there are statistics to explain."}},"/preview-blob":async({name},{getState,setState,log})=>{let st=getState(),account=st.s_account,container=st.s_container;if(!account||!container||!name)return{ok:!1,error:"Open a container and pick a blob to preview."};setState({s_preview:{name,pending:!0}});let MAX=32*1024,r=await safe(()=>az.downloadBlob(account,container,name,{maxBytes:MAX}));if(!r.ok)return setState({s_preview:{name,pending:!1,error:r.error}}),log(`Preview failed: ${r.error}`),{ok:!1,error:r.error};let slice=r.data.subarray(0,MAX),{isText,text}=sniffText(slice),bin=isText?null:sniffBinaryFormat(slice,name),mime=isText?null:imageMime(name),sz=await safe(()=>az.blobSize(account,container,name)),total=sz.ok?sz.data:slice.length,RENDER_MAX=25*1024*1024,GLB_MAX=50*1024*1024,image=mime&&total<=RENDER_MAX?{mime}:null,glb=null;if(bin&&/GLB/.test(bin.format)&&total<=GLB_MAX){let full=await safe(()=>az.downloadBlob(account,container,name));full.ok&&(glb=parseGlb(full.data))}return setState({s_preview:{name,pending:!1,size:total,truncated:total>MAX,isText,lang:previewLang(name),text:isText?text:null,format:bin?bin.format:null,category:bin?bin.category:null,magicHex:bin?bin.magicHex:null,image,glb,explanation:null}}),log(`Previewed ${name}: ${isText?"text":bin.format}, ${total} bytes.`),{ok:!0,isText,bytes:total}},"/explain-blob":async(_p,{getState,setState})=>{let p=getState().s_preview;return!p||p.pending||p.error?{ok:!1,error:"Preview a blob first."}:!p.isText&&!p.format?{ok:!1,error:"Preview a blob first."}:(setState({s_preview:{...p,explaining:!0,explanation:null}}),await agent2?.requestPreviewExplain?.({name:p.name,isText:!!p.isText,format:p.format||null,category:p.category||null,size:p.size,glb:p.glb||null}),{ok:!0})},"/dismiss-preview":(_p,{setState})=>(setState({s_preview:null}),{ok:!0}),"/dismiss-insight":(_p,{setState})=>(setState({s_insight:null}),{ok:!0}),"/open-storage-explorer":async(_p,{getState,log})=>{let st=getState(),account=st.s_account||null,kind=st.s_kind||null,item=st.s_item||null,where=item?`${account}/${item}`:account||"the app";log(`Opening Azure Storage Explorer \u2192 ${where}\u2026`);let r=await az.openInStorageExplorer({account,kind,item});return r.ok?(log(r.deepLink?`Opened Storage Explorer at ${where}.`:"Opened Storage Explorer."),{ok:!0,deepLink:r.deepLink}):r.notInstalled?(log("Azure Storage Explorer isn't installed. Opening the download page so you can get the free desktop app."),{ok:!1,notInstalled:!0,install:r.install}):(log(`Storage Explorer: ${r.error}`),{ok:!1,error:r.error})},"/dismiss-error":(_p,{setState})=>(setState({s_error:null,s_needsAuth:!1}),{ok:!0}),"/sign-in":async({tenantId}={},ctx)=>{let{setState,log}=ctx;if(tenantId&&![...ctx.getState().s_subscriptions||[],...ctx.getState().s_tenants||[]].some(s=>s.tenantId===tenantId))throw new Error("The requested tenant is not in the discovered Azure inventory.");setState({s_signingIn:!0,s_error:null}),log("Opening az login in your browser\u2026");let r=await az.signIn({tenantId});return r.ok?(setState({s_signingIn:!1,s_needsAuth:!1,s_error:null}),log("Signed in successfully. Loading subscriptions\u2026"),await loadSubscriptions({},ctx),(ctx.getState().s_selectedSubs||[]).length&&await selectSubscriptions({subscriptionIds:ctx.getState().s_selectedSubs},ctx),{ok:!0}):(setState({s_signingIn:!1,s_error:r.error,s_needsAuth:!0}),log(`Sign-in failed: ${r.error}`),{ok:!1,error:r.error})}},initialState={s_subscriptions:[],s_selectedSubs:[],s_subsLoading:!1,s_accounts:[],s_tree:{},s_containers:[],s_blobs:[],s_filtered:[],s_shares:[],s_queues:[],s_tables:[],s_files:[],s_messages:[],s_entities:null,s_kind:null,s_item:null,s_svcLoading:null,s_account:null,s_container:null,s_stats:null,s_batch:null,s_sasInfo:null,s_sasBlobs:[],s_ask:null,s_transfer:null,s_insight:null,s_preview:null,s_transferErr:null,s_loading:!1,s_accountsLoading:!1,s_error:null,activityLog:[],s_signingIn:!1,s_seInstalled:null};async function mediaHandler(pathname,query,{getState}){if(pathname!=="/blob-media")return null;let name=query.get("name"),st=getState(),p=st.s_preview;if(!p||p.name!==name||!p.image)return null;let account=st.s_account,container=st.s_container;if(!account||!container)return null;let r=await safe(()=>az.downloadBlob(account,container,name));return r.ok?{contentType:p.image.mime||imageMime(name)||"application/octet-stream",buffer:r.data}:null}let server2=await createCanvasServer({html:pageHtml,routes,media:mediaHandler,initialState,port});if(typeof az.registerAccountContext=="function"){let releaseContext=az.registerAccountContext(()=>({accounts:server2.getState().s_accounts,subscriptions:server2.getState().s_subscriptions})),close=server2.close;server2.close=()=>{releaseContext(),close()}}return server2.loadAccounts=()=>loadAccounts({},{getState:server2.getState,setState:server2.setState,log:server2.log}),server2.loadSubscriptions=()=>loadSubscriptions({},{getState:server2.getState,setState:server2.setState,log:server2.log}),server2}var session;async function post(server2,route,body){let res=await fetch(`http://127.0.0.1:${server2.port}${route}`,{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify(body||{})});try{return await res.json()}catch{return{ok:res.ok}}}var agent={async requestInsight({container}={}){session?.send({prompt:`On the "Azure Storage" canvas, the user clicked "Explain these statistics"${container?` for container "${container}"`:""}. +`;return(FEEDBACK_BASE+"?title="+encodeURIComponent(title)+"&body="+encodeURIComponent(body)).replace(/&/g,"&")}function escAttr(s){return String(s??"").replace(/[&<>"]/g,function(c){return{"&":"&","<":"<",">":">",'"':"""}[c]})}var BUILD_STAMP_HTML='v'+escAttr(BUILD_INFO.version)+"+"+escAttr(BUILD_INFO.revision)+"",FEEDBACK_HTML='',BODY_WITH_IDENTITY=BODY.replace("","").replace("",BUILD_STAMP_HTML).replace("",FEEDBACK_HTML);function pageHtml(nonce){let n=nonce?' nonce="'+nonce+'"':"";return'Azure Storage"+BODY_WITH_IDENTITY+'"}import{randomUUID}from"node:crypto";import{execFile as execFile3}from"node:child_process";var ID=/^[a-z][a-z0-9_.-]{0,79}$/,VERSION=/^[a-zA-Z0-9][a-zA-Z0-9.+_-]{0,39}$/,SOURCES=new Set(["model","panel","system","unknown"]),CONTROL_TYPES=new Set(["button","link","select","input","textarea","details","other"]),INTERACTIONS=new Set(["click","change","submit"]),FAILURE_CODES=Object.freeze({succeeded:"",failed:"action_error",rejected:"action_rejected",cancelled:"action_cancelled"}),RESERVED=new Set(["__proto__","constructor","prototype"]),USAGE_ENVELOPE_SCHEMA_VERSION=2,DEFAULT_REQUEST_TIMEOUT_MS=1e4,PUBLIC_CANVAS_USAGE_SERVICE=Object.freeze({serviceId:"public-canvas-product-usage",schemaVersion:USAGE_ENVELOPE_SCHEMA_VERSION,dataClassification:"pseudonymous_product_usage",endpoint:"https://canvas-metrics-pilot-0b8944-add3hsghhddcbxe9.b01.azurefd.net/api/events"}),PUBLIC_CANVAS_USAGE_ENDPOINT=PUBLIC_CANVAS_USAGE_SERVICE.endpoint,GITHUB_USER_ID=/^[1-9][0-9]{0,19}$/;function githubCliEnvironment(source){let env={...source};for(let key of Object.keys(env))["GH_TOKEN","GITHUB_TOKEN"].includes(key.toUpperCase())&&delete env[key];return env}function runFile(command,args,options,execFileImpl){return new Promise((resolve,reject)=>{execFileImpl(command,args,options,(error,stdout)=>{error?reject(error):resolve(stdout)})})}async function resolveGitHubCliUserId({signal,env=process.env,execFileImpl=execFile3}={}){if(typeof execFileImpl!="function")throw new TypeError("GitHub CLI executor must be a function.");let stdout;try{stdout=await runFile("gh",["api","--hostname","github.com","user","--jq",".id"],{env:githubCliEnvironment(env),signal,timeout:5e3,maxBuffer:1024,windowsHide:!0},execFileImpl)}catch{throw new Error("GitHub identity is unavailable.")}let githubUserId=String(stdout).trim();if(!GITHUB_USER_ID.test(githubUserId))throw new Error("GitHub identity is unavailable.");return githubUserId}function identifier(value,label,pattern=ID){if(typeof value!="string"||!pattern.test(value)||RESERVED.has(value))throw new TypeError(`${label} must be a bounded, code-defined identifier.`);return value}function record(value,label){if(!value||typeof value!="object"||![Object.prototype,null].includes(Object.getPrototypeOf(value)))throw new TypeError(`${label} must be a plain record.`);return value}function integer(value,min,max,label){if(!Number.isSafeInteger(value)||valuemax)throw new TypeError(`${label} must be an integer from ${min} to ${max}.`);return value}function endpoint(value){try{let url=new URL(value);if(url.protocol==="https:"&&!url.username&&!url.password&&!url.search&&!url.hash)return url.href}catch{}throw new TypeError("Telemetry endpoint must be credential-free HTTPS without a query or fragment.")}function createCanvasUsageMetrics(options={}){return createUsageMetrics(options)}function createUsageMetrics(options,skillContract){let{contract,canvasVersion,releaseChannel="development",enabled=!1,endpoint:destination,audience,getAccessToken,transport,fetchImpl=globalThis.fetch,getGitHubUserId,now=Date.now,randomId=randomUUID,onDiagnostic,batchSize=20,maxQueueSize=100,flushIntervalMs=1e3,requestTimeoutMs=DEFAULT_REQUEST_TIMEOUT_MS,closeTimeoutMs=DEFAULT_REQUEST_TIMEOUT_MS}=options;if(contract!==void 0&&(record(contract,"contract"),["canvasId","host","actionUsage","controls"].some(key=>Object.hasOwn(options,key))))throw new TypeError("Contract identity and registries cannot be overridden.");let canvasId=contract?.canvasId??options.canvasId,host=contract?.host??options.host??"copilot_app",actionUsage=contract?.actions??options.actionUsage??{},controls=contract?.controls??options.controls??{};if(identifier(canvasId,"canvasId"),identifier(canvasVersion,"canvasVersion",VERSION),!["copilot_app","mcp_app"].includes(host))throw new TypeError("Unsupported telemetry host.");if(contract!==void 0){if(!Array.isArray(contract.versions)||!contract.versions.length||contract.versions.some(version=>typeof version!="string"||!VERSION.test(version)))throw new TypeError("Contract versions must be bounded version identifiers.");if(!contract.versions.includes(canvasVersion))throw new TypeError("Canvas version is not registered by the telemetry contract.")}if(!["development","internal","preview","stable"].includes(releaseChannel))throw new TypeError("Unsupported telemetry release channel.");if(typeof enabled!="boolean")throw new TypeError("enabled must be a boolean.");let url=destination===void 0&&(!enabled||transport!==void 0)?void 0:endpoint(destination);for(let[name,fn]of Object.entries({now,randomId}))if(typeof fn!="function")throw new TypeError(`${name} must be a function.`);if(transport!==void 0&&typeof transport!="function")throw new TypeError("transport must be a function.");if(getAccessToken!==void 0&&typeof getAccessToken!="function")throw new TypeError("getAccessToken must be a function.");if(enabled&&!transport&&typeof fetchImpl!="function")throw new TypeError("fetchImpl must be a function.");if(getGitHubUserId!==void 0&&typeof getGitHubUserId!="function")throw new TypeError("getGitHubUserId must be a function.");if(audience!==void 0&&(typeof audience!="string"||!audience.trim()||audience.length>512||/[\r\n]/.test(audience)))throw new TypeError("audience must be a bounded nonempty string.");if(onDiagnostic!==void 0&&typeof onDiagnostic!="function")throw new TypeError("onDiagnostic must be a function.");integer(batchSize,1,100,"batchSize"),integer(maxQueueSize,batchSize,1e3,"maxQueueSize"),integer(flushIntervalMs,10,6e4,"flushIntervalMs"),integer(requestTimeoutMs,10,3e4,"requestTimeoutMs"),integer(closeTimeoutMs,10,3e4,"closeTimeoutMs");let actions=new Map(Object.entries(record(actionUsage,"actionUsage")).map(([name,entry])=>{if(identifier(name,"action name"),record(entry,"action usage"),identifier(entry.featureId,"featureId"),identifier(entry.featureArea,"featureArea"),!["intentional","automatic","excluded"].includes(entry.usageClass)||typeof entry.mutates!="boolean")throw new TypeError("Action usage requires a supported usageClass and boolean mutates.");return[name,Object.freeze({featureId:entry.featureId,featureArea:entry.featureArea,usageClass:entry.usageClass,mutates:entry.mutates})]})),controlRegistry=new Map(Object.entries(record(controls,"controls")).map(([id,type])=>{if(identifier(id,"control ID"),!CONTROL_TYPES.has(type))throw new TypeError("Unsupported telemetry control type.");return[id,type]})),queue=[],stats={delivered:0,dropped:0,invalid:0},reported=new Set,shutdown=new AbortController,timer,activeFlush,activeController,closePromise,inFlight=0,closing=!1,resolvedGitHubUserId;function diagnose(code){if(reported.has(code))return;reported.add(code);let warn=message=>{try{process.emitWarning(message,{code:"CANVAS_USAGE_METRICS"})}catch{}};if(onDiagnostic){let reportFailure=()=>warn("Canvas telemetry diagnostic callback failed.");try{Promise.resolve(onDiagnostic(Object.freeze({code}))).catch(reportFailure)}catch{reportFailure()}}else warn(`Canvas telemetry: ${code}.`)}function invalid(){return stats.invalid+=1,diagnose("invalid_event"),!1}async function githubIdentity(signal){if(resolvedGitHubUserId)return resolvedGitHubUserId;try{let value=await getGitHubUserId(signal);if(signal.aborted)return;if(typeof value=="string"&&GITHUB_USER_ID.test(value))return resolvedGitHubUserId=value,value}catch{}diagnose("identity_unavailable")}function schedule(){closing||timer||activeFlush||!queue.length||(timer=setTimeout(()=>{timer=void 0,flush()},queue.length>=batchSize?0:flushIntervalMs),timer.unref?.())}function enqueue(eventName,fields={}){if(!enabled||closing)return!1;try{let eventId=randomId();if(typeof eventId!="string"||!/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(eventId))return invalid();let event=Object.freeze({...skillContract?{schemaVersion:2,pluginName:skillContract.pluginName,pluginVersion:canvasVersion}:{schemaVersion:1,canvasId,canvasVersion},host,releaseChannel,timestamp:new Date(now()).toISOString(),eventId,eventName,invocationSource:"unknown",...fields}),overflow=queue.length===maxQueueSize;return overflow&&(queue.shift(),stats.dropped+=1),queue.push(event),overflow&&diagnose("queue_overflow"),queue.length>=batchSize&&!activeFlush?flush():schedule(),!0}catch{return stats.dropped+=1,diagnose("event_construction_failed"),!1}}async function deliver(batch){let controller=new AbortController;activeController=controller;let stop=()=>controller.abort(shutdown.signal.reason),deadline=setTimeout(()=>controller.abort("delivery_timeout"),requestTimeoutMs);deadline.unref?.(),shutdown.signal.addEventListener("abort",stop,{once:!0}),shutdown.signal.aborted&&stop();let onAbort,aborted=new Promise(resolve=>{onAbort=()=>resolve(controller.signal.reason),controller.signal.addEventListener("abort",onAbort,{once:!0}),controller.signal.aborted&&onAbort()}),request=Promise.resolve().then(async()=>{if(controller.signal.aborted)return controller.signal.reason;if(transport){await transport(Object.freeze(batch),{signal:controller.signal});return}let headers={"Content-Type":"application/json"};if(getAccessToken){let credential2=await getAccessToken(audience,controller.signal);if(controller.signal.aborted)return controller.signal.reason;let token=typeof credential2=="string"?credential2:credential2?.accessToken;if(typeof token!="string"||!token.trim()||/[\r\n]/.test(token))return"delivery_failed";headers.Authorization=`Bearer ${token}`}let githubUserId=getGitHubUserId?await githubIdentity(controller.signal):void 0;if(getGitHubUserId&&controller.signal.aborted)return controller.signal.reason;let response=await fetchImpl(url,{method:"POST",headers,body:JSON.stringify({schemaVersion:USAGE_ENVELOPE_SCHEMA_VERSION,...githubUserId?{githubUserId}:{},events:batch}),signal:controller.signal,redirect:"error",credentials:"omit"}),accepted=response?.ok;return await response?.body?.cancel(),accepted?void 0:"delivery_rejected"}).catch(()=>"delivery_failed");try{let failure=await Promise.race([request,aborted]);return failure&&diagnose(failure),{delivered:failure?0:batch.length,dropped:failure?batch.length:0}}finally{clearTimeout(deadline),shutdown.signal.removeEventListener("abort",stop),controller.signal.removeEventListener("abort",onAbort),activeController===controller&&(activeController=void 0)}}function flush(){if(activeFlush)return activeFlush;if(clearTimeout(timer),timer=void 0,!queue.length)return Promise.resolve({delivered:0,dropped:0});let batch=queue.splice(0,batchSize);return inFlight=batch.length,activeFlush=deliver(batch).then(result=>(stats.delivered+=result.delivered,stats.dropped+=result.dropped,result)).finally(()=>{inFlight=0,activeFlush=void 0,schedule()}),activeFlush}function feature(actionName,invocationSource){let usage2=actions.get(actionName);return!usage2||!SOURCES.has(invocationSource)?invalid():usage2.usageClass==="excluded"?!1:{...usage2,actionName,invocationSource}}function cancelPending(){activeController?.abort("delivery_cancelled")}function close({flush:drain=!0}={}){return drain||(closing=!0,stats.dropped+=queue.length,queue.length=0,shutdown.abort("delivery_cancelled"),cancelPending()),closePromise||(closing=!0,clearTimeout(timer),timer=void 0,closePromise=(async()=>{let deadline=setTimeout(()=>shutdown.abort("shutdown_timeout"),closeTimeoutMs);deadline.unref?.();try{for(;(queue.length||activeFlush)&&!shutdown.signal.aborted;)await flush();return activeFlush&&await activeFlush,stats.dropped+=queue.length,queue.length=0,{delivered:stats.delivered,dropped:stats.dropped}}finally{clearTimeout(deadline)}})(),closePromise)}let lifecycle={get enabled(){return enabled&&!closing},getStats:()=>Object.freeze({...stats,queued:queue.length,inFlight,closed:closing}),flush,close,cancelPending};if(skillContract){let skills=new Set(skillContract.skills),workflowId=value=>value===""||typeof value=="string"&&/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);return Object.freeze({...lifecycle,get enabled(){return enabled&&!closing},trackSkillInvoked({skillName,skillTrigger,workflowId:id=""}={}){return!enabled||closing?!1:!skills.has(skillName)||!["user-invoked","agent-invoked","context-load","file-read","unknown"].includes(skillTrigger)||!workflowId(id)?invalid():enqueue("skill_invoked",{skillName,skillTrigger,workflowId:id,invocationSource:"system"})},trackWorkflowCompleted(actionName,{workflowId:id,skillNames,outcome,durationMs}={}){if(!enabled||closing)return!1;let fields=feature(actionName,"system");return fields?!workflowId(id)||!id||!Array.isArray(skillNames)||skillNames.length>skills.size||skillNames.some(name=>!skills.has(name))||new Set(skillNames).size!==skillNames.length||typeof outcome!="string"||!Object.hasOwn(FAILURE_CODES,outcome)||!Number.isFinite(durationMs)?invalid():enqueue("skill_workflow_completed",{...fields,canvasId:skillContract.workflows[actionName].canvasId,workflowId:id,skillNames:Object.freeze([...skillNames].sort()),outcome,durationMs:Math.max(0,Math.min(Math.round(durationMs),36e5)),failureCode:FAILURE_CODES[outcome]}):!1}})}return Object.freeze({...lifecycle,get enabled(){return enabled&&!closing},hasAction:name=>actions.has(name),trackCanvasOpened(invocationSource="unknown"){return!enabled||closing?!1:SOURCES.has(invocationSource)?enqueue("canvas_opened",{invocationSource}):invalid()},trackUiInteraction(input={}){return!enabled||closing?!1:!input||!INTERACTIONS.has(input.interactionType)||!controlRegistry.has(input.controlId)||controlRegistry.get(input.controlId)!==input.controlType?invalid():enqueue("ui_interaction",{invocationSource:"panel",interactionType:input.interactionType,controlId:input.controlId,controlType:input.controlType})},trackFeatureInvoked(actionName,invocationSource="unknown"){if(!enabled||closing)return!1;let fields=feature(actionName,invocationSource);return fields?enqueue("feature_invoked",fields):!1},trackFeatureCompleted(actionName,{outcome,durationMs,invocationSource="unknown",failureCode}={}){if(!enabled||closing)return!1;let fields=feature(actionName,invocationSource);return fields?typeof outcome!="string"||!Object.hasOwn(FAILURE_CODES,outcome)||!Number.isFinite(durationMs)||failureCode!==void 0&&failureCode!==FAILURE_CODES[outcome]?invalid():enqueue("feature_completed",{...fields,outcome,durationMs:Math.max(0,Math.min(Math.round(durationMs),36e5)),failureCode:FAILURE_CODES[outcome]}):!1}})}function createPublicCanvasUsageMetrics(options={}){for(let key of["enabled","endpoint","audience","getAccessToken","transport","now","randomId"])if(Object.hasOwn(options,key))throw new TypeError(`Public canvas telemetry does not accept ${key}.`);return createCanvasUsageMetrics({...options,enabled:!0,endpoint:PUBLIC_CANVAS_USAGE_SERVICE.endpoint,getGitHubUserId:options.getGitHubUserId??resolveGitHubCliUserId})}function capture(callback,fallback){try{let result=callback();return Promise.resolve(result).catch(()=>{}),result}catch{return fallback}}function dataProperty(value,key,inherited=!1){try{for(let depth=0;value!=null&&depth<(inherited?8:1);depth++){let descriptor=Object.getOwnPropertyDescriptor(value,key);if(descriptor)return descriptor.value;value=Object.getPrototypeOf(value)}}catch{}}function completionFromResult(result){return dataProperty(result,"cancelled")===!0||dataProperty(result,"canceled")===!0||dataProperty(result,"status")==="cancelled"?"cancelled":dataProperty(result,"ok")===!1||dataProperty(result,"isError")===!0?"rejected":"succeeded"}function isAbortError(error){if(dataProperty(error,"name",!0)==="AbortError")return!0;try{return Object.getOwnPropertyDescriptor(DOMException.prototype,"name").get.call(error)==="AbortError"}catch{return!1}}function sourceFrom(resolver,args){let source=typeof resolver=="function"?capture(()=>resolver(...args),"unknown"):resolver;return SOURCES.has(source)?source:"unknown"}async function measure(metrics2,name,invocationSource,run,opened,now){let start2=capture(now,0);opened&&capture(()=>metrics2.trackCanvasOpened(invocationSource)),capture(()=>metrics2.trackFeatureInvoked(name,invocationSource));try{let result=await run();return capture(()=>metrics2.trackFeatureCompleted(name,{outcome:completionFromResult(result),invocationSource,durationMs:capture(now,start2)-start2})),result}catch(error){throw capture(()=>metrics2.trackFeatureCompleted(name,{outcome:isAbortError(error)?"cancelled":"failed",invocationSource,durationMs:capture(now,start2)-start2})),error}}function instrumentActionDispatch(dispatch,metrics2,{invocationSource="panel",openedActions=[],now=()=>performance.now()}={}){if(typeof dispatch!="function"||!SOURCES.has(invocationSource)&&typeof invocationSource!="function")throw new TypeError("Invalid dispatch instrumentation.");let opened=new Set(openedActions);return capture(()=>metrics2.enabled,!0)===!1?dispatch:function(name,...args){return capture(()=>metrics2.hasAction(name),!0)?measure(metrics2,name,sourceFrom(invocationSource,[name,...args]),()=>dispatch.call(this,name,...args),opened.has(name),now):dispatch.call(this,name,...args)}}function usage(featureId,featureArea,mutates=!1,usageClass="intentional"){return Object.freeze({featureId,featureArea,usageClass,mutates})}var AZURE_STORAGE_USAGE_CONTRACT=Object.freeze({canvasId:"azure-storage",host:"copilot_app",versions:Object.freeze(["0.3.3"]),actions:Object.freeze({"load-accounts":usage("inventory.accounts","inventory"),"load-subscriptions":usage("inventory.subscriptions","inventory"),"tenant-subscriptions":usage("inventory.tenant_subscriptions","inventory"),"select-subscriptions":usage("inventory.select_subscriptions","inventory"),account:usage("browse.account","browse"),"activate-service":usage("browse.service","browse"),container:usage("browse.container","browse"),"list-shares":usage("browse.shares","browse"),"list-queues":usage("browse.queues","browse"),"list-tables":usage("browse.tables","browse"),"storage-page":usage("browse.page","browse"),"tree-containers":usage("browse.containers","browse"),"open-queue":usage("browse.queue","browse"),"open-table":usage("browse.table","browse"),"open-share":usage("browse.share","browse"),"download-many":usage("blob.download_many","download"),"download-filtered":usage("blob.download_filtered","download"),"share-download":usage("file.download","download"),"sas-download":usage("sas.download","download"),"open-folder":usage("download.open_folder","download"),"upload-many":usage("blob.upload_many","mutation",!0),"container-create":usage("container.create","mutation",!0),"container-delete":usage("container.delete","mutation",!0),"blob-delete":usage("blob.delete","mutation",!0),"queue-create":usage("queue.create","mutation",!0),"queue-delete":usage("queue.delete","mutation",!0),"queue-send":usage("queue.send","mutation",!0),"queue-clear":usage("queue.clear","mutation",!0),"queue-msg-update":usage("queue.update_message","mutation",!0),"queue-msg-delete":usage("queue.delete_message","mutation",!0),"table-create":usage("table.create","mutation",!0),"table-delete":usage("table.delete","mutation",!0),"entity-upsert":usage("table.upsert_entity","mutation",!0),"entity-delete":usage("table.delete_entity","mutation",!0),"share-create":usage("file.create_share","mutation",!0),"share-delete":usage("file.delete_share","mutation",!0),"share-mkdir":usage("file.create_directory","mutation",!0),"share-delete-item":usage("file.delete_item","mutation",!0),"share-upload":usage("file.upload","mutation",!0),"delete-selected":usage("selection.delete","mutation",!0),"cleanup-filtered":usage("blob.cleanup_filtered","mutation",!0),"generate-sas":usage("sas.generate_blob","sas",!0),"generate-container-sas":usage("sas.generate_container","sas",!0),"sas-connect":usage("sas.connect","sas"),filter:usage("blob.filter","analysis"),"transfer-plan":usage("transfer.plan","transfer"),"azcopy-doctor":usage("transfer.doctor","transfer"),"preview-blob":usage("blob.preview","preview"),insights:usage("analysis.explain_stats","analysis"),"explain-blob":usage("preview.explain","preview"),"open-storage-explorer":usage("browse.open_explorer","browse"),"sign-in":usage("auth.sign_in","auth",!0),"save-prefs":usage("settings.save","settings"),"get-prefs":usage("settings.read","settings",!1,"excluded"),"read-state":usage("state.read","state",!1,"excluded"),"canvas-answer":usage("state.answer","state",!1,"excluded"),"dismiss-batch":usage("state.dismiss_batch","state",!1,"excluded"),"dismiss-preview":usage("state.dismiss_preview","state",!1,"excluded"),"dismiss-insight":usage("state.dismiss_insight","state",!1,"excluded"),"dismiss-error":usage("state.dismiss_error","state",!1,"excluded"),diagnose:usage("legacy.diagnose","legacy",!1,"excluded")}),controls:Object.freeze({})});function diagnostic({code}){process.emitWarning(`Canvas telemetry: ${code}.`,{code:"CANVAS_USAGE_METRICS"})}function createStorageMetrics({testMode=process.env.AZURE_STORAGE_TEST_MODE==="1",fetchImpl,getGitHubUserId}={}){let options={contract:AZURE_STORAGE_USAGE_CONTRACT,canvasVersion:BUILD_INFO.version,releaseChannel:"stable"};if(testMode)return createCanvasUsageMetrics({...options,enabled:!1});try{return createPublicCanvasUsageMetrics({...options,fetchImpl,getGitHubUserId,onDiagnostic:diagnostic})}catch{return diagnostic({code:"invalid_configuration"}),createCanvasUsageMetrics({...options,enabled:!1})}}function instrumentStorageRoutes(routes,metrics2){for(let route of Object.keys(routes))if(!Object.hasOwn(AZURE_STORAGE_USAGE_CONTRACT.actions,route.slice(1)))throw new TypeError("Every Storage route requires registered usage metadata.");if(!metrics2)return routes;let dispatch=instrumentActionDispatch(async(name,input,ctx)=>{let result=await routes[`/${name}`](input,ctx);return{result,ok:result?.ok!==!1&&!(Array.isArray(result?.failed)&&result.failed.length),cancelled:result?.cancelled===!0}},metrics2,{invocationSource:(_name,_input,_ctx,source)=>source});return Object.fromEntries(Object.keys(routes).map(route=>[route,async(input,ctx,source="panel")=>(await dispatch(route.slice(1),input,ctx,source)).result]))}var DL_ROOT=path2.join(os2.homedir(),"Downloads","azure-storage");function redactEndpointError(message,endpoints){let msg=String(message).replace(/([?&]sig=)[^&\s"'<>]*/gi,"$1[redacted]");for(let raw of endpoints)try{let url=new URL(raw);msg=msg.replaceAll(raw,`${url.origin}${url.pathname}?[redacted]`);let signature=url.searchParams.get("sig");if(signature)for(let value of[signature,encodeURIComponent(signature)])msg=msg.replaceAll(value,"[redacted]")}catch{}return msg}var PREFS_FILE=path2.join(os2.homedir(),".azure-storage","prefs.json");async function readPrefs(){try{let raw=await fs.readFile(PREFS_FILE,"utf8"),obj=JSON.parse(raw)||{};return{pinned:obj.pinned&&typeof obj.pinned=="object"?obj.pinned:{},hidden:obj.hidden&&typeof obj.hidden=="object"?obj.hidden:{},hiddenTenants:obj.hiddenTenants||{},hiddenSubscriptions:obj.hiddenSubscriptions||{}}}catch{return{pinned:{},hidden:{}}}}async function writePrefs({pinned,hidden,hiddenTenants,hiddenSubscriptions}={}){let safePinned=pinned&&typeof pinned=="object"?pinned:{},safeHidden=hidden&&typeof hidden=="object"?hidden:{};await fs.mkdir(path2.dirname(PREFS_FILE),{recursive:!0});let previous=await readPrefs(),prefs={pinned:safePinned,hidden:safeHidden,hiddenTenants:hiddenTenants||previous.hiddenTenants||{},hiddenSubscriptions:hiddenSubscriptions||previous.hiddenSubscriptions||{}};return await fs.writeFile(PREFS_FILE,JSON.stringify(prefs)),prefs}async function saveDownload(container,name,buffer){let root=path2.resolve(DL_ROOT),full=path2.resolve(root,String(container||""),String(name||"").replace(/[\\/]+/g,path2.sep));if(full!==root&&!full.startsWith(root+path2.sep))throw new Error(`refusing to write outside the download folder: ${container}/${name}`);return await fs.mkdir(path2.dirname(full),{recursive:!0}),await fs.writeFile(full,buffer),full}async function openFolder(dir){let target=path2.resolve(dir||DL_ROOT),isDir=!1;try{isDir=(await fs.stat(target)).isDirectory()}catch{isDir=!1}if(!isDir){if(dir)return{ok:!1,target,error:"folder not found"};await fs.mkdir(target,{recursive:!0}).catch(()=>{}),isDir=!0}let attempts=process.platform==="win32"?[["explorer.exe",[target]],[process.env.ComSpec||"cmd.exe",["/c","start","",target]]]:process.platform==="darwin"?[["open",[target]]]:[["xdg-open",[target]]];for(let[cmd,args]of attempts)if(await new Promise(resolve=>{try{let child=spawn3(cmd,args,{detached:!0,stdio:"ignore"}),settled=!1;child.on("error",()=>{settled||(settled=!0,resolve(!1))}),child.unref(),setTimeout(()=>{settled||(settled=!0,resolve(!0))},200)}catch{resolve(!1)}}))return{ok:!0,target};return{ok:!1,target,error:"no file manager available"}}async function start({agent:agent2,port,az=azure_exports,listTransferSas=listSasBlobs2,transferDoctor=azcopyDoctor,metrics:metrics2}={}){let subscriptionGeneration=0,navigationGeneration=0,discoveredSubscriptions=new Map,pageKey=t=>JSON.stringify([t.account||"",t.kind,t.resource||"",t.dir||t.prefix||""]),pageRequests=new Map,pageResults=new Map,serviceViews=new Map,viewFields={blob:["s_container","s_blobPrefix","s_blobPrefixes","s_blobs","s_blobsMore","s_stats","s_filtered","s_criteria","s_insight"],file:["s_fileDir","s_files"],queue:["s_messages"],table:["s_entities"]};function saveServiceView(st){st.s_account&&viewFields[st.s_kind]&&serviceViews.set(JSON.stringify([st.s_account,st.s_kind]),{...Object.fromEntries(["s_kind","s_item",...viewFields[st.s_kind]].map(key=>[key,st[key]])),incomplete:!!st.s_loading})}async function loadStoragePage(target,ctx,direction){let{getState,setState,log}=ctx,key=pageKey(target),old=getState().s_pages?.[key],cachedPages=direction?(pageResults.get(key)||[]).slice():[],index=direction==="last"?Math.max(0,cachedPages.length-1):direction==="next"?(old?.index||0)+1:direction==="previous"?(old?.index||0)-1:0;if(direction&&(!old||index<0||direction==="next"&&!old.nextToken&&!cachedPages[index]))return{ok:!1,error:"That page is not available. Refresh the current view."};let tokens=direction?old.tokens.slice():[null];direction==="next"&&!cachedPages[index]&&(tokens[index]=old.nextToken);let generation=target.resource&&target.kind!=="sas"?++navigationGeneration:null,request=(pageRequests.get(key)||0)+1;pageRequests.set(key,request);let sasUrl=target.kind==="sas"?getState().s_sasUrl:null;setState({s_pageLoading:key,s_error:null});let result=cachedPages[index]?{ok:!0,data:cachedPages[index]}:await safe(()=>az.listStoragePage({...target,token:tokens[index],...sasUrl?{sasUrl}:{}}));if(pageRequests.get(key)!==request||sasUrl&&sasUrl!==getState().s_sasUrl||generation!=null&&(generation!==navigationGeneration||target.account!==getState().s_account||target.kind!==getState().s_kind||target.resource!==getState().s_item))return{ok:!1,error:"Resource navigation changed; older page discarded."};if(!result.ok){let message=redactEndpointError(result.error,[sasUrl||""]);return setState({s_pageLoading:null,s_pageProgress:null,...target.resource?{s_loading:!1}:{},s_error:message,s_needsAuth:result.needsAuth}),log(`Read ${target.kind} page failed: ${message}`),{ok:!1,error:message}}let data=result.data;for(let scope of pageResults.keys()){let[account,kind,resource]=JSON.parse(scope);scope!==key&&!direction&&account===target.account&&kind===target.kind&&!!resource==!!target.resource&&pageResults.delete(scope)}cachedPages[index]=data,pageResults.set(key,cachedPages);let columns;target.kind==="table"&&target.resource&&(columns=[...new Set(cachedPages.flatMap(page=>page?.entities?.columns||[]))]);let patch={s_pages:{...getState().s_pages,[key]:{index,tokens,nextToken:data.nextToken||null,cachedPageCount:cachedPages.length,cachedItemCount:cachedPages.reduce((count,page)=>count+(page?.items?.length||page?.entities?.rows.length||0),0)}},s_pageProgress:null,s_pageLoading:getState().s_pageLoading===key?null:getState().s_pageLoading,...target.resource?{s_loading:!1}:{}};if(target.kind==="sas"){let parsed=parseSasUrl(getState().s_sasUrl);patch.s_sasBlobs=parsed.canRead?data.items.map(b=>({...b,url:blobSasUrl(getState().s_sasUrl,b.name)})):data.items,patch.s_sasStats=filterAndStat(data.items,{}).stats}else if(target.resource)if(target.kind==="blob"){let criteria=getState().s_criteria,filtered=criteria?filterAndStat(data.items,criteria):null;Object.assign(patch,{s_blobs:data.items,s_blobPrefixes:data.prefixes||[],s_blobsMore:!!data.nextToken,s_stats:filtered?.stats||null,s_filtered:filtered?filtered.filtered.map(b=>b.name):[]})}else target.kind==="file"?patch.s_files=data.items:target.kind==="table"&&(patch.s_entities={...data.entities,columns});else{let field={blob:"containers",file:"shares",queue:"queues",table:"tables"}[target.kind];setTree(ctx,target.account,{[field]:data.items},{[target.kind]:!1})}return setState(patch),{ok:!0,...target.kind==="sas"?{blobs:data.items.length}:target.resource?target.kind==="blob"?{blobs:data.items.length,directories:(data.prefixes||[]).length,truncated:!!data.nextToken}:target.kind==="file"?{items:data.items.length}:{entities:data.entities.rows.length}:{count:data.items.length,...target.kind==="blob"?{containers:data.items.map(item=>item.name)}:{}},page:index+1,hasNext:!!data.nextToken}}async function loadAccounts(_p,{setState,log}){setState({s_loading:!0,s_accountsLoading:!0,s_error:null});let r=await safe(()=>az.listStorageAccounts());return r.ok?(setState({s_loading:!1,s_accountsLoading:!1,s_accounts:r.data}),log(`Loaded ${r.data.length} storage accounts.`),{ok:!0,count:r.data.length}):(setState({s_loading:!1,s_accountsLoading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Load accounts failed: ${r.error}`),{ok:!1,error:r.error})}async function loadSubscriptions(_p,{setState,log}){setState({s_subsLoading:!0,s_error:null,s_seInstalled:az.storageExplorerInstalled()});let r=await safe(()=>az.listSubscriptions());if(!r.ok)return setState({s_subsLoading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Load subscriptions failed: ${r.error}`),{ok:!1,error:r.error};let inventory=await safe(()=>az.listTenants()),subs=[...r.data.filter(s=>!discoveredSubscriptions.has(s.tenantId)),...[...discoveredSubscriptions.values()].flat()].sort((a,b)=>(a.displayName||"").localeCompare(b.displayName||"")),tenants=inventory.ok?inventory.data:[];for(let sub of subs)tenants.some(t=>t.tenantId===sub.tenantId)||tenants.push({tenantId:sub.tenantId,displayName:"Cached directory",current:!1});return setState({s_subsLoading:!1,s_subscriptions:subs,s_tenants:tenants,s_tenantInventoryError:inventory.ok?null:inventory.error}),inventory.ok||log(`Current tenant inventory failed: ${inventory.error}`),log(`Found ${tenants.length} directories and ${subs.length} cached subscriptions.`),{ok:!0,count:subs.length}}async function selectSubscriptions({subscriptionIds},{getState,setState,log}){let generation=++subscriptionGeneration,requested=Array.isArray(subscriptionIds)?subscriptionIds:[],chosen=(getState().s_subscriptions||[]).filter(s=>requested.includes(s.subscriptionId)),ids=chosen.map(s=>s.subscriptionId),clearResource={s_account:null,s_container:null,s_kind:null,s_item:null,s_tree:{},s_containers:[],s_blobs:[],s_stats:null,s_filtered:[],s_shares:[],s_queues:[],s_tables:[],s_files:[],s_messages:[],s_entities:null},st=getState(),currentAccount=(st.s_accounts||[]).find(a=>a.name===st.s_account),keepResource=currentAccount&&ids.includes(currentAccount.subscriptionId);if(setState({s_selectedSubs:ids,s_loading:!0,s_accountsLoading:!0,s_error:null,s_needsAuth:!1,s_subscriptionErrors:[],...keepResource?{}:{s_accounts:[],...clearResource}}),!chosen.length)return setState({s_loading:!1,s_accountsLoading:!1}),log("Scope cleared. No subscriptions selected."),{ok:!0,count:0};log(`Loading storage accounts for ${chosen.length} subscription(s)\u2026`);let r=await safe(()=>az.listStorageAccountsForSubs(chosen));if(generation!==subscriptionGeneration)return{ok:!1,error:"Subscription selection changed while accounts were loading. Older results were discarded."};if(!r.ok)return setState({s_loading:!1,s_accountsLoading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Load accounts failed: ${r.error}`),{ok:!1,error:r.error};let{accounts,errors}=r.data,patch={s_loading:!1,s_accountsLoading:!1,s_accounts:accounts};if(getState().s_account&&!accounts.some(a=>a.name===getState().s_account)&&Object.assign(patch,clearResource),patch.s_subscriptionErrors=errors||[],errors&&errors.length){patch.s_error=`${errors.length} subscription(s) could not be read. ${accounts.length} storage account(s) loaded successfully.`;for(let e of errors)log(`Subscription ${e.subscriptionName} (${e.subscriptionId}, tenant ${e.tenantId||"unknown"}) failed: ${e.error}`)}return setState(patch),log(`Loaded ${accounts.length} accounts from ${chosen.length} subscription(s).`),{ok:!errors.length,count:accounts.length,errors}}function setTree({getState,setState},account,patch,loadingPatch){let tree={...getState().s_tree||{}},cur=tree[account]||{containers:null,shares:null,queues:null,tables:null,loading:{}};tree[account]={...cur,...patch||{},loading:{...cur.loading,...loadingPatch||{}}},setState({s_tree:tree})}async function openAccount({account},ctx){++navigationGeneration;let{getState,setState,log}=ctx;if(!account)return{ok:!1,error:"account required"};if(saveServiceView(getState()),setState({s_error:null,s_account:account,s_container:null,s_blobs:[],s_stats:null,s_filtered:[],s_criteria:null,s_insight:null,s_kind:null,s_item:null,s_files:[],s_messages:[],s_entities:null}),getState().s_tree&&getState().s_tree[account]&&getState().s_tree[account].containers!=null)return log(`Focused account ${account}.`),{ok:!0,cached:!0};if(az.listStoragePage)return loadStoragePage({account,kind:"blob"},ctx);setTree(ctx,account,{},{blob:!0}),log(`Opening account ${account}\u2026`);let r=await safe(()=>az.listContainers(account));return r.ok?(setTree(ctx,account,{containers:r.data},{blob:!1}),log(`${account}: ${r.data.length} containers.`),{ok:!0,containers:r.data.map(c=>c.name)}):(setTree(ctx,account,{},{blob:!1}),setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`Open ${account} failed: ${r.error}`),{ok:!1,error:r.error})}async function openContainer({container,account,prefix=""},ctx){let{getState,setState,log}=ctx,generation=++navigationGeneration;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(!container)return{ok:!1,error:"container required"};if(typeof prefix!="string"||prefix&&!prefix.endsWith("/"))throw new Error("Invalid blob directory prefix.");if(setState({s_loading:!0,s_error:null,s_account:account,s_container:container,s_kind:"blob",s_item:container,s_blobPrefix:prefix,s_blobPrefixes:[],s_blobs:[],s_blobsMore:!1,s_stats:null,s_filtered:[],s_criteria:null,s_insight:null}),az.listStoragePage)return loadStoragePage({account,kind:"blob",resource:container,prefix},ctx);log(`Listing blobs in ${account}/${container}\u2026`);let r=await safe(()=>az.listBlobDirectory(account,container,{prefix}));return generation!==navigationGeneration||getState().s_account!==account||getState().s_kind!=="blob"?{ok:!1,error:"Resource navigation changed; older blob listing discarded."}:r.ok?(setState({s_loading:!1,s_blobs:r.data.blobs,s_blobPrefixes:r.data.prefixes,s_blobsMore:!!r.data.truncated}),log(`${container}/${prefix}: ${r.data.blobs.length} blobs, ${r.data.prefixes.length} directories${r.data.truncated?" (listing capped at "+r.data.limit+" items)":""}.`),{ok:!0,blobs:r.data.blobs.length,directories:r.data.prefixes.length,truncated:r.data.truncated}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`List ${container} failed: ${r.error}`),{ok:!1,error:r.error})}async function listShares2({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(az.listStoragePage)return loadStoragePage({account,kind:"file"},ctx);setTree(ctx,account,{},{file:!0});let r=await safe(()=>az.listShares(account));return r.ok?(setTree(ctx,account,{shares:r.data},{file:!1}),log(`${account}: ${r.data.length} file shares.`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{file:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List shares failed: ${r.error}`),{ok:!1,error:r.error})}async function listQueues2({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(az.listStoragePage)return loadStoragePage({account,kind:"queue"},ctx);setTree(ctx,account,{},{queue:!0});let r=await safe(()=>az.listQueues(account));return r.ok?(setTree(ctx,account,{queues:r.data},{queue:!1}),log(`${account}: ${r.data.length} queues.`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{queue:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List queues failed: ${r.error}`),{ok:!1,error:r.error})}async function listTables2({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"open an account first"};if(az.listStoragePage)return loadStoragePage({account,kind:"table"},ctx);setTree(ctx,account,{},{table:!0});let r=await safe(()=>az.listTables(account));return r.ok?(setTree(ctx,account,{tables:r.data},{table:!1}),log(`${account}: ${r.data.length} tables.`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{table:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List tables failed: ${r.error}`),{ok:!1,error:r.error})}async function listContainersTree({account}={},ctx){let{getState,log}=ctx;if(account=account||getState().s_account,!account)return{ok:!1,error:"account required"};if(getState().s_tree&&getState().s_tree[account]&&getState().s_tree[account].containers!=null)return{ok:!0,cached:!0};if(az.listStoragePage)return loadStoragePage({account,kind:"blob"},ctx);setTree(ctx,account,{},{blob:!0});let r=await safe(()=>az.listContainers(account));return r.ok?(setTree(ctx,account,{containers:r.data},{blob:!1}),log(`${account}: ${r.data.length} containers (tree).`),{ok:!0,count:r.data.length}):(setTree(ctx,account,{},{blob:!1}),ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),log(`List containers failed: ${r.error}`),{ok:!1,error:r.error})}async function openQueue({queue,account},{getState,setState,log}){let generation=++navigationGeneration;if(account=account||getState().s_account,!account||!queue)return{ok:!1,error:"open an account first"};setState({s_loading:!0,s_error:null,s_account:account,s_kind:"queue",s_item:queue,s_messages:[]}),log(`Peeking messages in ${account} \xB7 queue ${queue}\u2026`);let r=await safe(()=>az.peekQueueMessages(account,queue));return generation!==navigationGeneration||getState().s_account!==account||getState().s_kind!=="queue"||getState().s_item!==queue?{ok:!1,error:"Resource navigation changed; older queue listing discarded."}:r.ok?(setState({s_loading:!1,s_messages:r.data}),log(`${queue}: peeked ${r.data.length} message(s).`),{ok:!0,messages:r.data.length}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Peek ${queue} failed: ${r.error}`),{ok:!1,error:r.error})}async function openTable({table,account},ctx){let{getState,setState,log}=ctx;if(++navigationGeneration,account=account||getState().s_account,!account||!table)return{ok:!1,error:"open an account first"};if(setState({s_loading:!0,s_error:null,s_account:account,s_kind:"table",s_item:table,s_entities:null}),az.listStoragePage)return loadStoragePage({account,kind:"table",resource:table},ctx);log(`Querying entities in ${account} \xB7 table ${table}\u2026`);let r=await safe(()=>az.queryTableEntities(account,table));return r.ok?(setState({s_loading:!1,s_entities:r.data}),log(`${table}: ${r.data.rows.length} entities.`),{ok:!0,entities:r.data.rows.length}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`Query ${table} failed: ${r.error}`),{ok:!1,error:r.error})}async function openShare({share,account,dir=""},ctx){let{getState,setState,log}=ctx,generation=++navigationGeneration;if(account=account||getState().s_account,!account||!share)return{ok:!1,error:"open an account first"};if(dir=shareDirectory(dir),setState({s_loading:!0,s_error:null,s_account:account,s_kind:"file",s_item:share,s_fileDir:dir,s_files:[]}),az.listStoragePage)return loadStoragePage({account,kind:"file",resource:share,dir},ctx);log(`Listing files in ${account} \xB7 share ${share}\u2026`);let r=await safe(()=>az.listShareItems(account,share,dir));return generation!==navigationGeneration||getState().s_account!==account||getState().s_kind!=="file"?{ok:!1,error:"Resource navigation changed; older file listing discarded."}:r.ok?(setState({s_loading:!1,s_files:r.data}),log(`${share}: ${r.data.length} item(s).`),{ok:!0,items:r.data.length}):(setState({s_loading:!1,s_error:r.error,s_needsAuth:r.needsAuth}),log(`List ${share} failed: ${r.error}`),{ok:!1,error:r.error})}async function refreshCategory(account,kind,ctx){if(az.listStoragePage)return loadStoragePage({account,kind},ctx);kind==="blob"?setTree(ctx,account,{containers:await az.listContainers(account)}):kind==="queue"?setTree(ctx,account,{queues:await az.listQueues(account)}):kind==="table"?setTree(ctx,account,{tables:await az.listTables(account)}):kind==="file"&&setTree(ctx,account,{shares:await az.listShares(account)})}async function afterMutation(read,ctx){let result=await safe(read);if(result.ok&&result.data?.ok!==!1)return{ok:!0};let cause=result.ok?result.data.error:result.error,error="Operation completed, but the listing could not be refreshed. Refresh it before retrying any operation.";return ctx.setState({s_error:error}),ctx.log(`Post-operation refresh failed: ${cause}`),{ok:!1,error,operationCompleted:!0}}function mutate(fn,{refresh,reopen}={}){return async(payload,ctx)=>{let account=payload&&payload.account||ctx.getState().s_account;if(!account)return{ok:!1,error:"open an account first"};let r=await safe(()=>fn(account,payload,ctx));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),ctx.log(`Operation failed: ${r.error}`),{ok:!1,error:r.error};if(refresh){let result=await afterMutation(()=>refreshCategory(account,refresh,ctx),ctx);if(!result.ok)return result}if(reopen){let result=await afterMutation(()=>reopen(account,payload,ctx),ctx);if(result?.ok===!1)return result}return r.data??{ok:!0}}}let reopenQueue=(account,{queue},ctx)=>openQueue({account,queue},ctx),reopenTable=(account,{table},ctx)=>openTable({account,table},ctx),reopenShare=(account,{share,dir=""},ctx)=>{let st=ctx.getState();return st.s_account!==account||st.s_kind!=="file"||st.s_item!==share||(st.s_fileDir||"")!==dir?{ok:!0}:openShare({share,account,dir},ctx)};function shareDirectory(dir){if(typeof dir!="string"||dir.split("/").some(p=>p==="."||p==="..")||dir.includes("\\"))throw new Error("Invalid file-share directory path.");return dir.split("/").filter(Boolean).join("/")}async function runBatchDownload(names,{getState,setState,log}){let st=getState(),account=st.s_account,container=st.s_container;if(!account||!container)return{ok:!1,error:"open a container first"};let list=(names||[]).filter(Boolean);if(!list.length)return{ok:!1,error:"no blobs given"};let dir=path2.join(DL_ROOT,container),done=0,okc=0,failed=[];setState({s_batch:{op:"download",total:list.length,done,ok:okc,failed:[],running:!0,dir}}),log(`Downloading ${list.length} blob(s) from ${container}\u2026`);for(let name of list){let r=await safe(()=>az.downloadBlob(account,container,name));if(done++,r.ok)try{await saveDownload(container,name,r.data),okc++}catch(e){failed.push({name,error:"could not save to disk: "+(e&&e.message?e.message:String(e))})}else failed.push({name,error:r.error});setState({s_batch:{op:"download",total:list.length,done,ok:okc,failed:[...failed],running:donei&&i.name&&i.contentBase64);if(!list.length)return{ok:!1,error:"no items given"};let done=0,okc=0,failed=[];setState({s_batch:{op:"upload",total:list.length,done,ok:okc,failed:[],running:!0}}),log(`Uploading ${list.length} file(s) to ${container}\u2026`);for(let it of list){let buf=Buffer.from(it.contentBase64,"base64"),r=await safe(()=>az.uploadBlob(account,container,(st.s_blobPrefix||"")+it.name,buf,it.contentType));done++,r.ok?okc++:failed.push({name:it.name,error:r.error}),setState({s_batch:{op:"upload",total:list.length,done,ok:okc,failed:[...failed],running:doneopenContainer({account,container,prefix:st.s_blobPrefix||""},ctx),ctx):{ok:!0};return log(`Upload complete: ${okc} ok, ${failed.length} failed.`),{ok:refreshed.ok&&!failed.length,uploaded:okc,failed,...refreshed.ok?{}:{error:"Upload finished, but listing refresh failed.",operationCompleted:okc>0}}}async function runBatchCleanup(action,ctx){let{getState,setState,log}=ctx,st=getState(),account=st.s_account,container=st.s_container;if(!account||!container)return{ok:!1,error:"open a container first"};let list=(st.s_filtered||[]).filter(Boolean);if(!list.length)return{ok:!1,error:"filter some blobs first, nothing matched"};let op=action==="delete"?"delete":"archive",done=0,okc=0,failed=[];setState({s_batch:{op,total:list.length,done,ok:okc,failed:[],running:!0}}),log(`${op==="delete"?"Deleting":"Archiving"} ${list.length} matched blob(s) in ${container}\u2026`);for(let name of list){let r=op==="delete"?await safe(()=>az.deleteBlob(account,container,name)):await safe(()=>az.setBlobTier(account,container,name,"Archive"));done++,r.ok?okc++:failed.push({name,error:r.error}),setState({s_batch:{op,total:list.length,done,ok:okc,failed:[...failed],running:doneopenContainer({account,container,prefix:st.s_blobPrefix||""},ctx),ctx):{ok:!0};return log(`${op==="delete"?"Delete":"Archive"} complete: ${okc} ok, ${failed.length} failed.`),{ok:refreshed.ok&&!failed.length,op,cleaned:okc,failed,...refreshed.ok?{}:{error:"Cleanup finished, but listing refresh failed.",operationCompleted:okc>0}}}let routes={"/load-accounts":loadAccounts,"/load-subscriptions":loadSubscriptions,"/tenant-subscriptions":async({tenantId},ctx)=>{if(!(ctx.getState().s_tenants||[]).some(t=>t.tenantId===tenantId))throw new Error("Tenant is not in the discovered inventory.");ctx.setState({s_tenantLoading:tenantId});try{let subscriptions=await az.listTenantSubscriptions(tenantId);return discoveredSubscriptions.set(tenantId,subscriptions),ctx.setState({s_subscriptions:[...(ctx.getState().s_subscriptions||[]).filter(s=>s.tenantId!==tenantId),...subscriptions],s_tenantErrors:{...ctx.getState().s_tenantErrors||{},[tenantId]:null}}),ctx.log(`Refreshed ${subscriptions.length} subscriptions for tenant ${tenantId}.`),{ok:!0}}catch(error){let failure=az.discoveryFailure(error);return ctx.setState({s_tenantErrors:{...ctx.getState().s_tenantErrors||{},[tenantId]:failure}}),ctx.log(`Tenant subscription discovery failed: ${failure.error}`),{ok:!1,error:failure.message}}finally{ctx.setState({s_tenantLoading:null})}},"/select-subscriptions":selectSubscriptions,"/account":openAccount,"/activate-service":async({account,kind},ctx)=>{let st=ctx.getState();if(account!==st.s_account||!["blob","file","queue","table"].includes(kind))throw new Error("Service selection changed.");saveServiceView(st),++navigationGeneration;let saved=serviceViews.get(JSON.stringify([account,kind])),{incomplete,...cached}=saved||{};return ctx.setState({s_entities:null,s_blobs:[],s_blobPrefixes:[],s_files:[],s_messages:[],s_stats:null,s_filtered:[],s_criteria:null,s_container:null,...saved?cached:{s_kind:kind,s_item:null},s_loading:!1,s_error:null,s_pageLoading:null,s_pageProgress:null}),incomplete&&cached.s_item?kind==="blob"?openContainer({account,container:cached.s_item,prefix:cached.s_blobPrefix||""},ctx):kind==="file"?openShare({account,share:cached.s_item,dir:cached.s_fileDir||""},ctx):kind==="table"?openTable({account,table:cached.s_item},ctx):openQueue({account,queue:cached.s_item},ctx):{ok:!0,cached:!!saved}},"/container":openContainer,"/list-shares":listShares2,"/list-queues":listQueues2,"/list-tables":listTables2,"/storage-page":async({account,kind,resource,prefix="",dir="",direction},ctx)=>{let st=ctx.getState();if(!["first","last","next","previous","refresh","cancel"].includes(direction))throw new Error("Invalid page direction.");if(kind==="sas"){if(!st.s_sasInfo||account!==st.s_sasInfo.account||resource!==st.s_sasInfo.container)throw new Error("SAS connection changed; refresh its page.")}else if(account!==st.s_account||resource&&(kind!==st.s_kind||resource!==st.s_item||kind==="blob"&&prefix!==(st.s_blobPrefix||"")||kind==="file"&&dir!==(st.s_fileDir||"")))throw new Error("Resource navigation changed; page request discarded.");if(direction==="cancel"){let key=pageKey({account,kind,resource,prefix,dir});return pageRequests.set(key,(pageRequests.get(key)||0)+1),ctx.setState({s_pageLoading:null,s_pageProgress:null,...resource&&kind!=="sas"?{s_loading:!1}:{}}),{ok:!0,cancelled:!0}}return loadStoragePage({account,kind,resource,prefix,dir},ctx,direction==="refresh"?void 0:direction)},"/tree-containers":listContainersTree,"/open-queue":openQueue,"/open-table":openTable,"/open-share":openShare,"/download-many":({blobs},ctx)=>runBatchDownload(blobs,ctx),"/open-folder":async({dir},_ctx)=>await openFolder(dir),"/dismiss-batch":(_p,{setState})=>(setState({s_batch:null}),{ok:!0}),"/get-prefs":async()=>({ok:!0,prefs:await readPrefs()}),"/save-prefs":async(input={})=>({ok:!0,prefs:await writePrefs(input)}),"/upload-many":({items,account,container,prefix},ctx)=>{let st=ctx.getState();if(account!==void 0&&(account!==st.s_account||container!==st.s_container||prefix!==(st.s_blobPrefix||""))){let error="The upload destination changed. Choose files again in the intended directory.";return ctx.setState({s_error:error}),ctx.log(error),{ok:!1,error}}return runBatchUpload(items,ctx)},"/container-create":mutate((a,{container})=>az.createContainer(a,container),{refresh:"blob"}),"/container-delete":async({container,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteContainer(a,container));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"blob",ctx),ctx);return ctx.getState().s_kind==="blob"&&ctx.getState().s_item===container&&ctx.setState({s_kind:null,s_item:null,s_container:null,s_blobs:[]}),ctx.log(`Deleted container ${container}.`),refreshed},"/blob-delete":async({container,name,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteBlob(a,container,name));return r.ok?(await afterMutation(()=>openContainer({account:a,container,prefix:ctx.getState().s_blobPrefix||""},ctx),ctx)).ok?(ctx.log(`Deleted blob ${name}.`),{ok:!0}):{ok:!1,operationCompleted:!0,error:"Blob deleted, but listing refresh failed."}:(ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error})},"/generate-sas":async({container,name,account,minutes,permissions},{getState,log})=>{let a=account||getState().s_account,c=container||getState().s_container;if(!c||!name)return{ok:!1,error:"no blob selected"};let r=await safe(()=>az.generateBlobSas(a,c,name,{minutes:Number(minutes)||60,permissions:permissions||"r"}));return r.ok?(log(`Generated ${r.data.mode} SAS for ${name} (${r.data.permissions}, expires ${r.data.expiresOn}).`),{ok:!0,name,...r.data}):{ok:!1,error:r.error,needsAuth:r.needsAuth}},"/generate-container-sas":async({container,account,minutes,permissions},{getState,log})=>{let a=account||getState().s_account,c=container||getState().s_container;if(!c)return{ok:!1,error:"no container selected"};let r=await safe(()=>az.generateContainerSas(a,c,{minutes:Number(minutes)||1440,permissions:permissions||"rl"}));return r.ok?(log(`Generated ${r.data.mode} container SAS for ${c} (${r.data.permissions}, expires ${r.data.expiresOn}).`),{ok:!0,container:c,...r.data}):{ok:!1,error:r.error,needsAuth:r.needsAuth}},"/queue-create":mutate((a,{queue})=>az.createQueue(a,queue),{refresh:"queue"}),"/queue-delete":async({queue,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteQueue(a,queue));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"queue",ctx),ctx);return ctx.getState().s_kind==="queue"&&ctx.getState().s_item===queue&&ctx.setState({s_kind:null,s_item:null,s_messages:[]}),ctx.log(`Deleted queue ${queue}.`),refreshed},"/queue-send":mutate((a,{queue,text})=>az.sendQueueMessage(a,queue,text),{reopen:reopenQueue}),"/queue-clear":mutate((a,{queue})=>az.clearQueueMessages(a,queue),{reopen:reopenQueue}),"/queue-msg-update":mutate((a,{queue,messageId,text})=>az.updateQueueMessage(a,queue,messageId,text),{reopen:reopenQueue}),"/queue-msg-delete":mutate((a,{queue,messageId})=>az.deleteQueueMessage(a,queue,messageId),{reopen:reopenQueue}),"/table-create":mutate((a,{table})=>az.createTable(a,table),{refresh:"table"}),"/table-delete":async({table,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteTable(a,table));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"table",ctx),ctx);return ctx.getState().s_kind==="table"&&ctx.getState().s_item===table&&ctx.setState({s_kind:null,s_item:null,s_entities:null}),ctx.log(`Deleted table ${table}.`),refreshed},"/entity-upsert":mutate((a,{table,entity})=>az.upsertEntity(a,table,entity),{reopen:reopenTable}),"/entity-delete":mutate((a,{table,partitionKey,rowKey})=>az.deleteEntity(a,table,partitionKey,rowKey),{reopen:reopenTable}),"/share-create":mutate((a,{share})=>az.createShare(a,share),{refresh:"file"}),"/share-delete":async({share,account},ctx)=>{let a=account||ctx.getState().s_account,r=await safe(()=>az.deleteShare(a,share));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let refreshed=await afterMutation(()=>refreshCategory(a,"file",ctx),ctx);return ctx.getState().s_kind==="file"&&ctx.getState().s_item===share&&ctx.setState({s_kind:null,s_item:null,s_files:[]}),ctx.log(`Deleted file share ${share}.`),refreshed},"/share-mkdir":mutate((a,{share,dir="",name})=>az.createShareDirectory(a,share,shareDirectory(name?[dir,name].filter(Boolean).join("/"):dir)),{reopen:reopenShare}),"/share-delete-item":mutate((a,{share,name,kind,dir=""})=>az.deleteShareItem(a,share,name,kind,shareDirectory(dir)),{reopen:reopenShare}),"/share-upload":mutate((a,{share,name,contentBase64,dir=""})=>az.uploadShareFile(a,share,name,Buffer.from(contentBase64||"","base64"),shareDirectory(dir)),{reopen:reopenShare}),"/share-download":async({account,share,name,dir:directory=""},ctx)=>{let a=account||ctx.getState().s_account;directory=shareDirectory(directory);let r=await safe(()=>az.downloadShareFile(a,share,name,directory));if(!r.ok)return ctx.setState({s_error:r.error,s_needsAuth:r.needsAuth}),{ok:!1,error:r.error};let dir=path2.join(DL_ROOT,share);try{await saveDownload(share,[directory,name].filter(Boolean).join("/"),r.data)}catch(e){let msg="could not save to disk: "+(e&&e.message?e.message:String(e));return ctx.setState({s_error:msg}),{ok:!1,error:msg}}return ctx.setState({s_batch:{op:"download",total:1,done:1,ok:1,failed:[],running:!1,dir}}),ctx.log(`Downloaded ${name} from share ${share} to ${dir}.`),{ok:!0,dir}},"/delete-selected":async({account,kind,resource,dir="",prefix="",names},ctx)=>{let st=ctx.getState();dir=shareDirectory(dir);let fail=error=>(ctx.setState({s_error:error}),ctx.log(`Delete selected failed: ${error}`),{ok:!1,error});if(!["blob","file"].includes(kind)||st.s_account!==account||st.s_kind!==kind||st.s_item!==resource||kind==="file"&&(st.s_fileDir||"")!==dir||kind==="blob"&&(st.s_blobPrefix||"")!==prefix)return fail("The open resource changed. Select items again before deleting.");let items=kind==="blob"?st.s_blobs||[]:st.s_files||[];if(!Array.isArray(names)||!names.length||names.some(n=>!items.some(i=>i.name===n)))return fail("Select items from the current listing before deleting.");let failures=[],deleted=0;for(let name of new Set(names)){let item=items.find(i=>i.name===name),result=await safe(()=>kind==="blob"?az.deleteBlob(account,resource,name):az.deleteShareItem(account,resource,name,item.kind,dir));result.ok?deleted++:(failures.push({name,error:result.error}),ctx.log(`Delete ${name} failed: ${result.error}`))}let refreshed=ctx.getState().s_account===account&&ctx.getState().s_kind===kind&&ctx.getState().s_item===resource&&(kind!=="file"||(ctx.getState().s_fileDir||"")===dir)&&(kind!=="blob"||(ctx.getState().s_blobPrefix||"")===prefix)?await(kind==="blob"?openContainer({account,container:resource,prefix},ctx):openShare({account,share:resource,dir},ctx)):{ok:!0};if(ctx.log(`Delete selected: ${deleted} deleted, ${failures.length} failed.`),failures.length||!refreshed.ok){let error=`${deleted} item(s) deleted; ${failures.length} failed.${refreshed.ok?"":" Listing refresh also failed."} Open diagnostics for details.`;return ctx.setState({s_error:error}),{ok:!1,deleted,failures,error,operationCompleted:deleted>0}}return{ok:!0,deleted}},"/diagnose":(_p,_ctx)=>({ok:!1,error:"diagnosis removed"}),"/filter":async(criteria,{getState,setState,log})=>{let st=getState();if(!(st.s_kind==="blob"&&st.s_container))return{ok:!1,error:"Open a container first to filter its blobs."};let blobs=st.s_blobs||[],{filtered,stats}=filterAndStat(blobs,criteria||{});return setState({s_criteria:criteria||{},s_filtered:filtered.map(b=>b.name),s_stats:stats}),log(`Filter matched ${filtered.length} of ${blobs.length} blobs.`),{ok:!0,matched:filtered.length,stats}},"/download-filtered":async(_p,ctx)=>{let names=ctx.getState().s_filtered||[];return runBatchDownload(names,ctx)},"/cleanup-filtered":({action},ctx)=>runBatchCleanup(action,ctx),"/transfer-plan":async({dest,resolution,useFiltered,source:srcContainer,sourceSas},{getState,setState,log})=>{let st=getState(),account=st.s_account,container=String(srcContainer||st.s_container||"").trim(),sourceSasUrl=String(sourceSas||"").trim(),fail=(message,severity="error")=>{let msg=redactEndpointError(message,[sourceSasUrl,String(dest||"")]);return setState({s_transferErr:msg,s_transferSeverity:severity,s_transferProgress:null,s_transfer:null}),log(`Transfer plan ${severity==="warning"?"blocked":"failed"}: ${msg}`),{ok:!1,error:msg,severity}},parsedSource;if(sourceSasUrl){if(parsedSource=parseSasUrl(sourceSasUrl),!parsedSource.ok)return fail(parsedSource.error);if(!parsedSource.container||parsedSource.blobPath||parsedSource.resource!=="c")return fail("Source must be a container SAS URL, not an account or blob SAS.");if(parsedSource.expired)return fail("Source SAS has expired. Provide a current container SAS.");if(!parsedSource.canRead||!parsedSource.canList)return fail("Source SAS needs Read and List permissions to plan and copy the container.");if(useFiltered)return fail("Filtered transfer applies only to the open account/container source, not a SAS source.")}else{if(!account)return fail("Open a storage account first. Pick one in the Explorer.");if(!container)return fail("Choose a source container.")}let isOpen=!sourceSasUrl&&st.s_kind==="blob"&&st.s_item===container;if(useFiltered&&(!isOpen||!st.s_criteria))return fail("Apply filter criteria to the open source container before planning a filtered transfer.");let progress=side=>count=>setState({s_transferProgress:{side,count}});setState({s_transfer:null,s_transferErr:null,s_transferSeverity:null,s_transferProgress:{side:"source",count:0}});let criteria=useFiltered?{...st.s_criteria,now:st.s_criteria.now||new Date().toISOString()}:null,options={complete:!0,onProgress:progress("source"),...criteria?{prefix:st.s_blobPrefix||"",requireTags:!!(criteria.tags&&Object.keys(criteria.tags).length||criteria.tagKeys?.length)}:{}},allBlobs;if(parsedSource){let rs=await safe(()=>listTransferSas(sourceSasUrl,options));if(!rs.ok)return fail(`Couldn't read the source SAS container: ${rs.error}`);allBlobs=rs.data.blobs}else{let rs=await safe(()=>az.listBlobs(account,container,options));if(!rs.ok)return fail(`Couldn't read source container '${container}': ${rs.error}`);allBlobs=rs.data}if(allBlobs.truncated)return fail("Source inventory is incomplete. No transfer plan was generated.");let prefix=st.s_blobPrefix||"",source=criteria?applyBlobFilter(allBlobs.filter(b=>b.name.startsWith(prefix)&&!b.name.slice(prefix.length).includes("/")),criteria):allBlobs,names=criteria?new Set(source.map(b=>b.name)):null,destStr=String(dest||"").trim();if(!destStr)return fail("Enter a destination: 'account/container' (e.g. myaccount/backups) or a container SAS URL.");if(!/[?&]sig=/.test(destStr)&&destStr.replace(/^https?:\/\//,"").split("/").filter(Boolean).length<2)return fail(`Destination must be 'account/container', not just '${destStr}'. Example: ${account}/${destStr||"backups"}, or paste a container SAS URL.`);if(`${account}/${container}`===destStr.replace(/^https?:\/\//,"").replace(".blob.core.windows.net",""))return fail("Source and destination are the same container. Pick a different destination.");let res=["overwrite","skip","if-newer"].includes(resolution)?resolution:"overwrite",destBlobs=[],destUrl="",destSas=!1;setState({s_transferProgress:{side:"destination",count:0}});let r=await safe(async()=>{if(/[?&]sig=/.test(destStr)){let p=parseSasUrl(destStr);if(!p.ok)throw new Error(p.error);if(!p.container||p.blobPath||p.resource!=="c")throw new Error("Destination must be a container SAS URL, not an account or blob SAS.");if(p.expired)throw new Error("Destination SAS has expired.");if(!p.canList)throw new Error("Destination SAS needs List permission to calculate conflicts.");if(!/w/i.test(p.permissions))throw new Error("Destination SAS needs Write permission for the planned copy.");return destUrl=p.containerUrl,destSas=!0,await listTransferSas(destStr,{complete:!0,onProgress:progress("destination")}).then(x=>x.blobs)}let m=destStr.replace(/^https?:\/\//,"").split("/"),dAcct=m[0].split(".")[0],dCont=m[1];if(!dAcct||!dCont)throw new Error("Destination must be 'account/container' or a container SAS URL.");return destUrl=`https://${dAcct}.blob.core.windows.net/${dCont}`,await az.listBlobs(dAcct,dCont,{complete:!0,onProgress:progress("destination")})});if(!r.ok)return fail(`Couldn't read the destination: ${r.error}`);if(destBlobs=r.data,destBlobs.truncated)return fail("Destination inventory is incomplete. No transfer plan was generated.");let sourceUrl=parsedSource?parsedSource.containerUrl:`https://${account}.blob.core.windows.net/${container}`;if(new URL(sourceUrl).origin+new URL(sourceUrl).pathname===new URL(destUrl).origin+new URL(destUrl).pathname)return fail("Source and destination are the same container. Pick a different destination.");if(res==="if-newer"){let byName=new Map(destBlobs.map(b=>[b.name,b]));if(source.some(b=>byName.has(b.name)&&(!Number.isFinite(Date.parse(b.lastModified))||!Number.isFinite(Date.parse(byName.get(b.name).lastModified)))))return fail("Last modified timestamps are missing for conflicting blobs. Choose Overwrite or Skip existing instead of If source newer.")}let plan=planTransfer(source,destBlobs,res),allSource=!names,{command,needsLogin,empty}=azcopyCommand({sourceUrl,destUrl,resolution:res,includeNames:names?plan.toTransfer:null,allSource});if(empty)return fail("Nothing to transfer \u2014 every selected blob already exists at the destination under the current conflict rule.");let azcopy=await transferDoctor(),summary={...plan,toTransfer:plan.toTransfer.slice(0,100),toTransferTruncated:plan.toTransfer.length>100};return setState({s_transferErr:null,s_transferSeverity:null,s_transferProgress:null,s_transfer:{plan:summary,command,needsLogin,azcopy,sourceLabel:parsedSource?`${parsedSource.account}/${parsedSource.container} (SAS)`:`${account}/${container}`,destLabel:destSas?`${plan.destCount} blobs (SAS)`:destStr,source:allSource?"all blobs":`${source.length} filtered blobs`}}),log(`Transfer plan: ${plan.transferCount} to transfer, ${plan.conflictCount} conflicts (${res}).${azcopy.installed?` AzCopy ${azcopy.version||"detected"}.`:" AzCopy not found \u2014 install it to run the command."}`),{ok:!0,plan:summary,...!parsedSource&&!destSas?{command}:{},needsLogin,azcopy}},"/azcopy-doctor":async(_p,_ctx)=>({ok:!0,azcopy:await azcopyDoctor({refresh:!0})}),"/sas-connect":async({url},ctx)=>{let{setState,log}=ctx;setState({s_loading:!0,s_error:null});let parsed=parseSasUrl(url);if(!parsed.ok)return setState({s_loading:!1,s_error:parsed.error}),{ok:!1,error:parsed.error};if(az.listStoragePage){if(parsed.expired||!parsed.canList||!parsed.container)return setState({s_loading:!1,s_error:"Provide an unexpired container SAS with List permission."}),{ok:!1,error:"Provide an unexpired container SAS with List permission."};let{containerUrl,...info}=parsed;return setState({s_sasUrl:url,s_sasInfo:info,s_sasBlobs:[]}),loadStoragePage({account:parsed.account,kind:"sas",resource:parsed.container},ctx)}log(`Connecting to SAS: ${describeSas(parsed)}`);let r=await safe(()=>listSasBlobs2(url));if(!r.ok)return setState({s_loading:!1,s_error:r.error}),log(`SAS connect failed: ${r.error}`),{ok:!1,error:r.error};let{stats}=filterAndStat(r.data.blobs,{}),withUrls=parsed.canRead?r.data.blobs.map(b=>({...b,url:blobSasUrl(url,b.name)})):r.data.blobs;return setState({s_loading:!1,s_sasUrl:url,s_sasInfo:{account:r.data.account,container:r.data.container,permissions:r.data.permissions,expiresOn:r.data.expiresOn,canRead:parsed.canRead,canList:parsed.canList,canWrite:parsed.canWrite,canDelete:parsed.canDelete,expired:parsed.expired,resource:parsed.resource},s_sasBlobs:withUrls,s_sasStats:stats}),log(`SAS connected: ${r.data.blobs.length} blobs in ${r.data.container}.`),{ok:!0,blobs:r.data.blobs.length}},"/sas-download":async({name},{getState,setState,log})=>{let url=getState().s_sasUrl;if(!url)return{ok:!1,error:"connect a SAS first"};let r=await safe(()=>downloadSasBlob2(url,name));if(!r.ok)return setState({s_error:r.error}),{ok:!1,error:r.error};let container=getState().s_sasInfo?.container||"sas",savedPath;try{savedPath=await saveDownload(container,name,Buffer.from(r.data.contentBase64,"base64"))}catch(e){let msg="could not save to disk: "+(e&&e.message?e.message:String(e));return setState({s_error:msg}),log(`SAS download failed: ${msg}`),{ok:!1,name,error:msg}}return log(`Downloaded '${name}' (${r.data.size} bytes) from SAS.`),{ok:!0,name,size:r.data.size,path:savedPath,dir:path2.dirname(savedPath)}},"/read-state":(_p,{getState})=>{let st=getState(),blobPage=st.s_pages?.[pageKey({account:st.s_account,kind:"blob",resource:st.s_container,prefix:st.s_blobPrefix||""})];return{ok:!0,state:{account:st.s_account,container:st.s_container,subscriptions:(st.s_subscriptions||[]).length,selectedSubs:st.s_selectedSubs||[],selectedSubscriptions:(st.s_subscriptions||[]).filter(s=>(st.s_selectedSubs||[]).includes(s.subscriptionId)),tenants:[...new Set((st.s_subscriptions||[]).map(s=>s.tenantId))],tenantInventory:st.s_tenants||[],tenantInventoryError:st.s_tenantInventoryError||null,loading:!!st.s_loading,error:st.s_error||null,subscriptionErrors:st.s_subscriptionErrors||[],accounts:(st.s_accounts||[]).map(a=>a.name),tree:st.s_tree||{},containers:Object.values(st.s_tree||{}).flatMap(t=>(t.containers||[]).map(c=>c.name)),blobs:st.s_blobs||[],blobsTruncated:blobPage?blobPage.index>0||!!blobPage.nextToken:!!st.s_blobs?.truncated,stats:st.s_stats||null,criteria:st.s_kind==="blob"&&st.s_criteria||null,filtered:st.s_filtered||[],batch:st.s_batch||null,listingPages:Object.entries(st.s_pages||{}).map(([scope,page])=>({scope:JSON.parse(scope),page:page.index+1,hasNext:!!page.nextToken})),sasInfo:st.s_sasInfo||null,sasBlobs:(st.s_sasBlobs||[]).map(b=>({name:b.name,size:b.size,lastModified:b.lastModified||null,contentType:b.contentType||null})),transfer:st.s_transfer?(()=>{let{command,sourceUrl,destUrl,...rest}=st.s_transfer;return rest})():null,insight:st.s_insight||null,preview:st.s_preview?{name:st.s_preview.name,isText:st.s_preview.isText,size:st.s_preview.size,truncated:st.s_preview.truncated,lang:st.s_preview.lang,format:st.s_preview.format||null,category:st.s_preview.category||null,magicHex:st.s_preview.magicHex||null,glb:st.s_preview.glb||null,isImage:!!st.s_preview.image,explaining:!!st.s_preview.explaining,explained:!!st.s_preview.explanation,text:st.s_preview.text||null}:null,s_queues:(st.s_queues||[]).map(q=>({name:q.name})),s_tables:(st.s_tables||[]).map(t=>({name:t.name})),s_shares:(st.s_shares||[]).map(x=>({name:x.name})),s_messages:(st.s_messages||[]).map(m=>({messageId:m.messageId||null,messageText:m.messageText})),s_entities:st.s_entities||null,s_files:(st.s_files||[]).map(f=>({name:f.name,kind:f.kind,size:f.size??null,lastModified:f.lastModified||null})),s_kind:st.s_kind||null,s_item:st.s_item||null,s_fileDir:st.s_fileDir||"",s_blobPrefix:st.s_blobPrefix||"",s_blobPrefixes:st.s_blobPrefixes||[]}}},"/canvas-answer":({answer,visual,target,suggestedFilter},{getState,setState})=>{let payload={pending:!1,answer:String(answer||""),visual:visual||null},st=getState(),t=target==="insights"||target==="preview"||target==="ask"?target:null;if(st.s_preview&&st.s_preview.explaining?t="preview":st.s_insight&&st.s_insight.pending?t="insights":t||(t="ask"),t==="insights")setState({s_insight:{...payload,suggestedFilter:suggestedFilter||null}});else if(t==="preview"){let p=st.s_preview||{};setState({s_preview:{...p,explaining:!1,explanation:payload}})}else setState({s_ask:payload});return{ok:!0}},"/insights":async(_p,{getState,setState,log})=>{let st=getState();return st.s_stats?(setState({s_insight:{pending:!0,answer:null,visual:null,suggestedFilter:null}}),log("Asked Copilot to explain the statistics."),await agent2?.requestInsight?.({account:st.s_account,container:st.s_container}),{ok:!0}):{ok:!1,error:"Filter or open a container first so there are statistics to explain."}},"/preview-blob":async({name},{getState,setState,log})=>{let st=getState(),account=st.s_account,container=st.s_container;if(!account||!container||!name)return{ok:!1,error:"Open a container and pick a blob to preview."};setState({s_preview:{name,pending:!0}});let MAX=32*1024,r=await safe(()=>az.downloadBlob(account,container,name,{maxBytes:MAX}));if(!r.ok)return setState({s_preview:{name,pending:!1,error:r.error}}),log(`Preview failed: ${r.error}`),{ok:!1,error:r.error};let slice=r.data.subarray(0,MAX),{isText,text}=sniffText(slice),bin=isText?null:sniffBinaryFormat(slice,name),mime=isText?null:imageMime(name),sz=await safe(()=>az.blobSize(account,container,name)),total=sz.ok?sz.data:slice.length,RENDER_MAX=25*1024*1024,GLB_MAX=50*1024*1024,image=mime&&total<=RENDER_MAX?{mime}:null,glb=null;if(bin&&/GLB/.test(bin.format)&&total<=GLB_MAX){let full=await safe(()=>az.downloadBlob(account,container,name));full.ok&&(glb=parseGlb(full.data))}return setState({s_preview:{name,pending:!1,size:total,truncated:total>MAX,isText,lang:previewLang(name),text:isText?text:null,format:bin?bin.format:null,category:bin?bin.category:null,magicHex:bin?bin.magicHex:null,image,glb,explanation:null}}),log(`Previewed ${name}: ${isText?"text":bin.format}, ${total} bytes.`),{ok:!0,isText,bytes:total}},"/explain-blob":async(_p,{getState,setState})=>{let p=getState().s_preview;return!p||p.pending||p.error?{ok:!1,error:"Preview a blob first."}:!p.isText&&!p.format?{ok:!1,error:"Preview a blob first."}:(setState({s_preview:{...p,explaining:!0,explanation:null}}),await agent2?.requestPreviewExplain?.({name:p.name,isText:!!p.isText,format:p.format||null,category:p.category||null,size:p.size,glb:p.glb||null}),{ok:!0})},"/dismiss-preview":(_p,{setState})=>(setState({s_preview:null}),{ok:!0}),"/dismiss-insight":(_p,{setState})=>(setState({s_insight:null}),{ok:!0}),"/open-storage-explorer":async(_p,{getState,log})=>{let st=getState(),account=st.s_account||null,kind=st.s_kind||null,item=st.s_item||null,where=item?`${account}/${item}`:account||"the app";log(`Opening Azure Storage Explorer \u2192 ${where}\u2026`);let r=await az.openInStorageExplorer({account,kind,item});return r.ok?(log(r.deepLink?`Opened Storage Explorer at ${where}.`:"Opened Storage Explorer."),{ok:!0,deepLink:r.deepLink}):r.notInstalled?(log("Azure Storage Explorer isn't installed. Opening the download page so you can get the free desktop app."),{ok:!1,notInstalled:!0,install:r.install}):(log(`Storage Explorer: ${r.error}`),{ok:!1,error:r.error})},"/dismiss-error":(_p,{setState})=>(setState({s_error:null,s_needsAuth:!1}),{ok:!0}),"/sign-in":async({tenantId}={},ctx)=>{let{setState,log}=ctx;if(tenantId&&![...ctx.getState().s_subscriptions||[],...ctx.getState().s_tenants||[]].some(s=>s.tenantId===tenantId))throw new Error("The requested tenant is not in the discovered Azure inventory.");setState({s_signingIn:!0,s_error:null}),log("Opening az login in your browser\u2026");let r=await az.signIn({tenantId});return r.ok?(setState({s_signingIn:!1,s_needsAuth:!1,s_error:null}),log("Signed in successfully. Loading subscriptions\u2026"),await loadSubscriptions({},ctx),(ctx.getState().s_selectedSubs||[]).length&&await selectSubscriptions({subscriptionIds:ctx.getState().s_selectedSubs},ctx),{ok:!0}):(setState({s_signingIn:!1,s_error:r.error,s_needsAuth:!0}),log(`Sign-in failed: ${r.error}`),{ok:!1,error:r.error})}},initialState={s_subscriptions:[],s_selectedSubs:[],s_subsLoading:!1,s_accounts:[],s_tree:{},s_containers:[],s_blobs:[],s_filtered:[],s_shares:[],s_queues:[],s_tables:[],s_files:[],s_messages:[],s_entities:null,s_kind:null,s_item:null,s_svcLoading:null,s_account:null,s_container:null,s_stats:null,s_batch:null,s_sasInfo:null,s_sasBlobs:[],s_ask:null,s_transfer:null,s_insight:null,s_preview:null,s_transferErr:null,s_loading:!1,s_accountsLoading:!1,s_error:null,activityLog:[],s_signingIn:!1,s_seInstalled:null};async function mediaHandler(pathname,query,{getState}){if(pathname!=="/blob-media")return null;let name=query.get("name"),st=getState(),p=st.s_preview;if(!p||p.name!==name||!p.image)return null;let account=st.s_account,container=st.s_container;if(!account||!container)return null;let r=await safe(()=>az.downloadBlob(account,container,name));return r.ok?{contentType:p.image.mime||imageMime(name)||"application/octet-stream",buffer:r.data}:null}let server2=await createCanvasServer({html:pageHtml,routes:instrumentStorageRoutes(routes,metrics2),media:mediaHandler,initialState,port});if(typeof az.registerAccountContext=="function"){let releaseContext=az.registerAccountContext(()=>({accounts:server2.getState().s_accounts,subscriptions:server2.getState().s_subscriptions})),close=server2.close;server2.close=()=>{releaseContext(),close()}}return server2.loadAccounts=()=>loadAccounts({},{getState:server2.getState,setState:server2.setState,log:server2.log}),server2.loadSubscriptions=()=>loadSubscriptions({},{getState:server2.getState,setState:server2.setState,log:server2.log}),server2}var session,metrics=createStorageMetrics();async function post(server2,route,body){let res=await fetch(`http://127.0.0.1:${server2.port}${route}`,{method:"POST",headers:{"Content-Type":"application/json","X-Storage-Invocation-Source":"model"},body:JSON.stringify(body||{})});try{return await res.json()}catch{return{ok:res.ok}}}var agent={async requestInsight({container}={}){session?.send({prompt:`On the "Azure Storage" canvas, the user clicked "Explain these statistics"${container?` for container "${container}"`:""}. Call read_canvas_state ONCE and read the "stats" object (count, total/avg/max size, size histogram buckets, byType, byTier, byExt, topLargest) and the "filtered" set. The numbers are ALREADY provided \u2014 do NOT run shell commands or call other tools to compute them. Then immediately call answer_on_canvas with target:"insights" and a concise, specific interpretation: what stands out about size distribution, access tiers (Hot/Cool/Cold/Archive), file types, and any cost/cleanup/lifecycle opportunity (e.g. large cold data, many tiny files, old blobs). Cite real numbers. Include a visual ({kind:'bar'|'stat'|'table', ...}) when it helps. If an obvious cleanup/selection would help, ALSO pass suggestedFilter = a storage_filter_blobs criteria object (e.g. {olderThanDays:180} or {tiers:["Archive"],minSize:1048576}) so the user can apply it in one click. Do NOT explain in chat \u2014 render on the canvas.`,displayPrompt:"\u{1F9E0} Storage canvas \u2014 explain statistics"})},async requestPreviewExplain({name,isText,format,category,glb}={}){let glbNote=glb&&!glb.truncated?` The GLB's embedded JSON was parsed \u2014 read preview.glb for the REAL counts (meshes:${glb.meshes}, materials:${glb.materials}, textures:${glb.textures}, nodes:${glb.nodes}, animations:${glb.animations}, vertices:${glb.vertices}, triangles:${glb.triangles}${glb.generator?`, generator:"${glb.generator}"`:""}) and CITE these actual numbers instead of speaking generically.`:"",prompt=isText?`On the "Azure Storage" canvas, the user clicked "Ask Copilot to explain" for the previewed blob "${name}". Call read_canvas_state ONCE and read "preview" ({name, lang, size, truncated, text}). The file content is ALREADY provided in preview.text \u2014 do NOT run any shell commands, do NOT read files from disk, and do NOT call other tools to fetch it. Work only from preview.text. Then immediately call answer_on_canvas with target:"preview" and a concise explanation: what this file is, its structure/schema (columns/keys/format), what the data represents, and anything notable. If it was truncated, say so. Add a visual ({kind:'table'|'bar'|'stat'}) only if it clarifies the structure. Do NOT explain in chat \u2014 render on the canvas.`:`On the "Azure Storage" canvas, the user clicked "Explain this file" for the BINARY blob "${name}" (no text preview). Call read_canvas_state ONCE and read "preview" ({name, size, format, category, magicHex, glb}). The format was detected from the leading magic bytes${format?` and is "${format}"${category?` (${category})`:""}`:""}.${glbNote} The bytes are binary \u2014 do NOT run any shell commands, do NOT download or read the file, and do NOT call other tools. Reason only from the fields in preview. -Then immediately call answer_on_canvas with target:"preview" and a concise explanation of THIS FILE: what "${format||"this format"}" is and what it's used for, ${glb&&!glb.truncated?"PLUS the specific model composition using the real preview.glb counts (meshes/materials/textures/vertices/triangles), and a visual ({kind:'table'} listing those counts). ":""}what tooling opens/produces it, and practical Azure Storage tips (right Content-Type, whether it compresses, viewing, lifecycle/tiering). Keep it specific and do NOT claim to have inspected raw geometry beyond the provided counts. Do NOT explain in chat \u2014 render on the canvas.`;session?.send({prompt,displayPrompt:`\u{1F441} Storage canvas \u2014 explain "${String(name).slice(0,60)}"`})}},server=await start({agent}),canvas=createCanvas({id:"azure-storage",displayName:"Azure Storage",description:"Browse Azure Storage blobs/containers, bulk-transfer with live progress, filter + aggregate statistics, and open SAS links with no login \u2014 all agent-drivable on one live surface.",actions:[{name:"read_canvas_state",description:"Pull the live data shown on the Storage canvas (current account/directory, blob and file metadata, queue message IDs, applied current-page filter criteria and statistics, filtered set, batch progress, SAS info). Use this before answering any question about what's on the canvas.",inputSchema:{type:"object",properties:{keys:{type:"array",items:{type:"string"}}}},handler:async()=>post(server,"/read-state",{})},{name:"answer_on_canvas",description:"Render shared agent notes alongside resource contents. `target` selects the surface: 'ask' (legacy default name for resource annotations, not a chat), 'insights' (statistics), or 'preview' (blob preview). Conversation stays in the session chat. Pass text `answer` and an optional quantitative `visual` ({kind:'bar'|'stat'|'table', ...}). For target='insights', suggestedFilter can offer a one-click storage filter.",inputSchema:{type:"object",properties:{answer:{type:"string"},visual:{type:"object"},target:{type:"string",enum:["ask","insights","preview"]},suggestedFilter:{type:"object"}},required:["answer"]},handler:async({input})=>post(server,"/canvas-answer",{answer:input.answer,visual:input.visual,target:input.target,suggestedFilter:input.suggestedFilter})},{name:"storage_open_account",description:"Open a storage account on the canvas and load its containers live. Pass the account name exactly as shown in the account list (read_canvas_state \u2192 accounts).",inputSchema:{type:"object",properties:{account:{type:"string"}},required:["account"]},handler:async({input})=>post(server,"/account",{account:input.account})},{name:"storage_open_container",description:"Open a blob container directory on the canvas. Optional prefix is an exact virtual-folder prefix ending in '/'; omit for root. Returns immediate blobs with canonical names and directory prefixes. Capped listings explicitly report incompleteness. Requires an open account.",inputSchema:{type:"object",properties:{container:{type:"string"},prefix:{type:"string"}},required:["container"]},handler:async({input})=>post(server,"/container",{container:input.container,prefix:input.prefix||""})},{name:"storage_batch_download",description:"Download several blobs at once from the open container with a LIVE progress bar and per-file pass/fail summary. Pass `blobs` = array of exact blob names (choose them by reading the blob list \u2014 e.g. the largest, or those older than a date). Non-destructive to the cloud.",inputSchema:{type:"object",properties:{blobs:{type:"array",items:{type:"string"}}},required:["blobs"]},handler:async({input})=>post(server,"/download-many",{blobs:input.blobs})},{name:"storage_batch_upload",description:"MUTATION \u2014 upload multiple files into the open container directory with a live progress bar. The current directory prefix is prepended to each supplied name. Only after the user has clearly asked to upload; narrate what you're uploading first. Pass `items` = array of {name, contentBase64, contentType}.",mutates:!0,inputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object"}}},required:["items"]},handler:async({input})=>post(server,"/upload-many",{items:input.items})},{name:"storage_filter_blobs",description:"Filter the displayed blob page in the open container directory and compute LIVE statistics on the canvas (count, total/avg/max size, size histogram, by-type/by-tier/by-extension breakdown, top-10 largest). This does not enumerate other pages or folders. All criteria optional: namePattern (glob), prefix/suffix/contains (substring match on blob name), minSize/maxSize (bytes), olderThanDays/newerThanDays, type (image|video|audio|text/data|document|archive|binary), tiers (Hot|Cool|Cold|Archive \u2014 array or comma list), blobTypes (BlockBlob|PageBlob|AppendBlob), tagKeys (blob-index tag keys that must exist), tags (object of key=value that must all match). The matched NAMES become the filtered set for storage_download_filtered. read_canvas_state returns the applied criteria.",inputSchema:{type:"object",properties:{namePattern:{type:"string"},prefix:{type:"string"},suffix:{type:"string"},contains:{type:"string"},minSize:{type:"number"},maxSize:{type:"number"},olderThanDays:{type:"number"},newerThanDays:{type:"number"},type:{type:"string"},tiers:{type:"array",items:{type:"string"}},blobTypes:{type:"array",items:{type:"string"}},tagKeys:{type:"array",items:{type:"string"}},tags:{type:"object"}}},handler:async({input})=>post(server,"/filter",input)},{name:"storage_download_filtered",description:"Download every blob currently matched by the last storage_filter_blobs, with a live progress bar. Use after filtering, e.g. 'download everything older than 30 days'. Non-destructive to the cloud.",inputSchema:{type:"object",properties:{}},handler:async()=>post(server,"/download-filtered",{})},{name:"storage_open_sas",description:"Open a container SAS URL on the canvas WITHOUT any az login \u2014 parses the SAS, lists its blobs, and shows a read view. Use when the user pastes a 'https://acct.blob.core.windows.net/container?...&sig=...' link. Impossible for the Azure/Storage MCP (they require a full account + RBAC).",inputSchema:{type:"object",properties:{url:{type:"string"}},required:["url"]},handler:async({input})=>post(server,"/sas-connect",{url:input.url})},{name:"storage_download_sas_blob",description:"Download one blob from the currently-connected SAS container. Pass the exact blob `name` from the SAS blob list. Read-only.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"]},handler:async({input})=>post(server,"/sas-download",{name:input.name})},{name:"storage_browse_page",description:"Browse the current Storage list using cached page results and Azure continuation tokens. First/previous/last use fetched pages; last means last cached page, not an automatic Azure inventory walk. Next fetches an uncached page; refresh invalidates this scope and returns to page one; cancel discards an in-flight read after it finishes. Replaces the displayed page, never modifies cloud data. kind is blob, file, queue (queue list only; message peek has no continuation), table or sas. resource is the open container/share/table, omitted for service resource lists. Retain the exact current blob prefix/file dir. Search and selection apply to the displayed page.",inputSchema:{type:"object",properties:{account:{type:"string"},kind:{type:"string",enum:["blob","file","queue","table","sas"]},resource:{type:"string"},prefix:{type:"string"},dir:{type:"string"},direction:{type:"string",enum:["first","last","next","previous","refresh","cancel"]}},required:["account","kind","direction"]},handler:async({input})=>post(server,"/storage-page",input)},{name:"storage_plan_transfer",description:"Preview an AzCopy transfer from the open container or an explicit sourceSas container URL (Read+List permissions) to dest ('account/container' or a container SAS with List+Write permissions). Follows every inventory page independently of the display cap and calculates conflicts; never moves data. useFiltered reapplies Filter & statistics criteria to all direct blobs in the open folder, not descendants or Search items text. Returns counts/bytes and bounded conflict/name samples with an unsigned command; commands containing SAS credentials stay on the canvas, not in the model response.",inputSchema:{type:"object",properties:{dest:{type:"string"},sourceSas:{type:"string"},resolution:{type:"string",enum:["overwrite","skip","if-newer"]},useFiltered:{type:"boolean"}},required:["dest"]},handler:async({input})=>post(server,"/transfer-plan",{dest:input.dest,sourceSas:input.sourceSas,resolution:input.resolution,useFiltered:input.useFiltered})},{name:"storage_preview_blob",description:"Open an inline text PREVIEW of one blob from the open container (first 32 KB). Pass the exact blob `name`. Read-only. Binary blobs are detected and reported as non-text. After previewing, you can read the text via read_canvas_state \u2192 preview.text and explain it via answer_on_canvas target:'preview'.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"]},handler:async({input})=>post(server,"/preview-blob",{name:input.name})},{name:"storage_explain_stats",description:"Trigger the canvas's 'Explain these statistics' insight panel \u2014 reads the live filtered stats and renders an interpretation (via answer_on_canvas target:'insights'). Use when the user asks what the storage stats mean or how to clean up / optimize cost.",inputSchema:{type:"object",properties:{}},handler:async()=>post(server,"/insights",{})},{name:"storage_list_queues",description:"List the Storage queues in the open account on the canvas (read-only). Opens the Queues view and shows each queue with its approximate message count. Requires an account to be open first (storage_open_account).",inputSchema:{type:"object",properties:{account:{type:"string"}}},handler:async({input})=>post(server,"/list-queues",{account:input.account})},{name:"storage_open_queue",description:"Open one Storage queue on the canvas and peek its messages live (read-only \u2014 peeking does not dequeue). Pass the exact queue `name` from storage_list_queues. Requires an account to be open first.",inputSchema:{type:"object",properties:{queue:{type:"string"},account:{type:"string"}},required:["queue"]},handler:async({input})=>post(server,"/open-queue",{queue:input.queue,account:input.account})},{name:"storage_list_tables",description:"List the Storage tables in the open account on the canvas (read-only). Opens the Tables view. Requires an account to be open first (storage_open_account).",inputSchema:{type:"object",properties:{account:{type:"string"}}},handler:async({input})=>post(server,"/list-tables",{account:input.account})},{name:"storage_open_table",description:"Open one Storage table on the canvas and list its entities live (read-only). Pass the exact table `name` from storage_list_tables. Requires an account to be open first.",inputSchema:{type:"object",properties:{table:{type:"string"},account:{type:"string"}},required:["table"]},handler:async({input})=>post(server,"/open-table",{table:input.table,account:input.account})},{name:"storage_list_shares",description:"List the Azure File shares in the open account on the canvas (read-only). Opens the File shares view. Requires an account to be open first (storage_open_account).",inputSchema:{type:"object",properties:{account:{type:"string"}}},handler:async({input})=>post(server,"/list-shares",{account:input.account})},{name:"storage_open_share",description:"Open one Azure File share directory on the canvas and list its files and directories live (read-only). Pass the exact share `name` from storage_list_shares and optional share-relative dir; omit dir for root. Requires an account to be open first.",inputSchema:{type:"object",properties:{share:{type:"string"},account:{type:"string"},dir:{type:"string"}},required:["share"]},handler:async({input})=>post(server,"/open-share",{share:input.share,account:input.account,dir:input.dir||""})},{name:"log_message",description:"Append a message to the canvas activity log visible to the user.",inputSchema:{type:"object",properties:{message:{type:"string"}},required:["message"]},handler:({input})=>(server.log(input.message),{ok:!0})}],open(){return server.loadSubscriptions().catch(()=>{}),{url:`http://127.0.0.1:${server.port}`,title:"Azure Storage",status:"ready"}},onClose(){server.close?.()}});session=await joinSession({canvases:[canvas]}); +Then immediately call answer_on_canvas with target:"preview" and a concise explanation of THIS FILE: what "${format||"this format"}" is and what it's used for, ${glb&&!glb.truncated?"PLUS the specific model composition using the real preview.glb counts (meshes/materials/textures/vertices/triangles), and a visual ({kind:'table'} listing those counts). ":""}what tooling opens/produces it, and practical Azure Storage tips (right Content-Type, whether it compresses, viewing, lifecycle/tiering). Keep it specific and do NOT claim to have inspected raw geometry beyond the provided counts. Do NOT explain in chat \u2014 render on the canvas.`;session?.send({prompt,displayPrompt:`\u{1F441} Storage canvas \u2014 explain "${String(name).slice(0,60)}"`})}},server=await start({agent,metrics}),canvas=createCanvas({id:"azure-storage",displayName:"Azure Storage",description:"Browse Azure Storage blobs/containers, bulk-transfer with live progress, filter + aggregate statistics, and open SAS links with no login \u2014 all agent-drivable on one live surface.",actions:[{name:"read_canvas_state",description:"Pull the live data shown on the Storage canvas (current account/directory, blob and file metadata, queue message IDs, applied current-page filter criteria and statistics, filtered set, batch progress, SAS info). Use this before answering any question about what's on the canvas.",inputSchema:{type:"object",properties:{keys:{type:"array",items:{type:"string"}}}},handler:async()=>post(server,"/read-state",{})},{name:"answer_on_canvas",description:"Render shared agent notes alongside resource contents. `target` selects the surface: 'ask' (legacy default name for resource annotations, not a chat), 'insights' (statistics), or 'preview' (blob preview). Conversation stays in the session chat. Pass text `answer` and an optional quantitative `visual` ({kind:'bar'|'stat'|'table', ...}). For target='insights', suggestedFilter can offer a one-click storage filter.",inputSchema:{type:"object",properties:{answer:{type:"string"},visual:{type:"object"},target:{type:"string",enum:["ask","insights","preview"]},suggestedFilter:{type:"object"}},required:["answer"]},handler:async({input})=>post(server,"/canvas-answer",{answer:input.answer,visual:input.visual,target:input.target,suggestedFilter:input.suggestedFilter})},{name:"storage_open_account",description:"Open a storage account on the canvas and load its containers live. Pass the account name exactly as shown in the account list (read_canvas_state \u2192 accounts).",inputSchema:{type:"object",properties:{account:{type:"string"}},required:["account"]},handler:async({input})=>post(server,"/account",{account:input.account})},{name:"storage_open_container",description:"Open a blob container directory on the canvas. Optional prefix is an exact virtual-folder prefix ending in '/'; omit for root. Returns immediate blobs with canonical names and directory prefixes. Capped listings explicitly report incompleteness. Requires an open account.",inputSchema:{type:"object",properties:{container:{type:"string"},prefix:{type:"string"}},required:["container"]},handler:async({input})=>post(server,"/container",{container:input.container,prefix:input.prefix||""})},{name:"storage_batch_download",description:"Download several blobs at once from the open container with a LIVE progress bar and per-file pass/fail summary. Pass `blobs` = array of exact blob names (choose them by reading the blob list \u2014 e.g. the largest, or those older than a date). Non-destructive to the cloud.",inputSchema:{type:"object",properties:{blobs:{type:"array",items:{type:"string"}}},required:["blobs"]},handler:async({input})=>post(server,"/download-many",{blobs:input.blobs})},{name:"storage_batch_upload",description:"MUTATION \u2014 upload multiple files into the open container directory with a live progress bar. The current directory prefix is prepended to each supplied name. Only after the user has clearly asked to upload; narrate what you're uploading first. Pass `items` = array of {name, contentBase64, contentType}.",mutates:!0,inputSchema:{type:"object",properties:{items:{type:"array",items:{type:"object"}}},required:["items"]},handler:async({input})=>post(server,"/upload-many",{items:input.items})},{name:"storage_filter_blobs",description:"Filter the displayed blob page in the open container directory and compute LIVE statistics on the canvas (count, total/avg/max size, size histogram, by-type/by-tier/by-extension breakdown, top-10 largest). This does not enumerate other pages or folders. All criteria optional: namePattern (glob), prefix/suffix/contains (substring match on blob name), minSize/maxSize (bytes), olderThanDays/newerThanDays, type (image|video|audio|text/data|document|archive|binary), tiers (Hot|Cool|Cold|Archive \u2014 array or comma list), blobTypes (BlockBlob|PageBlob|AppendBlob), tagKeys (blob-index tag keys that must exist), tags (object of key=value that must all match). The matched NAMES become the filtered set for storage_download_filtered. read_canvas_state returns the applied criteria.",inputSchema:{type:"object",properties:{namePattern:{type:"string"},prefix:{type:"string"},suffix:{type:"string"},contains:{type:"string"},minSize:{type:"number"},maxSize:{type:"number"},olderThanDays:{type:"number"},newerThanDays:{type:"number"},type:{type:"string"},tiers:{type:"array",items:{type:"string"}},blobTypes:{type:"array",items:{type:"string"}},tagKeys:{type:"array",items:{type:"string"}},tags:{type:"object"}}},handler:async({input})=>post(server,"/filter",input)},{name:"storage_download_filtered",description:"Download every blob currently matched by the last storage_filter_blobs, with a live progress bar. Use after filtering, e.g. 'download everything older than 30 days'. Non-destructive to the cloud.",inputSchema:{type:"object",properties:{}},handler:async()=>post(server,"/download-filtered",{})},{name:"storage_open_sas",description:"Open a container SAS URL on the canvas WITHOUT any az login \u2014 parses the SAS, lists its blobs, and shows a read view. Use when the user pastes a 'https://acct.blob.core.windows.net/container?...&sig=...' link. Impossible for the Azure/Storage MCP (they require a full account + RBAC).",inputSchema:{type:"object",properties:{url:{type:"string"}},required:["url"]},handler:async({input})=>post(server,"/sas-connect",{url:input.url})},{name:"storage_download_sas_blob",description:"Download one blob from the currently-connected SAS container. Pass the exact blob `name` from the SAS blob list. Read-only.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"]},handler:async({input})=>post(server,"/sas-download",{name:input.name})},{name:"storage_browse_page",description:"Browse the current Storage list using cached page results and Azure continuation tokens. First/previous/last use fetched pages; last means last cached page, not an automatic Azure inventory walk. Next fetches an uncached page; refresh invalidates this scope and returns to page one; cancel discards an in-flight read after it finishes. Replaces the displayed page, never modifies cloud data. kind is blob, file, queue (queue list only; message peek has no continuation), table or sas. resource is the open container/share/table, omitted for service resource lists. Retain the exact current blob prefix/file dir. Search and selection apply to the displayed page.",inputSchema:{type:"object",properties:{account:{type:"string"},kind:{type:"string",enum:["blob","file","queue","table","sas"]},resource:{type:"string"},prefix:{type:"string"},dir:{type:"string"},direction:{type:"string",enum:["first","last","next","previous","refresh","cancel"]}},required:["account","kind","direction"]},handler:async({input})=>post(server,"/storage-page",input)},{name:"storage_plan_transfer",description:"Preview an AzCopy transfer from the open container or an explicit sourceSas container URL (Read+List permissions) to dest ('account/container' or a container SAS with List+Write permissions). Follows every inventory page independently of the display cap and calculates conflicts; never moves data. useFiltered reapplies Filter & statistics criteria to all direct blobs in the open folder, not descendants or Search items text. Returns counts/bytes and bounded conflict/name samples with an unsigned command; commands containing SAS credentials stay on the canvas, not in the model response.",inputSchema:{type:"object",properties:{dest:{type:"string"},sourceSas:{type:"string"},resolution:{type:"string",enum:["overwrite","skip","if-newer"]},useFiltered:{type:"boolean"}},required:["dest"]},handler:async({input})=>post(server,"/transfer-plan",{dest:input.dest,sourceSas:input.sourceSas,resolution:input.resolution,useFiltered:input.useFiltered})},{name:"storage_preview_blob",description:"Open an inline text PREVIEW of one blob from the open container (first 32 KB). Pass the exact blob `name`. Read-only. Binary blobs are detected and reported as non-text. After previewing, you can read the text via read_canvas_state \u2192 preview.text and explain it via answer_on_canvas target:'preview'.",inputSchema:{type:"object",properties:{name:{type:"string"}},required:["name"]},handler:async({input})=>post(server,"/preview-blob",{name:input.name})},{name:"storage_explain_stats",description:"Trigger the canvas's 'Explain these statistics' insight panel \u2014 reads the live filtered stats and renders an interpretation (via answer_on_canvas target:'insights'). Use when the user asks what the storage stats mean or how to clean up / optimize cost.",inputSchema:{type:"object",properties:{}},handler:async()=>post(server,"/insights",{})},{name:"storage_list_queues",description:"List the Storage queues in the open account on the canvas (read-only). Opens the Queues view and shows each queue with its approximate message count. Requires an account to be open first (storage_open_account).",inputSchema:{type:"object",properties:{account:{type:"string"}}},handler:async({input})=>post(server,"/list-queues",{account:input.account})},{name:"storage_open_queue",description:"Open one Storage queue on the canvas and peek its messages live (read-only \u2014 peeking does not dequeue). Pass the exact queue `name` from storage_list_queues. Requires an account to be open first.",inputSchema:{type:"object",properties:{queue:{type:"string"},account:{type:"string"}},required:["queue"]},handler:async({input})=>post(server,"/open-queue",{queue:input.queue,account:input.account})},{name:"storage_list_tables",description:"List the Storage tables in the open account on the canvas (read-only). Opens the Tables view. Requires an account to be open first (storage_open_account).",inputSchema:{type:"object",properties:{account:{type:"string"}}},handler:async({input})=>post(server,"/list-tables",{account:input.account})},{name:"storage_open_table",description:"Open one Storage table on the canvas and list its entities live (read-only). Pass the exact table `name` from storage_list_tables. Requires an account to be open first.",inputSchema:{type:"object",properties:{table:{type:"string"},account:{type:"string"}},required:["table"]},handler:async({input})=>post(server,"/open-table",{table:input.table,account:input.account})},{name:"storage_list_shares",description:"List the Azure File shares in the open account on the canvas (read-only). Opens the File shares view. Requires an account to be open first (storage_open_account).",inputSchema:{type:"object",properties:{account:{type:"string"}}},handler:async({input})=>post(server,"/list-shares",{account:input.account})},{name:"storage_open_share",description:"Open one Azure File share directory on the canvas and list its files and directories live (read-only). Pass the exact share `name` from storage_list_shares and optional share-relative dir; omit dir for root. Requires an account to be open first.",inputSchema:{type:"object",properties:{share:{type:"string"},account:{type:"string"},dir:{type:"string"}},required:["share"]},handler:async({input})=>post(server,"/open-share",{share:input.share,account:input.account,dir:input.dir||""})},{name:"log_message",description:"Append a message to the canvas activity log visible to the user.",inputSchema:{type:"object",properties:{message:{type:"string"}},required:["message"]},handler:({input})=>(server.log(input.message),{ok:!0})}],open(){return server.loadSubscriptions().catch(()=>{}),metrics.trackCanvasOpened("panel"),{url:`http://127.0.0.1:${server.port}`,title:"Azure Storage",status:"ready"}},async onClose(){server.close?.(),await metrics.close()}});session=await joinSession({canvases:[canvas]}); diff --git a/canvases/azure-storage/com.github.copilot/extensions/azure-storage/studio-package.json b/canvases/azure-storage/com.github.copilot/extensions/azure-storage/studio-package.json index 3a1cce6..06f8f87 100644 --- a/canvases/azure-storage/com.github.copilot/extensions/azure-storage/studio-package.json +++ b/canvases/azure-storage/com.github.copilot/extensions/azure-storage/studio-package.json @@ -1,4 +1,4 @@ { "name": "azure-storage", - "version": "0.3.2" + "version": "0.3.3" } diff --git a/canvases/azure-storage/package.json b/canvases/azure-storage/package.json index 0b23126..dcde060 100644 --- a/canvases/azure-storage/package.json +++ b/canvases/azure-storage/package.json @@ -1,6 +1,6 @@ { "name": "azure-storage", - "version": "0.3.2", + "version": "0.3.3", "private": true, "type": "module", "main": "com.github.copilot/extensions/azure-storage/extension.mjs", diff --git a/canvases/azure-storage/release.json b/canvases/azure-storage/release.json index 4665812..fb1bd7d 100644 --- a/canvases/azure-storage/release.json +++ b/canvases/azure-storage/release.json @@ -2,7 +2,7 @@ "schemaVersion": 2, "format": "plugin", "name": "azure-storage", - "version": "0.3.2", + "version": "0.3.3", "mutableDocumentation": true, "publicationRepository": "microsoft/azure-dev-tools", "plugin": { @@ -49,8 +49,8 @@ }, "build": { "esbuild": "0.25.12", - "lockSha256": "2e1e8312ac618fc822ed8ae6f6c836b0a8939417db3b291459d9bacb9f3abcf7", - "inputSha256": "96237f35544ee31a4069222b7c14e09354250668be0bcd9999e96cb63f558f5b" + "lockSha256": "58506251f648905700430510e8bb76f1151e345d4ab5bfeb2d49a429039b2469", + "inputSha256": "a232022935bd48209a2f787d73b5d5386b2be678eb084b4ec05d3aa63fd51438" }, "dependencies": [ { @@ -153,11 +153,21 @@ "version": "12.32.0", "license": "MIT" }, + { + "name": "@cloud-foundation/azure-storage-core", + "version": "0.1.0", + "license": null + }, { "name": "@cloud-foundation/studio-runtime", "version": "0.1.0", "license": null }, + { + "name": "@microsoft/canvas-toolkit", + "version": "0.1.0", + "license": "MIT" + }, { "name": "@typespec/ts-http-runtime", "version": "0.3.9", From 63e67a0f9be99d8c5e36796d9599ba91dad5afd5 Mon Sep 17 00:00:00 2001 From: Craig Date: Mon, 26 Oct 2026 13:39:25 -0700 Subject: [PATCH 2/2] Correct Storage identity and independent release verification Keep historical immutable anchors and protected payload checks; derive current candidate versions from the catalog and verify independently published releases against their historical baseline. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 2eadb772-b466-49f1-bcd0-2e2c59791222 --- docs/release-process.md | 10 +++++ scripts/verify-plugin-marketplace.mjs | 47 ++++++------------------ test/fixtures/marketplace.candidate.json | 11 +----- test/plugin-marketplace.test.mjs | 24 +++++++----- test/storage-release.test.mjs | 14 ++++--- 5 files changed, 45 insertions(+), 61 deletions(-) diff --git a/docs/release-process.md b/docs/release-process.md index bbac4fb..4ea4f89 100644 --- a/docs/release-process.md +++ b/docs/release-process.md @@ -39,6 +39,16 @@ The verifier compares all protected files on the default branch to those tags, including additions and deletions, and pins each original tag to its reviewed commit. Do not rewrite or move old receipts, tags, or versions. +Storage's canonical product identity is `azure-storage`, with its package under +`canvases/azure-storage`. The verifier uses that identity for its launcher, +extension, receipt and release tags; `azure-storage-canvas` is obsolete. +Current candidate tests derive versions from the marketplace; historical +immutable tag checks retain their original pins. +Current Hosted Skills, Resources Query and Cost Health versions may be released +independently at different production commits. Their release commits must still +descend from the original Cost Health baseline; the historical combined-release +tags retain their exact commit and ancestry checks. + A new candidate for any canvas uses the reviewed `schemaVersion: 2`, `mutableDocumentation: true` contract: `release.json` and `checksums.json` enumerate protected files only, and its `SHA256SUMS` covers diff --git a/scripts/verify-plugin-marketplace.mjs b/scripts/verify-plugin-marketplace.mjs index 5a66ef3..1655adc 100644 --- a/scripts/verify-plugin-marketplace.mjs +++ b/scripts/verify-plugin-marketplace.mjs @@ -47,13 +47,13 @@ const packages = { extensionPath: "com.github.copilot/extensions/azure-sre-agent", receipt: "canvases/azure-sre-agent/SHA256SUMS", }, - "azure-storage-canvas": { - path: "canvases/azure-storage-canvas", + "azure-storage": { + path: "canvases/azure-storage", manifest: ".github/plugin/plugin.json", - skills: ["./skills/azure-storage-canvas/"], - extension: "azure-storage-canvas", - extensionPath: "com.github.copilot/extensions/azure-storage-canvas", - receipt: "canvases/azure-storage-canvas/SHA256SUMS", + skills: ["./skills/azure-storage/"], + extension: "azure-storage", + extensionPath: "com.github.copilot/extensions/azure-storage", + receipt: "canvases/azure-storage/SHA256SUMS", }, "containers": { path: "canvases/containers", @@ -350,36 +350,11 @@ export function verifyMarketplace(manifest, { candidate = false } = {}) { } const costCommit = git("rev-parse", "azure-cost-health-check-v0-4-3-b355172^{commit}"); verifySubsequentReleaseCommit(commits[0], costCommit); - const pairedTags = pairedCanvasProducts.map((name) => - releaseTagFor(name, marketplaceVersion(manifest, name), { allowMissing: candidate })); - if (pairedTags.some(Boolean) && !pairedTags.every(Boolean)) { - throw new Error("Paired canvas releases must both be candidates or both have immutable tags"); - } - const pairedCommits = pairedTags.every(Boolean) - ? pairedTags.map((tag) => git("rev-parse", `${tag}^{commit}`)) - : []; - if (pairedCommits.length) { - const canvasCommits = pairedCommits; - if (new Set(canvasCommits).size !== 1) { - throw new Error("New canvas release tags must point to the same reviewed merge commit"); - } - verifySubsequentReleaseCommit(costCommit, canvasCommits[0]); - } - const currentCostTag = releaseTagFor( - "azure-cost-health-check", - marketplaceVersion(manifest, "azure-cost-health-check"), - { allowMissing: candidate }, - ); - if (currentCostTag) { - if (!pairedCommits.length) { - throw new Error("Cost Health cannot be published before the paired canvas releases"); - } - verifySubsequentReleaseCommit( - pairedCommits[0], - git("rev-parse", `${currentCostTag}^{commit}`), - ); - } else if (pairedCommits.length) { - verifySubsequentReleaseCommit(pairedCommits[0], "HEAD"); + for (const name of [...pairedCanvasProducts, "azure-cost-health-check"]) { + const tag = releaseTagFor(name, marketplaceVersion(manifest, name), + { allowMissing: candidate }); + const commit = tag ? git("rev-parse", `${tag}^{commit}`) : git("rev-parse", "HEAD"); + if (commit !== costCommit) verifySubsequentReleaseCommit(costCommit, commit); } } return results; diff --git a/test/fixtures/marketplace.candidate.json b/test/fixtures/marketplace.candidate.json index a309481..04e69a6 100644 --- a/test/fixtures/marketplace.candidate.json +++ b/test/fixtures/marketplace.candidate.json @@ -6,37 +6,30 @@ "plugins": [ { "name": "azure-functions-hosted-skills", - "version": "0.5.5", "source": "canvases/azure-functions-hosted-skills" }, { "name": "azure-resources-query", - "version": "0.1.6", "source": "canvases/azure-resources-query" }, { "name": "canvas-authoring", - "version": "0.1.2", "source": "plugins/canvas-authoring" }, { "name": "azure-cost-health-check", - "version": "0.4.5", "source": "canvases/azure-cost-health-check" }, { "name": "azure-sre-agent", - "version": "0.2.7", "source": "canvases/azure-sre-agent" }, { - "name": "azure-storage-canvas", - "version": "0.1.1", - "source": "canvases/azure-storage-canvas" + "name": "azure-storage", + "source": "canvases/azure-storage" }, { "name": "containers", - "version": "0.1.1", "source": "canvases/containers" } ] diff --git a/test/plugin-marketplace.test.mjs b/test/plugin-marketplace.test.mjs index c1c6ca9..340b601 100644 --- a/test/plugin-marketplace.test.mjs +++ b/test/plugin-marketplace.test.mjs @@ -29,6 +29,7 @@ import { const fixture = JSON.parse(readFileSync(new URL("./fixtures/marketplace.candidate.json", import.meta.url))); const catalog = JSON.parse(readFileSync(new URL("../.github/plugin/marketplace.json", import.meta.url))); +fixture.plugins = fixture.plugins.map((plugin) => ({ ...plugin, version: versionOf(plugin.name) })); const root = fileURLToPath(new URL("../", import.meta.url)); const verifier = "scripts/verify-plugin-marketplace.mjs"; @@ -58,13 +59,13 @@ test("requires release tags for full verification, then checks every catalog plu assert.ok(results[0].startsWith(`azure-functions-hosted-skills@${versionOf("azure-functions-hosted-skills")} azure-functions-hosted-skills-v`)); assert.ok(results[1].startsWith(`azure-resources-query@${versionOf("azure-resources-query")} azure-resources-query-v`)); assert.ok(results[2].startsWith(`canvas-authoring@${versionOf("canvas-authoring")} canvas-authoring-v`)); - assert.match(results[3], /azure-cost-health-check@0\.4\.5 azure-cost-health-check-v0-4-5-/); - assert.match(results[4], /azure-sre-agent@0\.2\.7 azure-sre-agent-v0-2-7-/); - assert.ok(results[5].startsWith(`azure-storage-canvas@${versionOf("azure-storage-canvas")} azure-storage-canvas-v`)); - assert.equal(results[6], "containers@0.1.1 (candidate; immutable tag pending)"); + assert.ok(results[3].startsWith(`azure-cost-health-check@${versionOf("azure-cost-health-check")} azure-cost-health-check-v`)); + assert.ok(results[4].startsWith(`azure-sre-agent@${versionOf("azure-sre-agent")} azure-sre-agent-v`)); + assert.ok(results[5].startsWith(`azure-storage@${versionOf("azure-storage")} azure-storage-v`)); + assert.ok(results[6].startsWith(`containers@${versionOf("containers")} containers-v`)); }); -test("candidate validates every reviewed package and omits only missing immutable tags", () => { +test("candidate validates independently released products and omits only missing immutable tags", () => { const candidate = modified((m) => { m.name = "azure-dev-tools"; }); const results = verifyMarketplace(candidate, { candidate: true }); assert.equal(results.length, candidate.plugins.length); @@ -75,8 +76,8 @@ test("candidate validates every reviewed package and omits only missing immutabl assert.equal(results[index], `${name}@${version} ${tags[0] ?? "(candidate; immutable tag pending)"}`); } assert.ok(results[2].startsWith(`canvas-authoring@${versionOf("canvas-authoring")} `)); - assert.match(results[4], /azure-sre-agent@0\.2\.7 azure-sre-agent-v0-2-7-9da2e40/); - assert.equal(results[6], "containers@0.1.1 (candidate; immutable tag pending)"); + assert.ok(results[4].startsWith(`azure-sre-agent@${versionOf("azure-sre-agent")} `)); + assert.ok(results[6].startsWith(`containers@${versionOf("containers")} `)); }); test("candidate rejects corrupted protected payloads and checksum receipts", () => { @@ -204,7 +205,9 @@ test("canvas-authoring uses versioned receipts without changing historical recei assert.equal(git("show", `HEAD:${historical}`), git("show", `canvas-authoring-v0-1-1-8af10f8:${historical}`)); assert.doesNotThrow(verify); - assert.throws(() => verify(false), /expected exactly one reviewed immutable release tag/); + const currentTag = git("tag", "-l", `canvas-authoring-v${product.version.replaceAll(".", "-")}-*`); + if (currentTag) assert.doesNotThrow(() => verify(false)); + else assert.throws(() => verify(false), /expected exactly one reviewed immutable release tag/); const receipt = `docs/canvas-authoring/${product.version}/SHA256SUMS`; const original = readFileSync(join(clone, receipt), "utf8"); @@ -212,12 +215,12 @@ test("canvas-authoring uses versioned receipts without changing historical recei !line.endsWith(" com.github.copilot/extensions/canvas-authoring/extension.mjs")).join("\n")); git("add", "--", receipt); git("commit", "--quiet", "-m", "Remove authoring runtime receipt"); - assert.throws(verify, /checksum receipt must cover every protected plugin file exactly once/); + assert.throws(verify, /current checksum receipt differs|checksum receipt must cover every protected plugin file exactly once/); writeFileSync(join(clone, receipt), original.replace( /^[0-9a-f]{64}(?= plugin.json$)/m, "0".repeat(64))); git("add", "--", receipt); git("commit", "--quiet", "-m", "Corrupt authoring manifest receipt"); - assert.throws(verify, /plugin file differs from checksum receipt: plugin.json/); + assert.throws(verify, /current checksum receipt differs|plugin file differs from checksum receipt: plugin.json/); writeFileSync(join(clone, receipt), original); const parts = product.version.split(".").map(Number); @@ -236,6 +239,7 @@ test("canvas-authoring uses versioned receipts without changing historical recei git("add", "--", manifestPath, "docs/canvas-authoring"); git("commit", "--quiet", "-m", "Prepare next authoring receipt"); assert.doesNotThrow(verify); + assert.throws(() => verify(false), /expected exactly one reviewed immutable release tag/); } finally { rmSync(checkout, { recursive: true, force: true }); } diff --git a/test/storage-release.test.mjs b/test/storage-release.test.mjs index bf59aa6..1bd7647 100644 --- a/test/storage-release.test.mjs +++ b/test/storage-release.test.mjs @@ -1,6 +1,6 @@ import assert from "node:assert/strict"; import { execFileSync } from "node:child_process"; -import { mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; +import { copyFileSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { fileURLToPath } from "node:url"; @@ -12,7 +12,7 @@ import { verifyTagSource, } from "../scripts/verify-plugin-marketplace.mjs"; -const name = "azure-storage-canvas"; +const name = "azure-storage"; const source = `canvases/${name}`; const catalog = JSON.parse(readFileSync( new URL("../.github/plugin/marketplace.json", import.meta.url))); @@ -31,13 +31,13 @@ test("Storage accepts reviewed semantic versions without a fixed version gate", }); test("Storage rejects obsolete identities and noncanonical package sources", () => { - for (const obsolete of ["azure-storage", "azure-storage-explorer"]) { + for (const obsolete of ["azure-storage-canvas", "azure-storage-explorer"]) { assert.throws(() => verifyCurrentVersion(obsolete, "0.1.1"), /expected a reviewed product/); } for (const wrongSource of [ - "plugins/azure-storage-canvas", - "canvases/azure-storage-canvas/src", + `plugins/${name}`, + `${source}/src`, `${source}/com.github.copilot/extensions/${name}`, "canvases/azure-resources-query", { source: "github", repo: "microsoft/azure-dev-tools", path: source, ref: "main" }, @@ -81,7 +81,7 @@ test("the Storage candidate verifies its canonical plugin and protected receipt" const plugin = catalog.plugins.find((entry) => entry.name === name); assert.ok(plugin, "Storage must have a production marketplace entry"); assert.match(verifyPlugin(plugin, { candidate: true }), - /^azure-storage-canvas@\d+\.\d+\.\d+ /); + /^azure-storage@\d+\.\d+\.\d+ /); }); test("Storage rejects changes to its protected runtime, launcher, notices, icon and receipt", () => { @@ -101,6 +101,8 @@ test("Storage rejects changes to its protected runtime, launcher, notices, icon }; try { execFileSync("git", ["clone", "--quiet", "--local", "--no-hardlinks", root, clone]); + copyFileSync(join(root, "scripts/verify-plugin-marketplace.mjs"), + join(clone, "scripts/verify-plugin-marketplace.mjs")); git("config", "user.name", "Storage release policy test"); git("config", "user.email", "release-policy@example.invalid"); assert.doesNotThrow(verify);