glimmervoid 0.36.0 → 0.37.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -84
- package/dist/bin/glimmervoid.js +54 -115
- package/dist/chunks/{adapters-BdnLyJjf.js → adapters-BmfVPxlH.js} +22 -15
- package/dist/chunks/adapters-CqFFmM2R.js +1 -0
- package/dist/chunks/{agent-api-cli-DIqRLvu3.js → agent-api-cli-BTHEyJKS.js} +1 -1
- package/dist/chunks/{agent-api-cli-d-Bgsshx.js → agent-api-cli-CBgOKXoO.js} +41 -12
- package/dist/chunks/{agent-api-core-BUC9WND7.js → agent-api-core-DzKhakCv.js} +94 -3
- package/dist/chunks/{agent-setup-cli-CH7EHsAX.js → agent-setup-cli-C8DGxlA-.js} +1 -1
- package/dist/chunks/{config-CuZ16F9W.js → config-BYc2z1Oc.js} +15 -10
- package/dist/chunks/{config-store-LqQ7MuyV.js → config-store-B7MpiYxG.js} +7 -7
- package/dist/chunks/config-store-CsUoy88Q.js +1 -0
- package/dist/chunks/{contracts-BLZAOP57.js → contracts-C6ipN_zw.js} +48 -9
- package/dist/chunks/git-version-6QFnKbWa.js +139 -0
- package/dist/chunks/{grok-Ch4-35_r.js → grok-CZ6kP_vD.js} +5 -1
- package/dist/chunks/guardrails-deps-Cb-ki4EU.js +70 -0
- package/dist/chunks/{sandbox-proxy-core-hnQgKR0T.js → guardrails-proxy-core-BI3rdY-m.js} +4 -4
- package/dist/chunks/{knowledge-graph-cli-DD7QvcFd.js → knowledge-graph-cli-DApeMzA8.js} +1 -1
- package/dist/chunks/{loopback-post-DepQfdcM.js → loopback-post-DJ6iYWuL.js} +9 -9
- package/dist/chunks/{main-Rmo83Ow-.js → main-BUfjroXE.js} +12976 -11532
- package/dist/chunks/{pair-cli-DzFeAmq5.js → pair-cli-GZ9t1mIO.js} +2 -2
- package/dist/chunks/{remote-config-CPdSF2Xi.js → remote-config-D2Is89Kj.js} +1 -1
- package/dist/chunks/{session-B8RhqQJe.js → session-BZbariv5.js} +54 -8
- package/dist/chunks/{session-routes-DB5aDKsx.js → session-routes-C4t8O8yg.js} +9 -9
- package/dist/chunks/{settings-cli-C2TY6hl-.js → settings-cli-DQOjmjev.js} +3 -3
- package/dist/chunks/{ssh-sign-cli-C2nUwJC-.js → ssh-sign-cli-LIBP8ppO.js} +2 -2
- package/dist/client/assets/index-B_Qt7fbZ.css +1 -0
- package/dist/client/assets/index-D-upBor0.js +265 -0
- package/dist/client/index.html +2 -2
- package/dist/scripts/postinstall-path-check.js +4 -4
- package/dist/server/index.js +1 -1
- package/dist/session/command-hook-relay.js +1 -1
- package/dist/session/{sandbox-open-launcher.js → guardrails-open-launcher.js} +10 -10
- package/dist/session/hook-relay.js +1 -1
- package/dist/session/statusline-relay.js +1 -1
- package/package.json +4 -3
- package/dist/chunks/adapters-BVozg-Lg.js +0 -1
- package/dist/chunks/config-store-tVmt_nfm.js +0 -1
- package/dist/chunks/git-version-CUcDtF5k.js +0 -58
- package/dist/chunks/sandbox-deps-CWiH1d1w.js +0 -70
- package/dist/client/assets/index-BIaSyXwH.css +0 -1
- package/dist/client/assets/index-Ddu7M4Ad.js +0 -259
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { r as errorMessage } from "./text-DoirglWV.js";
|
|
2
|
-
import { c as resolveConfigPath } from "./config-store-
|
|
3
|
-
import { c as defaultPairingsPath, i as REVOCATION_PROPAGATION_SECONDS, l as defaultSeenPath, n as normalizeRemoteConfig, o as createPairingsStore, s as createSeenStore } from "./remote-config-
|
|
2
|
+
import { c as resolveConfigPath } from "./config-store-B7MpiYxG.js";
|
|
3
|
+
import { c as defaultPairingsPath, i as REVOCATION_PROPAGATION_SECONDS, l as defaultSeenPath, n as normalizeRemoteConfig, o as createPairingsStore, s as createSeenStore } from "./remote-config-D2Is89Kj.js";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
//#region server/text-format.ts
|
|
6
6
|
function trimIso(iso) {
|
|
@@ -3,7 +3,7 @@ import { n as equalsIgnoringCaseOnWindows } from "./paths-Bu4eH1Yd.js";
|
|
|
3
3
|
import { n as isRecord } from "./coerce-NSst8oRP.js";
|
|
4
4
|
import { n as unrefTimer, t as DEFAULT_TIMER_FNS } from "./timer-deps-u2DAQLok.js";
|
|
5
5
|
import { a as jsonStateLoadError, d as writeJsonAtomicSync, l as sleepSync, s as loadJsonStateFileSync, u as writeJsonAtomic } from "./json-file-BaxGcB2R.js";
|
|
6
|
-
import { o as glimmervoidHomeDir, st as canonicalizePath } from "./config-store-
|
|
6
|
+
import { o as glimmervoidHomeDir, st as canonicalizePath } from "./config-store-B7MpiYxG.js";
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
9
|
import crypto from "node:crypto";
|
|
@@ -706,13 +706,23 @@ var CheckRun = z.object({
|
|
|
706
706
|
__typename: z.literal("CheckRun"),
|
|
707
707
|
name: z.string(),
|
|
708
708
|
conclusion: z.string().nullable(),
|
|
709
|
-
status: z.string()
|
|
709
|
+
status: z.string(),
|
|
710
|
+
startedAt: z.string().nullish(),
|
|
711
|
+
checkSuite: z.object({
|
|
712
|
+
app: z.object({ databaseId: z.number().int().nullish() }).nullish(),
|
|
713
|
+
workflowRun: z.object({ workflow: z.object({ databaseId: z.number().int().nullish() }) }).nullish()
|
|
714
|
+
}).nullish()
|
|
710
715
|
});
|
|
711
716
|
var StatusContext = z.object({
|
|
712
717
|
__typename: z.literal("StatusContext"),
|
|
713
718
|
context: z.string(),
|
|
714
719
|
state: z.string()
|
|
715
720
|
});
|
|
721
|
+
var MyPrCheckContext = z.union([CheckRun, StatusContext]);
|
|
722
|
+
var CheckContextsPageInfo = z.object({
|
|
723
|
+
hasNextPage: z.boolean(),
|
|
724
|
+
endCursor: z.string().nullable()
|
|
725
|
+
});
|
|
716
726
|
var MyPrSearchNode = z.object({
|
|
717
727
|
__typename: z.literal("PullRequest"),
|
|
718
728
|
id: z.string().regex(NODE_ID_RE),
|
|
@@ -750,6 +760,7 @@ var MyPrSearchNode = z.object({
|
|
|
750
760
|
viewerDefaultMergeMethod: MyPrMergeMethod
|
|
751
761
|
}),
|
|
752
762
|
commits: z.object({ nodes: z.array(z.object({ commit: z.object({ statusCheckRollup: z.object({
|
|
763
|
+
id: z.string().regex(NODE_ID_RE).optional(),
|
|
753
764
|
state: z.enum([
|
|
754
765
|
"SUCCESS",
|
|
755
766
|
"FAILURE",
|
|
@@ -757,7 +768,10 @@ var MyPrSearchNode = z.object({
|
|
|
757
768
|
"ERROR",
|
|
758
769
|
"EXPECTED"
|
|
759
770
|
]).nullable(),
|
|
760
|
-
contexts: z.object({
|
|
771
|
+
contexts: z.object({
|
|
772
|
+
pageInfo: CheckContextsPageInfo.optional(),
|
|
773
|
+
nodes: z.array(MyPrCheckContext)
|
|
774
|
+
})
|
|
761
775
|
}).nullable() }) })) }),
|
|
762
776
|
reviewThreads: z.object({
|
|
763
777
|
pageInfo: z.object({ hasNextPage: z.boolean() }),
|
|
@@ -813,6 +827,13 @@ var MyPrThreadsResponse = z.object({
|
|
|
813
827
|
}) }).nullable() }).nullable() }),
|
|
814
828
|
errors: z.array(z.unknown()).optional()
|
|
815
829
|
});
|
|
830
|
+
var MyPrCheckContextsResponse = z.object({
|
|
831
|
+
data: z.object({ node: z.object({ contexts: z.object({
|
|
832
|
+
pageInfo: CheckContextsPageInfo,
|
|
833
|
+
nodes: z.array(z.unknown())
|
|
834
|
+
}).nullable() }).nullable() }).nullable(),
|
|
835
|
+
errors: z.array(z.unknown()).optional()
|
|
836
|
+
});
|
|
816
837
|
var MyPrMergeStateResponse = z.object({
|
|
817
838
|
data: z.object({ repository: z.object({ pullRequest: z.object({
|
|
818
839
|
state: z.string(),
|
|
@@ -844,16 +865,36 @@ var PendingWakeup = z.object({
|
|
|
844
865
|
reason: z.string().nullable()
|
|
845
866
|
}).passthrough();
|
|
846
867
|
var ASK_USER_QUESTION_TOOL_NAME = "AskUserQuestion";
|
|
868
|
+
var CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS = 4e3;
|
|
869
|
+
var CONVERSATION_PERMISSION_HOOK_EVENT = "permissionrequest-conversation";
|
|
870
|
+
var CONVERSATION_TOOL_RESULT_HOOK_EVENT = "posttooluse-conversation";
|
|
871
|
+
var CONVERSATION_QUESTION_MAX_CHARS = 1e3;
|
|
872
|
+
var CONVERSATION_ANSWER_MAX_CHARS = 2e3;
|
|
873
|
+
var ConversationQuestion = z.object({
|
|
874
|
+
question: z.string().min(1).max(CONVERSATION_QUESTION_MAX_CHARS),
|
|
875
|
+
header: z.string().max(100).default(""),
|
|
876
|
+
options: z.array(z.object({
|
|
877
|
+
label: z.string().min(1).max(200),
|
|
878
|
+
description: z.string().max(CONVERSATION_QUESTION_MAX_CHARS).default("")
|
|
879
|
+
})).min(1).max(8),
|
|
880
|
+
multiSelect: z.boolean().default(false)
|
|
881
|
+
});
|
|
847
882
|
var ConversationApproval = z.object({
|
|
848
883
|
requestId: z.union([z.string().min(1).max(128), z.number().int()]),
|
|
849
884
|
kind: z.enum([
|
|
850
885
|
"command",
|
|
851
886
|
"fileChange",
|
|
852
|
-
"permissions"
|
|
887
|
+
"permissions",
|
|
888
|
+
"tool",
|
|
889
|
+
"question"
|
|
853
890
|
]),
|
|
854
|
-
summary: z.string().max(
|
|
855
|
-
isSummaryTruncated: z.boolean()
|
|
891
|
+
summary: z.string().max(CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS),
|
|
892
|
+
isSummaryTruncated: z.boolean(),
|
|
893
|
+
toolName: z.string().max(200).optional(),
|
|
894
|
+
questions: z.array(ConversationQuestion).min(1).max(4).optional(),
|
|
895
|
+
alwaysAllowSummary: z.string().max(CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS).optional()
|
|
856
896
|
});
|
|
897
|
+
var ConversationAnswers = z.record(z.string().max(CONVERSATION_QUESTION_MAX_CHARS), z.string().max(CONVERSATION_ANSWER_MAX_CHARS));
|
|
857
898
|
var ConversationApprovalDecision = z.enum([
|
|
858
899
|
"accept",
|
|
859
900
|
"acceptForSession",
|
|
@@ -874,6 +915,7 @@ var SessionSnapshot = z.object({
|
|
|
874
915
|
ephemeral: z.boolean(),
|
|
875
916
|
isWorktree: z.boolean(),
|
|
876
917
|
isWorkspace: z.boolean().default(false),
|
|
918
|
+
isGuarded: z.boolean().default(false),
|
|
877
919
|
resumeSessionId: z.string().nullable(),
|
|
878
920
|
activeAgents: z.number().int().nonnegative(),
|
|
879
921
|
awaitingBackgroundTasks: z.boolean(),
|
|
@@ -915,13 +957,17 @@ var AGENT_API_VERBS = [
|
|
|
915
957
|
"cancel",
|
|
916
958
|
"push",
|
|
917
959
|
"edit",
|
|
918
|
-
"limits"
|
|
960
|
+
"limits",
|
|
961
|
+
"factory-status",
|
|
962
|
+
"factory-retry"
|
|
919
963
|
];
|
|
920
964
|
var ORCHESTRATOR_AGENT_VERBS = [
|
|
921
965
|
"dispatch",
|
|
922
966
|
"reopen",
|
|
923
967
|
"cancel"
|
|
924
968
|
];
|
|
969
|
+
var AgentFactoryStatusRequest = z.object({ projectId: z.string().trim().min(1).max(128).optional() }).strict();
|
|
970
|
+
var AgentFactoryRetryRequest = z.object({ intentId: z.string().trim().min(1).max(128) }).strict();
|
|
925
971
|
var AgentSpawnRequest = z.object({
|
|
926
972
|
prompt: z.string({ error: "prompt must be a string" }).min(1, { error: "prompt must not be empty" }).max(2e4, { error: "prompt must be at most 20000 characters" }).refine((prompt) => !prompt.trimStart().startsWith("-"), { message: "prompt must not start with a dash, which the agent CLI would read as an option" }),
|
|
927
973
|
name: z.string({ error: "name must be a string" }).min(1, { error: "name must not be empty" }).max(200, { error: "name must be at most 200 characters" }).optional(),
|
|
@@ -963,7 +1009,7 @@ var AgentLimitsReply = z.object({
|
|
|
963
1009
|
answers: z.boolean(),
|
|
964
1010
|
reason: z.string().optional()
|
|
965
1011
|
}).strict()),
|
|
966
|
-
|
|
1012
|
+
guarded: z.boolean(),
|
|
967
1013
|
deadEnds: z.array(z.object({
|
|
968
1014
|
blocked: z.string(),
|
|
969
1015
|
route: z.string()
|
|
@@ -983,4 +1029,4 @@ z.discriminatedUnion("ok", [z.object({
|
|
|
983
1029
|
reason: z.string()
|
|
984
1030
|
})]);
|
|
985
1031
|
//#endregion
|
|
986
|
-
export {
|
|
1032
|
+
export { repositoryName as $, SshSignReply as A, HEX_SHA_RE as At, MyPrMergeMethod as B, MERGEABLE_LIVE_STATES as Bt, SESSION_DOC_BODY_CAP_BYTES as C, TeamReviewThreadResult as Ct, SessionDoc as D, hasRequiredActionHead as Dt, SSH_SIGN_BODY_CAP_BYTES as E, decisionAsIfApprovalRequired as Et, MyPrAutoRebase as F, repoParts as Ft, MyPrMergeWhenReadyResult as G, MyPrMergeResult as H, STATES as Ht, MyPrCheckContext as I, GithubPollingRefreshRequest as It, MyPrThreadNode as J, MyPrSearchNode as K, MyPrCheckContextsResponse as L, GithubPollingRefreshResult as Lt, TASK_TITLE_CONTROL_CHARACTERS as M, GH_SEGMENT as Mt, TaskTitle as N, NODE_ID_RE as Nt, SessionSnapshot as O, hasStandingViewerApproval as Ot, LinkedPr as P, REPO_SLUG_RE as Pt, SessionPrLinks as Q, MyPrKeepMergeableRequest as R, githubPollingShape as Rt, SESSION_COMMANDS as S, TeamReviewThreadCommentsResponse as St, SIGN_URL_ENV as T, canApproveAfterComment as Tt, MyPrMergeStateResponse as U, MyPrMergeRequest as V, RESTARTABLE_STATES as Vt, MyPrMergeWhenReadyRequest as W, MyPrsState as X, MyPrThreadsResponse as Y, MyPrsStatus as Z, ConversationApprovalDecision as _, TeamReviewActionResult as _t, AgentDispatchRequest as a, PostingPlan as at, PendingWakeup as b, TeamReviewStateEntry as bt, AgentFactoryStatusRequest as c, ReviewChecksState as ct, AgentTicketRequest as d, ReviewFinding as dt, CommitSha as et, CONVERSATION_APPROVAL_SUMMARY_MAX_CHARS as f, ReviewResult as ft, ConversationApproval as g, TeamReviewActionRequest as gt, ConversationAnswers as h, SearchedPr as ht, AgentAttentionRequest as i, GithubReviewState as it, SshSignRequest as j, shortSha as jt, SessionState as k, FULL_SHA_RE as kt, AgentLimitsReply as l, ReviewComment as lt, CONVERSATION_TOOL_RESULT_HOOK_EVENT as m, ReviewVerdict as mt, AGENT_URL_ENV as n, FindingSeverity as nt, AgentEditRequest as o, PrDetail as ot, CONVERSATION_PERMISSION_HOOK_EVENT as p, ReviewThreadId as pt, MyPrSearchResponse as q, ASK_USER_QUESTION_TOOL_NAME as r, GithubReviewDecision as rt, AgentFactoryRetryRequest as s, QueuedReview as st, AGENT_API_VERBS as t, DECIDING_REVIEW_STATES as tt, AgentSpawnRequest as u, ReviewDraft as ut, ConversationQuestion as v, TeamReviewCompareFiles as vt, SESSION_DOC_PATH_MAX_CHARS as w, TeamReviewThreadsRepository as wt, RefinedTaskTitle as x, TeamReviewStatus as xt, ORCHESTRATOR_AGENT_VERBS as y, TeamReviewResolveResponse as yt, MyPrKeepMergeableResult as z, KILLABLE_STATES as zt };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { S as SESSION_COMMANDS } from "./session-BZbariv5.js";
|
|
2
2
|
//#region session/core/session-routes.ts
|
|
3
3
|
var SUPERVISION_LINE = "Glimmervoid supervises this session. Run glimmervoid --help for the commands it gives you and this session's limits.";
|
|
4
4
|
var GUARDRAILS_LINE = "Its guardrails are OS-enforced, so a read, write or connection they refuse will not succeed on retry.";
|
|
@@ -8,8 +8,8 @@ var SHELL_PREFIX_RULE = /^Bash\((.+):\*\)$/;
|
|
|
8
8
|
var NEVER_ASK_MODE = "dontAsk";
|
|
9
9
|
var NO_OWN_WORKTREE_REASON = "this session has no worktree of its own, so there is nothing of its own to land";
|
|
10
10
|
var ANSWERS = Object.freeze({ answers: true });
|
|
11
|
-
function supervisionLine({
|
|
12
|
-
if (!
|
|
11
|
+
function supervisionLine({ isGuarded }) {
|
|
12
|
+
if (!isGuarded) return SUPERVISION_LINE;
|
|
13
13
|
return `${SUPERVISION_LINE} ${GUARDRAILS_LINE}`;
|
|
14
14
|
}
|
|
15
15
|
function coversHelpCommand(permissionRule) {
|
|
@@ -94,8 +94,8 @@ function commandLimit(name, facts) {
|
|
|
94
94
|
reason: verdict.reason
|
|
95
95
|
};
|
|
96
96
|
}
|
|
97
|
-
function
|
|
98
|
-
if (!
|
|
97
|
+
function runsUnderOperatorSessionGuardrails({ isGuarded, isEphemeral, isSpawnedChild }) {
|
|
98
|
+
if (!isGuarded) return false;
|
|
99
99
|
return !isEphemeral || isSpawnedChild;
|
|
100
100
|
}
|
|
101
101
|
var OPERATOR_SESSION_GUARDRAIL_DEAD_ENDS = Object.freeze([{
|
|
@@ -105,13 +105,13 @@ var OPERATOR_SESSION_GUARDRAIL_DEAD_ENDS = Object.freeze([{
|
|
|
105
105
|
function describeSessionLimits(facts) {
|
|
106
106
|
return {
|
|
107
107
|
commands: SESSION_COMMANDS.map((name) => commandLimit(name, facts)),
|
|
108
|
-
|
|
109
|
-
deadEnds:
|
|
108
|
+
guarded: facts.isGuarded,
|
|
109
|
+
deadEnds: runsUnderOperatorSessionGuardrails(facts) ? [...OPERATOR_SESSION_GUARDRAIL_DEAD_ENDS] : []
|
|
110
110
|
};
|
|
111
111
|
}
|
|
112
|
-
function formatSessionLimits({ commands,
|
|
112
|
+
function formatSessionLimits({ commands, guarded, deadEnds }) {
|
|
113
113
|
const commandLines = commands.map(({ name, answers, reason }) => answers ? ` ${name}: available` : ` ${name}: not available, ${reason}`);
|
|
114
|
-
if (!
|
|
114
|
+
if (!guarded) return [
|
|
115
115
|
"This session:",
|
|
116
116
|
...commandLines,
|
|
117
117
|
" guardrails: none"
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { r as errorMessage, t as errorCode } from "./text-DoirglWV.js";
|
|
2
|
-
import "./session-
|
|
2
|
+
import "./session-BZbariv5.js";
|
|
3
3
|
import { t as renderTable } from "./ascii-figure-core-pDAwtlKO.js";
|
|
4
4
|
import { n as glimmervoidHomeDir, t as decideConfigPath } from "./config-path-core-DFt-TemS.js";
|
|
5
5
|
import { n as isRecord } from "./coerce-NSst8oRP.js";
|
|
6
|
-
import { r as BrowserConfig } from "./config-
|
|
7
|
-
import { $ as withEnvSecrets, A as settingsOf, C as deleteValueAtPath, D as scoreSettingsSearch, E as parsePosthogProjectSelection, M as FILE_ONLY_GROUPS, N as SETTINGS_REGISTRY, O as setValueAtPath, S as cloneValue, T as hasScaledUnit, X as ENV_SECRET_BINDINGS, Z as readEnvSecrets, j as valueAtPath, k as settingsGroupOf, l as saveConfigFileOnce, s as loadConfigFile, u as validateConfig, w as displayText, x as SETTINGS_INDEX } from "./config-store-
|
|
6
|
+
import { r as BrowserConfig } from "./config-BYc2z1Oc.js";
|
|
7
|
+
import { $ as withEnvSecrets, A as settingsOf, C as deleteValueAtPath, D as scoreSettingsSearch, E as parsePosthogProjectSelection, M as FILE_ONLY_GROUPS, N as SETTINGS_REGISTRY, O as setValueAtPath, S as cloneValue, T as hasScaledUnit, X as ENV_SECRET_BINDINGS, Z as readEnvSecrets, j as valueAtPath, k as settingsGroupOf, l as saveConfigFileOnce, s as loadConfigFile, u as validateConfig, w as displayText, x as SETTINGS_INDEX } from "./config-store-B7MpiYxG.js";
|
|
8
8
|
import fs from "node:fs";
|
|
9
9
|
import os from "node:os";
|
|
10
10
|
import { parseArgs } from "node:util";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { r as errorMessage } from "./text-DoirglWV.js";
|
|
2
|
-
import {
|
|
2
|
+
import { A as SshSignReply, T as SIGN_URL_ENV } from "./session-BZbariv5.js";
|
|
3
3
|
import { n as execFileAsync, o as spawn } from "./child-process-safe-CbQnyh7D.js";
|
|
4
4
|
import { a as isLoopbackHostname } from "./hook-relay-core-DA_bz2KA.js";
|
|
5
|
-
import { t as postToAgentIngress } from "./agent-api-cli-
|
|
5
|
+
import { t as postToAgentIngress } from "./agent-api-cli-CBgOKXoO.js";
|
|
6
6
|
import { l as parseSshPublicKey } from "./ssh-signing-core-CRhVVWNW.js";
|
|
7
7
|
import path from "node:path";
|
|
8
8
|
import fsp from "node:fs/promises";
|