@opencode-ai/client
Advanced tools
| export * as PtyHandoff from "./pty-handoff.js"; | ||
| import type { Info } from "./service.js"; | ||
| /** Publish the ticket before stopping its owner so every replacement contender can adopt it. */ | ||
| export declare function prepare(file: string, info: Info, timeout: number): Promise<void>; | ||
| export declare function environment(file: string, env?: Readonly<Record<string, string>>): Promise<{ | ||
| OPENCODE_PTY_HANDOFF: string | undefined; | ||
| }>; | ||
| export declare function complete(file: string, info: Info): Promise<void>; | ||
| export declare function clear(file: string): Promise<void>; |
| export * as PtyHandoff from "./pty-handoff.js"; | ||
| import { readFile, rename, rm, writeFile } from "node:fs/promises"; | ||
| /** Publish the ticket before stopping its owner so every replacement contender can adopt it. */ | ||
| export async function prepare(file, info, timeout) { | ||
| const existing = await read(file); | ||
| if (existing !== undefined && existing.expiresAt > Date.now() && same(existing.source, info)) | ||
| return; | ||
| const { ClientError, OpenCode } = await import("./promise/index.js"); | ||
| const client = OpenCode.make({ | ||
| baseUrl: info.url, | ||
| headers: info.password === undefined | ||
| ? undefined | ||
| : { authorization: "Basic " + Buffer.from(`opencode:${info.password}`).toString("base64") }, | ||
| }); | ||
| const missing = (error) => error instanceof ClientError && | ||
| error.reason === "UnexpectedStatus" && | ||
| typeof error.cause === "object" && | ||
| error.cause !== null && | ||
| "status" in error.cause && | ||
| error.cause.status === 404; | ||
| const result = await client.experimental.persistentPty.handoff({ signal: AbortSignal.timeout(timeout) }).then((value) => ({ value }), (cause) => ({ cause })); | ||
| if ("cause" in result) { | ||
| // Another caller may already have prepared and stopped this server. | ||
| const concurrent = await read(file); | ||
| if (concurrent !== undefined && concurrent.expiresAt > Date.now() && same(concurrent.source, info)) | ||
| return; | ||
| if (!missing(result.cause)) | ||
| throw new Error("Failed to prepare persistent terminals for service replacement", { cause: result.cause }); | ||
| console.warn("Background service cannot hand off persistent terminals; shutting them down before replacement"); | ||
| await client.experimental.persistentPty | ||
| .shutdown({ signal: AbortSignal.timeout(timeout) }) | ||
| .catch((cause) => { | ||
| if (missing(cause)) | ||
| return; | ||
| throw new Error("Failed to shut down persistent terminals before service replacement", { cause }); | ||
| }); | ||
| await publish(file, info, null); | ||
| return; | ||
| } | ||
| const body = result.value; | ||
| if (typeof body !== "object" || body === null || !("handoff" in body)) | ||
| throw new Error("Invalid persistent terminal handoff response"); | ||
| if (body.handoff === null) { | ||
| await publish(file, info, null); | ||
| return; | ||
| } | ||
| if (!isHandoff(body.handoff) || body.handoff.expiresAt <= Date.now()) | ||
| throw new Error("Invalid or expired persistent terminal handoff"); | ||
| await publish(file, info, body.handoff); | ||
| } | ||
| async function publish(file, info, handoff) { | ||
| const temporary = `${file}.pty-handoff.${crypto.randomUUID()}.tmp`; | ||
| await writeFile(temporary, JSON.stringify({ | ||
| source: { id: info.id, pid: info.pid, url: info.url }, | ||
| handoff, | ||
| expiresAt: handoff?.expiresAt ?? Date.now() + 30_000, | ||
| }), { mode: 0o600, flag: "wx" }); | ||
| await rename(temporary, file + ".pty-handoff").finally(() => rm(temporary, { force: true })); | ||
| } | ||
| export async function environment(file, env) { | ||
| const record = await read(file); | ||
| const current = await readFile(file, "utf8") | ||
| .then((text) => JSON.parse(text)) | ||
| .catch(() => undefined); | ||
| const handoff = record !== undefined && record.expiresAt > Date.now() && (current === undefined || same(record.source, current)) | ||
| ? record.handoff | ||
| : undefined; | ||
| return { ...env, OPENCODE_PTY_HANDOFF: handoff == null ? undefined : JSON.stringify(handoff) }; | ||
| } | ||
| export async function complete(file, info) { | ||
| const record = await read(file); | ||
| if (record !== undefined && !same(record.source, info)) | ||
| await clear(file); | ||
| } | ||
| export async function clear(file) { | ||
| await rm(file + ".pty-handoff", { force: true }); | ||
| } | ||
| async function read(file) { | ||
| const value = await readFile(file + ".pty-handoff", "utf8") | ||
| .then((text) => JSON.parse(text)) | ||
| .catch(() => undefined); | ||
| if (typeof value !== "object" || value === null || !("source" in value) || !("handoff" in value)) | ||
| return; | ||
| if (typeof value.source !== "object" || value.source === null) | ||
| return; | ||
| if (!("pid" in value.source) || typeof value.source.pid !== "number") | ||
| return; | ||
| if (!("url" in value.source) || typeof value.source.url !== "string") | ||
| return; | ||
| if ("id" in value.source && typeof value.source.id !== "string") | ||
| return; | ||
| if (value.handoff !== null && !isHandoff(value.handoff)) | ||
| return; | ||
| if (!("expiresAt" in value) || typeof value.expiresAt !== "number" || !Number.isFinite(value.expiresAt)) | ||
| return; | ||
| return { | ||
| source: { | ||
| id: "id" in value.source && typeof value.source.id === "string" ? value.source.id : undefined, | ||
| pid: value.source.pid, | ||
| url: value.source.url, | ||
| }, | ||
| handoff: value.handoff, | ||
| expiresAt: value.expiresAt, | ||
| }; | ||
| } | ||
| function same(left, right) { | ||
| return left.id === right.id && left.pid === right.pid && left.url === right.url; | ||
| } | ||
| function isHandoff(value) { | ||
| return (typeof value === "object" && | ||
| value !== null && | ||
| "directory" in value && | ||
| typeof value.directory === "string" && | ||
| "instanceID" in value && | ||
| typeof value.instanceID === "string" && | ||
| "ticket" in value && | ||
| typeof value.ticket === "string" && | ||
| "expiresAt" in value && | ||
| typeof value.expiresAt === "number" && | ||
| Number.isFinite(value.expiresAt)); | ||
| } |
@@ -438,2 +438,3 @@ // Generated by @opencode-ai/httpapi-codegen. Do not edit. | ||
| const EndpointExperimentalPersistentPtyShutdown = (raw) => () => preserveEffect()(raw["persistentPty.shutdown"]({}).pipe(Effect.mapError(mapClientError))); | ||
| const EndpointExperimentalPersistentPtyHandoff = (raw) => () => preserveEffect()(raw["persistentPty.handoff"]({}).pipe(Effect.mapError(mapClientError))); | ||
| const EndpointExperimentalPersistentPtyGet = (raw) => (input) => preserveEffect()(raw["persistentPty.get"]({ params: { ptyID: input["ptyID"] } }).pipe(Effect.mapError(mapClientError), Effect.map((value) => value.data))); | ||
@@ -455,2 +456,3 @@ const EndpointExperimentalPersistentPtyUpdate = (raw) => (input) => preserveEffect()(raw["persistentPty.update"]({ | ||
| shutdown: EndpointExperimentalPersistentPtyShutdown(raw), | ||
| handoff: EndpointExperimentalPersistentPtyHandoff(raw), | ||
| get: EndpointExperimentalPersistentPtyGet(raw), | ||
@@ -457,0 +459,0 @@ update: EndpointExperimentalPersistentPtyUpdate(raw), |
@@ -8,2 +8,3 @@ import { ServiceStatus } from "@opencode-ai/protocol/groups/health"; | ||
| import { matchesVersion } from "../service-version.js"; | ||
| import { PtyHandoff } from "../pty-handoff.js"; | ||
| export * from "../service.js"; | ||
@@ -59,5 +60,6 @@ // Find, start, and stop the local opencode background service. | ||
| return yield* Effect.fail(new Error("Missing service command")); | ||
| const env = yield* Effect.tryPromise(() => PtyHandoff.environment(options.file ?? fallback(), options.env)); | ||
| return yield* Effect.try({ | ||
| try: () => { | ||
| return spawnServiceContender(command, args, options.env); | ||
| return spawnServiceContender(command, args, env); | ||
| }, | ||
@@ -78,2 +80,4 @@ catch: (cause) => new Error("Failed to start server", { cause }), | ||
| yield* announce("missing"); | ||
| yield* Effect.logWarning("Background service is unresponsive; recovery cannot preserve persistent terminals"); | ||
| yield* Effect.tryPromise(() => PtyHandoff.clear(options.file ?? fallback())); | ||
| yield* terminate(info, options, timing); | ||
@@ -89,4 +93,6 @@ timeouts = undefined; | ||
| const compatible = !service.legacy && matchesVersion(service.version, options); | ||
| if (compatible && service.state === "ready") | ||
| if (compatible && service.state === "ready") { | ||
| yield* Effect.tryPromise(() => PtyHandoff.complete(options.file ?? fallback(), service.info)); | ||
| return Option.some(service); | ||
| } | ||
| if (compatible && service.state === "failed") | ||
@@ -97,2 +103,9 @@ return yield* Effect.fail(new Error("Background service failed to start")); | ||
| yield* announce("version-mismatch", service.version); | ||
| if (!service.legacy && service.state === "ready") | ||
| yield* Effect.tryPromise(() => PtyHandoff.prepare(options.file ?? fallback(), service.info, timing.requestTimeout)); | ||
| else { | ||
| if (!service.legacy) | ||
| yield* Effect.logWarning("Background service is not ready; replacement cannot preserve persistent terminals"); | ||
| yield* Effect.tryPromise(() => PtyHandoff.clear(options.file ?? fallback())); | ||
| } | ||
| yield* terminate(service.info, options, timing).pipe(Effect.ignore); | ||
@@ -129,2 +142,3 @@ lastSpawn = 0; | ||
| export const stop = Effect.fn("service.stop")(function* (options = {}) { | ||
| yield* Effect.tryPromise(() => PtyHandoff.clear(options.file ?? fallback())); | ||
| const info = yield* read(options.file); | ||
@@ -131,0 +145,0 @@ if (info !== undefined) |
@@ -1,2 +0,2 @@ | ||
| import type { ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, AgentGetInput, AgentGetOutput, PluginListInput, PluginListOutput, SessionListInput, SessionStatsInput, SessionCreateInput, SessionImportInput, SessionExportInput, SessionGetInput, SessionRemoveInput, SessionForkInput, SessionSwitchAgentInput, SessionSwitchModelInput, SessionRenameInput, SessionMoveInput, SessionPromptInput, SessionCommandInput, SessionSkillInput, SessionSyntheticInput, SessionShellInput, SessionCompactInput, SessionWaitInput, SessionRevertStageInput, SessionRevertClearInput, SessionRevertCommitInput, SessionContextInput, SessionInboxListInput, SessionInboxCancelInput, SessionInboxSteerInput, SessionInboxQueueInput, SessionInstructionsEntryListInput, SessionInstructionsEntryPutInput, SessionInstructionsEntryRemoveInput, SessionGenerateInput, SessionLogInput, SessionLogOutput, SessionInterruptInput, SessionBackgroundInput, SessionMessageInput, SessionMessageUpdateInput, SessionEnvironmentInput, SessionViewInput, MessageListInput, ModelListInput, ModelListOutput, ModelDefaultInput, ModelDefaultOutput, GenerateTextInput, ProviderListInput, ProviderListOutput, ProviderGetInput, ProviderGetOutput, IntegrationListInput, IntegrationListOutput, IntegrationGetInput, IntegrationGetOutput, IntegrationWellknownAddInput, IntegrationConnectKeyInput, IntegrationOauthConnectInput, IntegrationOauthConnectOutput, IntegrationOauthStatusInput, IntegrationOauthStatusOutput, IntegrationOauthCompleteInput, IntegrationOauthCancelInput, IntegrationCommandConnectInput, IntegrationCommandConnectOutput, IntegrationCommandStatusInput, IntegrationCommandStatusOutput, IntegrationCommandCancelInput, McpListInput, McpListOutput, McpAddInput, McpRemoveInput, McpConnectInput, McpDisconnectInput, McpResourceCatalogInput, McpResourceCatalogOutput, CredentialUpdateInput, CredentialActivateInput, CredentialRemoveInput, ProjectListOutput, ProjectUpdateInput, ProjectCurrentInput, FormRequestListInput, FormRequestListOutput, FormListInput, FormCreateInput, FormGetInput, FormStateInput, FormReplyInput, FormCancelInput, PermissionRequestListInput, PermissionRequestListOutput, PermissionSavedListInput, PermissionSavedRemoveInput, PermissionCreateInput, PermissionListInput, PermissionGetInput, PermissionReplyInput, FileReadInput, FileReadOutput, FileListInput, FileListOutput, FileFindInput, FileFindOutput, CommandListInput, CommandListOutput, SkillListInput, SkillListOutput, EventSubscribeOutput, PtyListInput, PtyListOutput, PtyCreateInput, PtyCreateOutput, PtyGetInput, PtyGetOutput, PtyUpdateInput, PtyUpdateOutput, PtyRemoveInput, PtyConnectTokenInput, PtyConnectTokenOutput, ExperimentalPersistentPtyListInput, ExperimentalPersistentPtyCreateInput, ExperimentalPersistentPtyGetInput, ExperimentalPersistentPtyUpdateInput, ExperimentalPersistentPtySnapshotInput, ExperimentalPersistentPtyRemoveInput, ExperimentalPersistentPtyConnectTokenInput, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, ReferenceListInput, ReferenceListOutput, WorktreeListInput, WorktreeCreateInput, WorktreeRemoveInput, WorktreeRefreshInput, WorkspaceCreateInput, WorkspaceDestroyInput, VcsGetInput, VcsGetOutput, VcsStatusInput, VcsStatusOutput, VcsBranchesInput, VcsBranchesOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput, MigrationV1StatusOutput, WebsearchProvidersInput, WebsearchProvidersOutput, WebsearchQueryInput, WebsearchQueryOutput, ConfigGetInput, ConfigGetOutput } from "./types.js"; | ||
| import type { ServerGetOutput, LocationGetInput, LocationGetOutput, AgentListInput, AgentListOutput, AgentGetInput, AgentGetOutput, PluginListInput, PluginListOutput, SessionListInput, SessionStatsInput, SessionCreateInput, SessionImportInput, SessionExportInput, SessionGetInput, SessionRemoveInput, SessionForkInput, SessionSwitchAgentInput, SessionSwitchModelInput, SessionRenameInput, SessionMoveInput, SessionPromptInput, SessionCommandInput, SessionSkillInput, SessionSyntheticInput, SessionShellInput, SessionCompactInput, SessionWaitInput, SessionRevertStageInput, SessionRevertClearInput, SessionRevertCommitInput, SessionContextInput, SessionInboxListInput, SessionInboxCancelInput, SessionInboxSteerInput, SessionInboxQueueInput, SessionInstructionsEntryListInput, SessionInstructionsEntryPutInput, SessionInstructionsEntryRemoveInput, SessionGenerateInput, SessionLogInput, SessionLogOutput, SessionInterruptInput, SessionBackgroundInput, SessionMessageInput, SessionMessageUpdateInput, SessionEnvironmentInput, SessionViewInput, MessageListInput, ModelListInput, ModelListOutput, ModelDefaultInput, ModelDefaultOutput, GenerateTextInput, ProviderListInput, ProviderListOutput, ProviderGetInput, ProviderGetOutput, IntegrationListInput, IntegrationListOutput, IntegrationGetInput, IntegrationGetOutput, IntegrationWellknownAddInput, IntegrationConnectKeyInput, IntegrationOauthConnectInput, IntegrationOauthConnectOutput, IntegrationOauthStatusInput, IntegrationOauthStatusOutput, IntegrationOauthCompleteInput, IntegrationOauthCancelInput, IntegrationCommandConnectInput, IntegrationCommandConnectOutput, IntegrationCommandStatusInput, IntegrationCommandStatusOutput, IntegrationCommandCancelInput, McpListInput, McpListOutput, McpAddInput, McpRemoveInput, McpConnectInput, McpDisconnectInput, McpResourceCatalogInput, McpResourceCatalogOutput, CredentialUpdateInput, CredentialActivateInput, CredentialRemoveInput, ProjectListOutput, ProjectUpdateInput, ProjectCurrentInput, FormRequestListInput, FormRequestListOutput, FormListInput, FormCreateInput, FormGetInput, FormStateInput, FormReplyInput, FormCancelInput, PermissionRequestListInput, PermissionRequestListOutput, PermissionSavedListInput, PermissionSavedRemoveInput, PermissionCreateInput, PermissionListInput, PermissionGetInput, PermissionReplyInput, FileReadInput, FileReadOutput, FileListInput, FileListOutput, FileFindInput, FileFindOutput, CommandListInput, CommandListOutput, SkillListInput, SkillListOutput, EventSubscribeOutput, PtyListInput, PtyListOutput, PtyCreateInput, PtyCreateOutput, PtyGetInput, PtyGetOutput, PtyUpdateInput, PtyUpdateOutput, PtyRemoveInput, PtyConnectTokenInput, PtyConnectTokenOutput, ExperimentalPersistentPtyListInput, ExperimentalPersistentPtyCreateInput, ExperimentalPersistentPtyHandoffOutput, ExperimentalPersistentPtyGetInput, ExperimentalPersistentPtyUpdateInput, ExperimentalPersistentPtySnapshotInput, ExperimentalPersistentPtyRemoveInput, ExperimentalPersistentPtyConnectTokenInput, ShellListInput, ShellListOutput, ShellCreateInput, ShellCreateOutput, ShellGetInput, ShellGetOutput, ShellTimeoutInput, ShellTimeoutOutput, ShellOutputInput, ShellOutputOutput, ShellRemoveInput, ReferenceListInput, ReferenceListOutput, WorktreeListInput, WorktreeCreateInput, WorktreeRemoveInput, WorktreeRefreshInput, WorkspaceCreateInput, WorkspaceDestroyInput, VcsGetInput, VcsGetOutput, VcsStatusInput, VcsStatusOutput, VcsBranchesInput, VcsBranchesOutput, VcsDiffInput, VcsDiffOutput, DebugLocationListOutput, DebugLocationEvictInput, MigrationV1StatusOutput, WebsearchProvidersInput, WebsearchProvidersOutput, WebsearchQueryInput, WebsearchQueryOutput, ConfigGetInput, ConfigGetOutput } from "./types.js"; | ||
| export interface ClientOptions { | ||
@@ -201,2 +201,3 @@ readonly baseUrl: string; | ||
| shutdown: (requestOptions?: RequestOptions) => Promise<void>; | ||
| handoff: (requestOptions?: RequestOptions) => Promise<ExperimentalPersistentPtyHandoffOutput>; | ||
| get: (input: ExperimentalPersistentPtyGetInput, requestOptions?: RequestOptions) => Promise<import("./types.js").PersistentPtyInfo>; | ||
@@ -203,0 +204,0 @@ update: (input: ExperimentalPersistentPtyUpdateInput, requestOptions?: RequestOptions) => Promise<import("./types.js").PersistentPtyInfo>; |
@@ -226,3 +226,3 @@ import { ClientError } from "./client-error.js"; | ||
| successStatus: 200, | ||
| declaredStatuses: [409, 401, 400], | ||
| declaredStatuses: [409, 404, 401, 400], | ||
| empty: false, | ||
@@ -1050,2 +1050,9 @@ }, requestOptions).then((value) => value.data), | ||
| }, requestOptions), | ||
| handoff: (requestOptions) => request({ | ||
| method: "POST", | ||
| path: `/api/experimental/persistent-pty/handoff`, | ||
| successStatus: 200, | ||
| declaredStatuses: [503, 401, 400], | ||
| empty: false, | ||
| }, requestOptions), | ||
| get: (input, requestOptions) => request({ | ||
@@ -1052,0 +1059,0 @@ method: "GET", |
@@ -7,2 +7,3 @@ import { readFile, rm } from "node:fs/promises"; | ||
| import { matchesVersion } from "../service-version.js"; | ||
| import { PtyHandoff } from "../pty-handoff.js"; | ||
| export * from "../service.js"; | ||
@@ -38,3 +39,3 @@ // Find, start, and stop the local opencode background service. | ||
| }; | ||
| const spawnContender = () => { | ||
| const spawnContender = async () => { | ||
| const [command, ...args] = options.command ?? ["opencode", "serve", "--service"]; | ||
@@ -44,3 +45,3 @@ if (command === undefined) | ||
| try { | ||
| return spawnServiceContender(command, args, options.env); | ||
| return spawnServiceContender(command, args, await PtyHandoff.environment(options.file ?? fallback(), options.env)); | ||
| } | ||
@@ -63,2 +64,4 @@ catch (cause) { | ||
| announce("missing"); | ||
| console.warn("Background service is unresponsive; recovery cannot preserve persistent terminals"); | ||
| await PtyHandoff.clear(options.file ?? fallback()); | ||
| await terminate(registration.info, options, timing); | ||
@@ -75,4 +78,6 @@ timeouts = undefined; | ||
| const compatible = !service.legacy && matchesVersion(service.version, options); | ||
| if (compatible && service.state === "ready") | ||
| if (compatible && service.state === "ready") { | ||
| await PtyHandoff.complete(options.file ?? fallback(), service.info); | ||
| return service.endpoint; | ||
| } | ||
| if (compatible && service.state === "failed") | ||
@@ -82,2 +87,9 @@ throw new Error("Background service failed to start"); | ||
| announce("version-mismatch", service.version); | ||
| if (!service.legacy && service.state === "ready") | ||
| await PtyHandoff.prepare(options.file ?? fallback(), service.info, timing.requestTimeout); | ||
| else { | ||
| if (!service.legacy) | ||
| console.warn("Background service is not ready; replacement cannot preserve persistent terminals"); | ||
| await PtyHandoff.clear(options.file ?? fallback()); | ||
| } | ||
| await terminate(service.info, options, timing).catch(() => undefined); | ||
@@ -101,3 +113,3 @@ lastSpawn = 0; | ||
| announce("missing"); | ||
| contenders.add(spawnContender()); | ||
| contenders.add(await spawnContender()); | ||
| lastSpawn = Date.now(); | ||
@@ -115,2 +127,3 @@ } | ||
| export async function stop(options = {}) { | ||
| await PtyHandoff.clear(options.file ?? fallback()); | ||
| const info = await read(options.file); | ||
@@ -117,0 +130,0 @@ if (info !== undefined) |
@@ -9,4 +9,4 @@ import { type ChildProcess } from "node:child_process"; | ||
| }; | ||
| export declare function spawnServiceContender(command: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string>>): ServiceContender; | ||
| export declare function spawnServiceContender(command: string, args: ReadonlyArray<string>, env?: Readonly<Record<string, string | undefined>>): ServiceContender; | ||
| export declare function contenderFailure(contender: ServiceContender): Error | undefined; | ||
| export declare function contenderFinished(contender: ServiceContender): boolean; |
+4
-4
| { | ||
| "$schema": "https://json.schemastore.org/package.json", | ||
| "name": "@opencode-ai/client", | ||
| "version": "0.0.0-beta-18387", | ||
| "version": "0.0.0-beta-18414", | ||
| "type": "module", | ||
@@ -60,4 +60,4 @@ "license": "MIT", | ||
| "dependencies": { | ||
| "@opencode-ai/schema": "0.0.0-beta-18387", | ||
| "@opencode-ai/protocol": "0.0.0-beta-18387" | ||
| "@opencode-ai/schema": "0.0.0-beta-18414", | ||
| "@opencode-ai/protocol": "0.0.0-beta-18414" | ||
| }, | ||
@@ -78,3 +78,3 @@ "peerDependencies": { | ||
| "@effect/platform-node": "4.0.0-rc.111", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-beta-18387", | ||
| "@opencode-ai/httpapi-codegen": "0.0.0-beta-18414", | ||
| "@tsconfig/bun": "1.0.9", | ||
@@ -81,0 +81,0 @@ "@types/bun": "1.3.13", |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
791997
1.27%50
4.17%18999
1%6
20%+ Added
+ Added
- Removed
- Removed