@sendbird/cli 0.0.0-stage → 0.4.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/.claude-plugin/plugin.json +11 -0
- package/.codex-plugin/plugin.json +39 -0
- package/LICENSE +13 -0
- package/NOTICE.md +1105 -0
- package/PROVENANCE.md +15 -0
- package/README.md +44 -2
- package/dist/acl.d.ts +2 -0
- package/dist/acl.js +12 -0
- package/dist/acl.js.map +1 -0
- package/dist/billing-plan.d.ts +68 -0
- package/dist/billing-plan.js +235 -0
- package/dist/billing-plan.js.map +1 -0
- package/dist/browser.d.ts +1 -0
- package/dist/browser.js +20 -0
- package/dist/browser.js.map +1 -0
- package/dist/calls-catalog.d.ts +79 -0
- package/dist/calls-catalog.js +322 -0
- package/dist/calls-catalog.js.map +1 -0
- package/dist/calls-credits.d.ts +31 -0
- package/dist/calls-credits.js +224 -0
- package/dist/calls-credits.js.map +1 -0
- package/dist/calls-trial.d.ts +45 -0
- package/dist/calls-trial.js +77 -0
- package/dist/calls-trial.js.map +1 -0
- package/dist/calls.d.ts +10 -0
- package/dist/calls.js +470 -0
- package/dist/calls.js.map +1 -0
- package/dist/catalog.d.ts +67 -0
- package/dist/catalog.js +784 -0
- package/dist/catalog.js.map +1 -0
- package/dist/cli.d.ts +6 -0
- package/dist/cli.js +502 -0
- package/dist/cli.js.map +1 -0
- package/dist/core.d.ts +86 -0
- package/dist/core.js +531 -0
- package/dist/core.js.map +1 -0
- package/dist/diagnostics.d.ts +206 -0
- package/dist/diagnostics.js +664 -0
- package/dist/diagnostics.js.map +1 -0
- package/dist/discovery.d.ts +703 -0
- package/dist/discovery.js +846 -0
- package/dist/discovery.js.map +1 -0
- package/dist/docs.d.ts +31 -0
- package/dist/docs.js +139 -0
- package/dist/docs.js.map +1 -0
- package/dist/features.d.ts +131 -0
- package/dist/features.js +227 -0
- package/dist/features.js.map +1 -0
- package/dist/generated-api.d.ts +116 -0
- package/dist/generated-api.js +118 -0
- package/dist/generated-api.js.map +1 -0
- package/dist/human-format.d.ts +40 -0
- package/dist/human-format.js +205 -0
- package/dist/human-format.js.map +1 -0
- package/dist/human-list.d.ts +2 -0
- package/dist/human-list.js +368 -0
- package/dist/human-list.js.map +1 -0
- package/dist/human-output.d.ts +10 -0
- package/dist/human-output.js +1351 -0
- package/dist/human-output.js.map +1 -0
- package/dist/human-pro.d.ts +3 -0
- package/dist/human-pro.js +201 -0
- package/dist/human-pro.js.map +1 -0
- package/dist/human-progress.d.ts +5 -0
- package/dist/human-progress.js +49 -0
- package/dist/human-progress.js.map +1 -0
- package/dist/local.d.ts +42 -0
- package/dist/local.js +707 -0
- package/dist/local.js.map +1 -0
- package/dist/oauth-callback.d.ts +3 -0
- package/dist/oauth-callback.js +66 -0
- package/dist/oauth-callback.js.map +1 -0
- package/dist/oauth-lock.d.ts +6 -0
- package/dist/oauth-lock.js +212 -0
- package/dist/oauth-lock.js.map +1 -0
- package/dist/oauth.d.ts +32 -0
- package/dist/oauth.js +493 -0
- package/dist/oauth.js.map +1 -0
- package/dist/presentation.d.ts +208 -0
- package/dist/presentation.js +156 -0
- package/dist/presentation.js.map +1 -0
- package/dist/pro-api-catalog.d.ts +24 -0
- package/dist/pro-api-catalog.js +619 -0
- package/dist/pro-api-catalog.js.map +1 -0
- package/dist/pro-api.d.ts +10 -0
- package/dist/pro-api.js +338 -0
- package/dist/pro-api.js.map +1 -0
- package/dist/project.d.ts +34 -0
- package/dist/project.js +62 -0
- package/dist/project.js.map +1 -0
- package/dist/recovery.d.ts +41 -0
- package/dist/recovery.js +417 -0
- package/dist/recovery.js.map +1 -0
- package/dist/runner.d.ts +47 -0
- package/dist/runner.js +603 -0
- package/dist/runner.js.map +1 -0
- package/dist/sdk-catalog.d.ts +69 -0
- package/dist/sdk-catalog.js +527 -0
- package/dist/sdk-catalog.js.map +1 -0
- package/dist/sdk-config.d.ts +19 -0
- package/dist/sdk-config.js +85 -0
- package/dist/sdk-config.js.map +1 -0
- package/dist/sdk-dto.d.ts +244 -0
- package/dist/sdk-dto.js +98 -0
- package/dist/sdk-dto.js.map +1 -0
- package/dist/sdk-limits.d.ts +2 -0
- package/dist/sdk-limits.js +3 -0
- package/dist/sdk-limits.js.map +1 -0
- package/dist/sdk-node-file-reader.d.ts +6 -0
- package/dist/sdk-node-file-reader.js +38 -0
- package/dist/sdk-node-file-reader.js.map +1 -0
- package/dist/sdk-pro-catalog.d.ts +22 -0
- package/dist/sdk-pro-catalog.js +234 -0
- package/dist/sdk-pro-catalog.js.map +1 -0
- package/dist/sdk-pro-runtime.d.ts +31 -0
- package/dist/sdk-pro-runtime.js +293 -0
- package/dist/sdk-pro-runtime.js.map +1 -0
- package/dist/sdk-query.d.ts +10 -0
- package/dist/sdk-query.js +29 -0
- package/dist/sdk-query.js.map +1 -0
- package/dist/sdk-runtime.d.ts +15 -0
- package/dist/sdk-runtime.js +685 -0
- package/dist/sdk-runtime.js.map +1 -0
- package/dist/sdk-worker.d.ts +1 -0
- package/dist/sdk-worker.js +26 -0
- package/dist/sdk-worker.js.map +1 -0
- package/dist/sdk.d.ts +29 -0
- package/dist/sdk.js +346 -0
- package/dist/sdk.js.map +1 -0
- package/dist/search-engine.d.ts +17 -0
- package/dist/search-engine.js +54 -0
- package/dist/search-engine.js.map +1 -0
- package/dist/search-intents.d.ts +2 -0
- package/dist/search-intents.js +106 -0
- package/dist/search-intents.js.map +1 -0
- package/dist/search.d.ts +7 -0
- package/dist/search.js +58 -0
- package/dist/search.js.map +1 -0
- package/dist/secret-output.d.ts +2 -0
- package/dist/secret-output.js +108 -0
- package/dist/secret-output.js.map +1 -0
- package/dist/shell.d.ts +3 -0
- package/dist/shell.js +14 -0
- package/dist/shell.js.map +1 -0
- package/dist/skills.d.ts +210 -0
- package/dist/skills.js +374 -0
- package/dist/skills.js.map +1 -0
- package/dist/value-semantics.d.ts +70 -0
- package/dist/value-semantics.js +173 -0
- package/dist/value-semantics.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/windows.d.ts +4 -0
- package/dist/windows.js +136 -0
- package/dist/windows.js.map +1 -0
- package/dist/workflows.d.ts +228 -0
- package/dist/workflows.js +1207 -0
- package/dist/workflows.js.map +1 -0
- package/package.json +90 -4
- package/plugin.json +25 -0
- package/references/agent-guide.json +199 -0
- package/references/commands.md +247 -0
- package/references/dashboard-inventory.json +14796 -0
- package/references/docs-index.json +5329 -0
- package/references/sdk-commands.json +15065 -0
- package/references/workflows.json +1246 -0
- package/skills/sendbird/SKILL.md +96 -0
- package/skills/sendbird/agents/openai.yaml +4 -0
- package/skills/sendbird/references/build-chat-app.md +50 -0
- package/skills/sendbird/references/client-guide.md +34 -0
- package/skills/sendbird/references/client-plugin-bridge.md +77 -0
- package/skills/sendbird/references/client-plugin-install.md +39 -0
- package/skills/sendbird/references/operational-checks.md +35 -0
- package/skills/sendbird/references/pro-capabilities.md +52 -0
- package/skills/sendbird/scripts/client-guide.mjs +283 -0
- package/skills/sendbird/scripts/client-plugin-bridge.mjs +703 -0
- package/skills/sendbird-setup/SKILL.md +18 -0
- package/skills/sendbird-setup/agents/openai.yaml +4 -0
- package/skills/sendbird-setup/references/onboarding.md +73 -0
- package/skills/sendbird-setup/scripts/install-cli.mjs +447 -0
- package/skills/sendbird-setup/scripts/npm-run.mjs +34 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// The client plugin owns SDK code. This helper only verifies its release,
|
|
3
|
+
// starts a bounded Claude session, and checks the noninteractive handoff result.
|
|
4
|
+
import {
|
|
5
|
+
constants,
|
|
6
|
+
openSync,
|
|
7
|
+
fstatSync,
|
|
8
|
+
readSync,
|
|
9
|
+
closeSync,
|
|
10
|
+
mkdtempSync,
|
|
11
|
+
readdirSync,
|
|
12
|
+
rmSync,
|
|
13
|
+
statSync,
|
|
14
|
+
realpathSync,
|
|
15
|
+
} from "node:fs";
|
|
16
|
+
import { tmpdir } from "node:os";
|
|
17
|
+
import { resolve, join } from "node:path";
|
|
18
|
+
import { createHash } from "node:crypto";
|
|
19
|
+
import { execFileSync, spawn } from "node:child_process";
|
|
20
|
+
import { pathToFileURL } from "node:url";
|
|
21
|
+
// Publisher's first-run notice, v0.1.74 (f2b12967d65f652c411baa8f3611e98557abf9ad).
|
|
22
|
+
// Do not infer a person's acknowledgement from a successful stderr write.
|
|
23
|
+
export const clientPluginNotice =
|
|
24
|
+
"Sendbird Client plugin: this plugin runs shell scripts on your computer with your user account's permissions, at session start, before file writes and shell commands, after an agent finishes, and when an answer ends. In Claude Code it also runs one JavaScript file that only shows this notice. They read your project files and parts of this conversation, can block an action or ask Claude to revise its answer, and fetch public pages from sendbird.com/docs and github.com/sendbird. Nothing from your project or conversation is sent to Sendbird. A plugin update replaces these scripts; auto-update is off unless you turn it on. Details: https://github.com/sendbird/sendbird-client-plugin#what-runs-on-your-machine (shown once, and again if this notice changes)";
|
|
25
|
+
// Reviewed publisher release. New releases require an explicit, independently reviewed digest.
|
|
26
|
+
export const reviewedClientRelease = {
|
|
27
|
+
tag: "v0.1.74",
|
|
28
|
+
sha256: "0cf95c1fb1cafa202272e5b6e6ecd832f698dd2a818c3624808a19c2aaa000e9",
|
|
29
|
+
};
|
|
30
|
+
export function verifyArchive(zip) {
|
|
31
|
+
const entries = command("unzip", ["-Z1", zip]).split("\n");
|
|
32
|
+
if (entries.at(-1) === "") entries.pop();
|
|
33
|
+
if (!entries.length) throw Error("Release ZIP is empty");
|
|
34
|
+
const roots = new Set([
|
|
35
|
+
".claude-plugin",
|
|
36
|
+
".codex-plugin",
|
|
37
|
+
"agents",
|
|
38
|
+
"codex",
|
|
39
|
+
"config",
|
|
40
|
+
"core",
|
|
41
|
+
"docs",
|
|
42
|
+
"hooks",
|
|
43
|
+
"knowledge",
|
|
44
|
+
"skills",
|
|
45
|
+
"templates",
|
|
46
|
+
"tools",
|
|
47
|
+
"LICENSE",
|
|
48
|
+
"NOTICE.md",
|
|
49
|
+
"README.md",
|
|
50
|
+
]);
|
|
51
|
+
const seen = new Set();
|
|
52
|
+
for (const entry of entries) {
|
|
53
|
+
const path = entry.endsWith("/") ? entry.slice(0, -1) : entry;
|
|
54
|
+
if (
|
|
55
|
+
!path ||
|
|
56
|
+
/[\\\x00-\x1f\x7f]/.test(path) ||
|
|
57
|
+
path
|
|
58
|
+
.split("/")
|
|
59
|
+
.some(
|
|
60
|
+
(part) => !part || part === "." || part === ".." || part === "bin",
|
|
61
|
+
) ||
|
|
62
|
+
!roots.has(path.split("/")[0]) ||
|
|
63
|
+
/(?:^|\/)\.?(?:mcp|lsp)(?:\.json|\/|$)/i.test(path) ||
|
|
64
|
+
(path.startsWith(".claude-plugin/") &&
|
|
65
|
+
path !== ".claude-plugin/plugin.json") ||
|
|
66
|
+
(path.startsWith(".codex-plugin/") &&
|
|
67
|
+
path !== ".codex-plugin/plugin.json") ||
|
|
68
|
+
seen.has(path)
|
|
69
|
+
)
|
|
70
|
+
throw Error("Release ZIP contains an unreviewed or unsafe entry");
|
|
71
|
+
seen.add(path);
|
|
72
|
+
}
|
|
73
|
+
const modes = command("unzip", ["-Zl", zip])
|
|
74
|
+
.split("\n")
|
|
75
|
+
.filter((line) => /^[^\s]\S{9}\s+\d+\.\d+\s/.test(line));
|
|
76
|
+
if (
|
|
77
|
+
modes.length !== entries.length ||
|
|
78
|
+
modes.some((line) => !/^[-d][rwx-]{9}\s+/.test(line))
|
|
79
|
+
)
|
|
80
|
+
throw Error(
|
|
81
|
+
"Release ZIP must contain only regular files and directories; links and special entries are forbidden",
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
export async function printExecutionNotice(
|
|
85
|
+
write = process.stderr.write.bind(process.stderr),
|
|
86
|
+
) {
|
|
87
|
+
await new Promise((resolve, reject) =>
|
|
88
|
+
write(
|
|
89
|
+
"Publisher notice (full plugin behavior):\n" +
|
|
90
|
+
clientPluginNotice +
|
|
91
|
+
"\nThis ZIP run is edit-only: non-managed hooks, shell tools, Skill shell execution, agents and MCP are disabled. Managed policy still applies. The parent Agent must review/build/test. This notice is emitted on every run; no seen/consent marker is written.\n",
|
|
92
|
+
(error) => (error ? reject(error) : resolve()),
|
|
93
|
+
),
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
// Read text from the verified ZIP without extracting or executing publisher code.
|
|
97
|
+
// Fail closed when the publisher changes the notice/manifest contract.
|
|
98
|
+
export function verifyPublisherNotice(zip, release) {
|
|
99
|
+
const manifest = JSON.parse(
|
|
100
|
+
command("unzip", ["-p", zip, ".claude-plugin/plugin.json"], {
|
|
101
|
+
maxBuffer: 64 * 1024,
|
|
102
|
+
}),
|
|
103
|
+
);
|
|
104
|
+
const keys = new Set([
|
|
105
|
+
"name",
|
|
106
|
+
"displayName",
|
|
107
|
+
"version",
|
|
108
|
+
"description",
|
|
109
|
+
"author",
|
|
110
|
+
"homepage",
|
|
111
|
+
"repository",
|
|
112
|
+
"license",
|
|
113
|
+
"keywords",
|
|
114
|
+
]);
|
|
115
|
+
if (Object.keys(manifest).some((key) => !keys.has(key)))
|
|
116
|
+
throw Error("Publisher manifest contains an unreviewed capability");
|
|
117
|
+
const script = command("unzip", ["-p", zip, "hooks/first-run-notice.sh"], {
|
|
118
|
+
maxBuffer: 64 * 1024,
|
|
119
|
+
});
|
|
120
|
+
const matches = [...script.matchAll(/^msg="([^"\r\n]*)"$/gm)];
|
|
121
|
+
if (
|
|
122
|
+
manifest.name !== "sendbird-client" ||
|
|
123
|
+
manifest.version !== release.slice(1) ||
|
|
124
|
+
matches.length !== 1
|
|
125
|
+
)
|
|
126
|
+
throw Error(
|
|
127
|
+
"Publisher notice metadata changed; update the CLI bridge before execution",
|
|
128
|
+
);
|
|
129
|
+
const message = matches[0][1]
|
|
130
|
+
.replace("${display:-$name}", manifest.displayName || manifest.name)
|
|
131
|
+
.replace("${repo}", manifest.repository);
|
|
132
|
+
if (message !== clientPluginNotice)
|
|
133
|
+
throw Error(
|
|
134
|
+
"Publisher notice changed; review and update the CLI bridge before execution",
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
// Keep this helper standalone: skills can be copied outside the npm package.
|
|
138
|
+
// Mirrors schema-v1's fields and conditional rules; do not silently accept a
|
|
139
|
+
// different plugin contract after an upgrade.
|
|
140
|
+
function validateSummary(value) {
|
|
141
|
+
const fail = () => {
|
|
142
|
+
throw Error("Invalid client plugin schema-v1 run summary");
|
|
143
|
+
};
|
|
144
|
+
const str = (v) => typeof v === "string";
|
|
145
|
+
const text = (v) => str(v) && v.length > 0;
|
|
146
|
+
const array = (v, check) => Array.isArray(v) && v.every(check);
|
|
147
|
+
const object = (v, required, optional = {}) => {
|
|
148
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return false;
|
|
149
|
+
const fields = { ...required, ...optional };
|
|
150
|
+
return (
|
|
151
|
+
Object.keys(v).every((k) => Object.hasOwn(fields, k)) &&
|
|
152
|
+
Object.entries(required).every(
|
|
153
|
+
([k, f]) => Object.hasOwn(v, k) && f(v[k]),
|
|
154
|
+
) &&
|
|
155
|
+
Object.entries(optional).every(
|
|
156
|
+
([k, f]) => !Object.hasOwn(v, k) || f(v[k]),
|
|
157
|
+
)
|
|
158
|
+
);
|
|
159
|
+
};
|
|
160
|
+
const status = (v) =>
|
|
161
|
+
["completed", "needs_input", "failed", "declined"].includes(v);
|
|
162
|
+
const changes = (v) =>
|
|
163
|
+
array(v, (x) =>
|
|
164
|
+
object(
|
|
165
|
+
x,
|
|
166
|
+
{
|
|
167
|
+
path: (p) =>
|
|
168
|
+
text(p) &&
|
|
169
|
+
!p.includes("\\") &&
|
|
170
|
+
!/^[a-z]:/i.test(p) &&
|
|
171
|
+
p.split("/").every((part) => part && part !== "." && part !== ".."),
|
|
172
|
+
action: (a) => ["created", "modified", "deleted"].includes(a),
|
|
173
|
+
},
|
|
174
|
+
{ summary: str },
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
const questions = (v) =>
|
|
178
|
+
array(v, (x) =>
|
|
179
|
+
object(
|
|
180
|
+
x,
|
|
181
|
+
{
|
|
182
|
+
id: (id) => str(id) && /^[a-z][a-z0-9-]*$/.test(id),
|
|
183
|
+
question: text,
|
|
184
|
+
},
|
|
185
|
+
{
|
|
186
|
+
options: (a) => array(a, str) && a.length > 0,
|
|
187
|
+
labels: (a) => array(a, str),
|
|
188
|
+
type: (v) => ["single", "multi", "text"].includes(v),
|
|
189
|
+
when: (v) =>
|
|
190
|
+
v !== null &&
|
|
191
|
+
typeof v === "object" &&
|
|
192
|
+
!Array.isArray(v) &&
|
|
193
|
+
Object.values(v).every((a) => array(a, str)),
|
|
194
|
+
},
|
|
195
|
+
),
|
|
196
|
+
);
|
|
197
|
+
if (
|
|
198
|
+
!object(
|
|
199
|
+
value,
|
|
200
|
+
{
|
|
201
|
+
schemaVersion: (v) => v === "1",
|
|
202
|
+
skill: (v) => str(v) && /^[a-z][a-z0-9-]*$/.test(v),
|
|
203
|
+
status,
|
|
204
|
+
changes,
|
|
205
|
+
manualSteps: (v) => array(v, text),
|
|
206
|
+
},
|
|
207
|
+
{
|
|
208
|
+
project: (v) =>
|
|
209
|
+
object(
|
|
210
|
+
v,
|
|
211
|
+
{},
|
|
212
|
+
{
|
|
213
|
+
platform: (p) => ["ios", "android", "js", "unknown"].includes(p),
|
|
214
|
+
host: str,
|
|
215
|
+
products: (p) => array(p, str) && new Set(p).size === p.length,
|
|
216
|
+
},
|
|
217
|
+
),
|
|
218
|
+
steps: (v) =>
|
|
219
|
+
array(v, (x) =>
|
|
220
|
+
object(
|
|
221
|
+
x,
|
|
222
|
+
{
|
|
223
|
+
description: text,
|
|
224
|
+
status: (s) => ["done", "skipped", "failed"].includes(s),
|
|
225
|
+
},
|
|
226
|
+
{ detail: str },
|
|
227
|
+
),
|
|
228
|
+
),
|
|
229
|
+
apis: (v) => array(v, (x) => object(x, { symbol: text, source: text })),
|
|
230
|
+
questions,
|
|
231
|
+
reason: text,
|
|
232
|
+
},
|
|
233
|
+
)
|
|
234
|
+
)
|
|
235
|
+
fail();
|
|
236
|
+
if (
|
|
237
|
+
value.status === "needs_input" &&
|
|
238
|
+
(!value.questions?.length || value.changes.length)
|
|
239
|
+
)
|
|
240
|
+
fail();
|
|
241
|
+
if (["failed", "declined"].includes(value.status) && !value.reason) fail();
|
|
242
|
+
return value;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Preserve report structure while removing credential-shaped text and known
|
|
246
|
+
// inherited secrets. This is output protection, not a filesystem sandbox.
|
|
247
|
+
export function redactReport(value, sensitiveValues) {
|
|
248
|
+
if (typeof value === "string") {
|
|
249
|
+
let safe = value
|
|
250
|
+
.replace(
|
|
251
|
+
/-----BEGIN (?:[A-Z0-9]+ )?PRIVATE KEY-----[\s\S]*?-----END (?:[A-Z0-9]+ )?PRIVATE KEY-----/g,
|
|
252
|
+
"[REDACTED]",
|
|
253
|
+
)
|
|
254
|
+
.replace(/\b(Bearer|Basic)\s+[A-Za-z0-9._~+/=-]+/gi, "$1 [REDACTED]")
|
|
255
|
+
.replace(/(https?:\/\/)[^\s/@:]+:[^\s/@]+@/gi, "$1[REDACTED]@")
|
|
256
|
+
.replace(
|
|
257
|
+
/\b((?:access[_-]?token|refresh[_-]?token|session[_-]?token|oauth[_-]?token|api[_-]?(?:token|key)|client[_-]?secret|password)["']?\s*[:=]\s*)(?:"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|[^\s,;&]+)/gi,
|
|
258
|
+
"$1[REDACTED]",
|
|
259
|
+
);
|
|
260
|
+
for (const secret of sensitiveValues)
|
|
261
|
+
safe = safe.replaceAll(secret, "[REDACTED]");
|
|
262
|
+
return safe;
|
|
263
|
+
}
|
|
264
|
+
if (Array.isArray(value))
|
|
265
|
+
return value.map((v) => redactReport(v, sensitiveValues));
|
|
266
|
+
if (value && typeof value === "object")
|
|
267
|
+
return Object.fromEntries(
|
|
268
|
+
Object.entries(value).map(([k, v]) => [
|
|
269
|
+
k,
|
|
270
|
+
redactReport(v, sensitiveValues),
|
|
271
|
+
]),
|
|
272
|
+
);
|
|
273
|
+
return value;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
export function parseRun(events, { sensitiveValues = [] } = {}) {
|
|
277
|
+
// Terminal result.result can contain only the final text fragment. Assemble
|
|
278
|
+
// every top-level assistant text block before finding the final JSON fence.
|
|
279
|
+
const init = events.find((e) => e.type === "system" && e.subtype === "init");
|
|
280
|
+
if (
|
|
281
|
+
!init?.plugins?.some(
|
|
282
|
+
(p) => (typeof p === "string" ? p : p.name) === "sendbird-client",
|
|
283
|
+
)
|
|
284
|
+
)
|
|
285
|
+
throw Error("Sendbird client plugin was not loaded");
|
|
286
|
+
const body = events
|
|
287
|
+
.filter((e) => e.type === "assistant" && !e.parent_tool_use_id)
|
|
288
|
+
.flatMap((e) => e.message?.content ?? [])
|
|
289
|
+
.filter((b) => b.type === "text")
|
|
290
|
+
.map((b) => b.text)
|
|
291
|
+
.join("");
|
|
292
|
+
const fences = [...body.matchAll(/```json\s*([\s\S]*?)```/g)];
|
|
293
|
+
if (!fences.length) throw Error("Plugin returned no JSON run summary");
|
|
294
|
+
const summary = validateSummary(JSON.parse(fences.at(-1)[1]));
|
|
295
|
+
const result = events.findLast((e) => e.type === "result");
|
|
296
|
+
if (!result || result.is_error || result.subtype !== "success")
|
|
297
|
+
throw Error(
|
|
298
|
+
"Claude did not finish successfully; partial changes may exist",
|
|
299
|
+
);
|
|
300
|
+
const metric = (value) => {
|
|
301
|
+
if (value == null) return null;
|
|
302
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0)
|
|
303
|
+
throw Error("Invalid Claude result metric");
|
|
304
|
+
return value;
|
|
305
|
+
};
|
|
306
|
+
return {
|
|
307
|
+
summary: redactReport(
|
|
308
|
+
summary,
|
|
309
|
+
sensitiveValues
|
|
310
|
+
.filter((s) => typeof s === "string" && s.length >= 4)
|
|
311
|
+
.sort((a, b) => b.length - a.length),
|
|
312
|
+
),
|
|
313
|
+
cost_usd: metric(result.total_cost_usd),
|
|
314
|
+
duration_ms: metric(result.duration_ms),
|
|
315
|
+
verification:
|
|
316
|
+
"Plugin report only; parent must independently verify build and runtime",
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
export function childEnvironment(parent, appId) {
|
|
321
|
+
// Only Claude authentication and necessary runtime settings are inherited.
|
|
322
|
+
// GitHub/cloud/Sendbird credentials are used by neither the child nor its tools.
|
|
323
|
+
const allowed = new Set([
|
|
324
|
+
"PATH",
|
|
325
|
+
"HOME",
|
|
326
|
+
"USER",
|
|
327
|
+
"LOGNAME",
|
|
328
|
+
"USERPROFILE",
|
|
329
|
+
"APPDATA",
|
|
330
|
+
"LOCALAPPDATA",
|
|
331
|
+
"SystemRoot",
|
|
332
|
+
"WINDIR",
|
|
333
|
+
"TMPDIR",
|
|
334
|
+
"TMP",
|
|
335
|
+
"TEMP",
|
|
336
|
+
"LANG",
|
|
337
|
+
"LC_ALL",
|
|
338
|
+
"LC_CTYPE",
|
|
339
|
+
"TZ",
|
|
340
|
+
"TERM",
|
|
341
|
+
"NO_COLOR",
|
|
342
|
+
"CLAUDE_CONFIG_DIR",
|
|
343
|
+
"ANTHROPIC_API_KEY",
|
|
344
|
+
"ANTHROPIC_AUTH_TOKEN",
|
|
345
|
+
"CLAUDE_CODE_OAUTH_TOKEN",
|
|
346
|
+
]);
|
|
347
|
+
const env = Object.fromEntries(
|
|
348
|
+
Object.entries(parent).filter(([key]) => allowed.has(key)),
|
|
349
|
+
);
|
|
350
|
+
return { ...env, SENDBIRD_APP_ID: appId, SENDBIRD_NONINTERACTIVE: "1" };
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function command(bin, args, options = {}) {
|
|
354
|
+
return execFileSync(bin, args, {
|
|
355
|
+
encoding: "utf8",
|
|
356
|
+
timeout: 60000,
|
|
357
|
+
maxBuffer: 4 * 1024 * 1024,
|
|
358
|
+
env: bin === "gh" ? { ...process.env, GH_HOST: "github.com" } : process.env,
|
|
359
|
+
...options,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// Bound the read itself, not just the decoded string: a generated brief may
|
|
364
|
+
// accidentally be huge, a device or a FIFO. Never launch external tools for it.
|
|
365
|
+
function readBounded(file, limit) {
|
|
366
|
+
const fd = openSync(file, constants.O_RDONLY | constants.O_NONBLOCK);
|
|
367
|
+
try {
|
|
368
|
+
const stat = fstatSync(fd);
|
|
369
|
+
if (!stat.isFile() || stat.size > limit)
|
|
370
|
+
throw Error("Expected a bounded regular file");
|
|
371
|
+
const bytes = Buffer.alloc(limit + 1);
|
|
372
|
+
let size = 0;
|
|
373
|
+
while (size < bytes.length) {
|
|
374
|
+
const count = readSync(fd, bytes, size, bytes.length - size, null);
|
|
375
|
+
if (!count) break;
|
|
376
|
+
size += count;
|
|
377
|
+
}
|
|
378
|
+
if (size > limit) throw Error("File exceeds the byte limit");
|
|
379
|
+
return bytes.subarray(0, size);
|
|
380
|
+
} finally {
|
|
381
|
+
closeSync(fd);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
let stage = "input";
|
|
386
|
+
async function run() {
|
|
387
|
+
if (process.argv.length === 3 && ["--help", "-h"].includes(process.argv[2])) {
|
|
388
|
+
process.stdout.write(
|
|
389
|
+
"Run the Sendbird client plugin from a checksum-verified release ZIP.\n" +
|
|
390
|
+
"Usage: node client-plugin-bridge.mjs --project DIR --app-id ID --prompt-file FILE\n" +
|
|
391
|
+
"Options: --release TAG --sha256 REVIEWED_DIGEST --budget USD --timeout-seconds N --model MODEL; default: reviewed v0.1.74, never latest\n" +
|
|
392
|
+
"Requires macOS/Linux, unzip, authenticated Claude Code 2.1.304+ and GitHub release access. Brief must contain no credentials and be at most 64 KiB.\n" +
|
|
393
|
+
"Exit codes: 0 plugin completed (independent verification required); 2 needs_input/failed/declined; 1 handoff error.\n",
|
|
394
|
+
);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
const options = {};
|
|
398
|
+
for (let i = 2; i < process.argv.length; i += 2) {
|
|
399
|
+
const key = process.argv[i];
|
|
400
|
+
if (
|
|
401
|
+
![
|
|
402
|
+
"--project",
|
|
403
|
+
"--app-id",
|
|
404
|
+
"--prompt-file",
|
|
405
|
+
"--release",
|
|
406
|
+
"--sha256",
|
|
407
|
+
"--budget",
|
|
408
|
+
"--timeout-seconds",
|
|
409
|
+
"--model",
|
|
410
|
+
].includes(key) ||
|
|
411
|
+
!process.argv[i + 1]
|
|
412
|
+
)
|
|
413
|
+
throw Error(
|
|
414
|
+
"Use --project DIR --app-id ID --prompt-file FILE [--release TAG] [--budget USD] [--timeout-seconds N] [--model MODEL]",
|
|
415
|
+
);
|
|
416
|
+
if (options[key]) throw Error(`Duplicate option ${key}`);
|
|
417
|
+
options[key] = process.argv[i + 1];
|
|
418
|
+
}
|
|
419
|
+
if (!["darwin", "linux"].includes(process.platform))
|
|
420
|
+
throw Error(
|
|
421
|
+
"ZIP execution currently requires macOS/Linux; use the native plugin or explicitly choose guide-only work on Windows",
|
|
422
|
+
);
|
|
423
|
+
const project = resolve(options["--project"] ?? "");
|
|
424
|
+
if (
|
|
425
|
+
!options["--project"] ||
|
|
426
|
+
!statSync(project).isDirectory() ||
|
|
427
|
+
!/^[a-zA-Z0-9_-]{1,128}$/.test(options["--app-id"] ?? "") ||
|
|
428
|
+
!options["--prompt-file"]
|
|
429
|
+
)
|
|
430
|
+
throw Error("Project, App ID, and non-secret prompt file are required");
|
|
431
|
+
const budget = Number(options["--budget"] ?? "3");
|
|
432
|
+
const timeout = Number(options["--timeout-seconds"] ?? "600") * 1000;
|
|
433
|
+
if (
|
|
434
|
+
!Number.isFinite(budget) ||
|
|
435
|
+
budget <= 0 ||
|
|
436
|
+
!Number.isFinite(timeout) ||
|
|
437
|
+
timeout <= 0 ||
|
|
438
|
+
timeout > 2_147_483_647
|
|
439
|
+
)
|
|
440
|
+
throw Error("Budget and timeout must be positive numbers");
|
|
441
|
+
// Validate the brief before network access or launching a paid session.
|
|
442
|
+
const prompt = new TextDecoder("utf-8", { fatal: true }).decode(
|
|
443
|
+
readBounded(resolve(options["--prompt-file"]), 64 * 1024),
|
|
444
|
+
);
|
|
445
|
+
if (
|
|
446
|
+
!prompt.trim() ||
|
|
447
|
+
prompt.includes("\0") ||
|
|
448
|
+
Buffer.byteLength(prompt) > 64 * 1024
|
|
449
|
+
)
|
|
450
|
+
throw Error("Brief must contain 1–64 KiB of non-secret text");
|
|
451
|
+
stage = "prerequisites";
|
|
452
|
+
// Older hosts can silently ignore unknown settings. Require the first host
|
|
453
|
+
// version verified for this edit-only policy rather than weakening it.
|
|
454
|
+
const version = command("claude", ["--version"])
|
|
455
|
+
.trim()
|
|
456
|
+
.match(/^(\d+)\.(\d+)\.(\d+)\b/);
|
|
457
|
+
const minimum = [2, 1, 304];
|
|
458
|
+
if (
|
|
459
|
+
!version ||
|
|
460
|
+
minimum.reduce(
|
|
461
|
+
(order, value, i) => order || Number(version[i + 1]) - value,
|
|
462
|
+
0,
|
|
463
|
+
) < 0
|
|
464
|
+
)
|
|
465
|
+
throw Error(
|
|
466
|
+
"Edit-only handoff requires Claude Code 2.1.304 or later; update Claude before retrying",
|
|
467
|
+
);
|
|
468
|
+
const auth = JSON.parse(
|
|
469
|
+
command("claude", ["auth", "status"], {
|
|
470
|
+
env: childEnvironment(process.env, options["--app-id"]),
|
|
471
|
+
}),
|
|
472
|
+
);
|
|
473
|
+
if (!auth.loggedIn) throw Error("Claude CLI login is required");
|
|
474
|
+
stage = "release";
|
|
475
|
+
const release = options["--release"] ?? reviewedClientRelease.tag;
|
|
476
|
+
if (!/^v\d+\.\d+\.\d+(?:[-.][a-zA-Z0-9.-]+)?$/.test(release))
|
|
477
|
+
throw Error("Unsupported release tag; latest is not an audited release");
|
|
478
|
+
const expectedDigest =
|
|
479
|
+
options["--sha256"] ??
|
|
480
|
+
(release === reviewedClientRelease.tag ? reviewedClientRelease.sha256 : "");
|
|
481
|
+
if (
|
|
482
|
+
!/^[a-fA-F0-9]{64}$/.test(expectedDigest) ||
|
|
483
|
+
(options["--sha256"] && !options["--release"])
|
|
484
|
+
)
|
|
485
|
+
throw Error(
|
|
486
|
+
"An explicit release and independently reviewed SHA-256 are required for an unpinned release",
|
|
487
|
+
);
|
|
488
|
+
const dir = mkdtempSync(join(tmpdir(), "sendbird-client-"));
|
|
489
|
+
try {
|
|
490
|
+
command("gh", [
|
|
491
|
+
"release",
|
|
492
|
+
"download",
|
|
493
|
+
release,
|
|
494
|
+
"-R",
|
|
495
|
+
"sendbird/sendbird-client-plugin",
|
|
496
|
+
"-p",
|
|
497
|
+
"sendbird-client-*.zip*",
|
|
498
|
+
"-D",
|
|
499
|
+
dir,
|
|
500
|
+
]);
|
|
501
|
+
const names = readdirSync(dir).filter((n) => n.endsWith(".zip"));
|
|
502
|
+
if (names.length !== 1) throw Error("Expected exactly one release ZIP");
|
|
503
|
+
const zip = names[0];
|
|
504
|
+
const checksum = readBounded(join(dir, `${zip}.sha256`), 4096)
|
|
505
|
+
.toString("utf8")
|
|
506
|
+
.trim();
|
|
507
|
+
const match = checksum.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
|
|
508
|
+
if (!match || match[2] !== zip)
|
|
509
|
+
throw Error("Checksum manifest does not identify the release ZIP");
|
|
510
|
+
const zipFile = join(dir, zip);
|
|
511
|
+
if (
|
|
512
|
+
zip !== `sendbird-client-${release.slice(1)}.zip` ||
|
|
513
|
+
statSync(zipFile).size > 16 * 1024 * 1024
|
|
514
|
+
)
|
|
515
|
+
throw Error("Unexpected or oversized release ZIP");
|
|
516
|
+
const digest = createHash("sha256")
|
|
517
|
+
.update(readBounded(zipFile, 16 * 1024 * 1024))
|
|
518
|
+
.digest("hex");
|
|
519
|
+
if (digest !== match[1].toLowerCase())
|
|
520
|
+
throw Error("Release checksum mismatch");
|
|
521
|
+
if (digest !== expectedDigest.toLowerCase())
|
|
522
|
+
throw Error("Release differs from the independently reviewed SHA-256");
|
|
523
|
+
verifyArchive(zipFile);
|
|
524
|
+
process.stderr.write(
|
|
525
|
+
`Client plugin ${release}: matches reviewed SHA-256 and release checksum (not a publisher signature); edit-only handoff; parent must install/build/test\n`,
|
|
526
|
+
);
|
|
527
|
+
stage = "execution";
|
|
528
|
+
const args = [
|
|
529
|
+
"-p",
|
|
530
|
+
"--append-system-prompt",
|
|
531
|
+
"This is an edit-only handoff. Read and edit customer source files using the loaded client plugin. Do not run commands, delegate agents, install packages, build, test, access a browser, or modify tool configuration. Report installation/build/runtime checks as skipped steps for the parent agent. Reserve manualSteps for missing external prerequisites under the plugin schema. Never claim these checks were executed. Finish with the schema-v1 JSON summary.",
|
|
532
|
+
"--restricted",
|
|
533
|
+
"--plugin-dir",
|
|
534
|
+
join(dir, zip),
|
|
535
|
+
"--setting-sources",
|
|
536
|
+
"",
|
|
537
|
+
"--settings",
|
|
538
|
+
'{"disableAllHooks":true,"disableSkillShellExecution":true}',
|
|
539
|
+
"--strict-mcp-config",
|
|
540
|
+
"--mcp-config",
|
|
541
|
+
'{"mcpServers":{}}',
|
|
542
|
+
"--permission-mode",
|
|
543
|
+
"acceptEdits",
|
|
544
|
+
// Keep tool availability and explicit denials: a Skill must not grant
|
|
545
|
+
// shell/agent execution through its own allowed-tools declaration.
|
|
546
|
+
"--tools",
|
|
547
|
+
"Read,Grep,Glob,Edit,Write,MultiEdit,Skill",
|
|
548
|
+
"--allowedTools",
|
|
549
|
+
"Read,Grep,Glob,Edit,Write,MultiEdit,Skill",
|
|
550
|
+
"--disallowedTools",
|
|
551
|
+
"Bash,PowerShell,Agent,Task,WebFetch,WebSearch,mcp__*",
|
|
552
|
+
"--output-format",
|
|
553
|
+
"stream-json",
|
|
554
|
+
"--verbose",
|
|
555
|
+
"--no-session-persistence",
|
|
556
|
+
"--max-budget-usd",
|
|
557
|
+
String(budget),
|
|
558
|
+
];
|
|
559
|
+
if (options["--model"]) args.push("--model", options["--model"]);
|
|
560
|
+
// Flush the caller-visible notice before starting any plugin/model execution.
|
|
561
|
+
stage = "notice";
|
|
562
|
+
verifyPublisherNotice(zipFile, release);
|
|
563
|
+
await printExecutionNotice();
|
|
564
|
+
stage = "execution";
|
|
565
|
+
const child = spawn("claude", args, {
|
|
566
|
+
cwd: project,
|
|
567
|
+
env: childEnvironment(process.env, options["--app-id"]),
|
|
568
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
569
|
+
detached: true,
|
|
570
|
+
});
|
|
571
|
+
const events = [];
|
|
572
|
+
let pending = "",
|
|
573
|
+
bytes = 0,
|
|
574
|
+
stopReason,
|
|
575
|
+
killTimer,
|
|
576
|
+
stopped;
|
|
577
|
+
const stop = (reason) => {
|
|
578
|
+
if (stopReason) return;
|
|
579
|
+
stopReason = reason;
|
|
580
|
+
try {
|
|
581
|
+
process.kill(-child.pid, "SIGTERM");
|
|
582
|
+
} catch {}
|
|
583
|
+
stopped = new Promise((done) => {
|
|
584
|
+
killTimer = setTimeout(() => {
|
|
585
|
+
try {
|
|
586
|
+
process.kill(-child.pid, "SIGKILL");
|
|
587
|
+
} catch {}
|
|
588
|
+
done();
|
|
589
|
+
}, 3000);
|
|
590
|
+
});
|
|
591
|
+
};
|
|
592
|
+
const interrupted = (signal) => {
|
|
593
|
+
interruptionExitCode = signal === "SIGINT" ? 130 : 143;
|
|
594
|
+
stop("Client handoff interrupted; inspect project edits before retrying");
|
|
595
|
+
};
|
|
596
|
+
const onInterrupt = () => interrupted("SIGINT");
|
|
597
|
+
const onTerminate = () => interrupted("SIGTERM");
|
|
598
|
+
process.on("SIGINT", onInterrupt);
|
|
599
|
+
process.on("SIGTERM", onTerminate);
|
|
600
|
+
const timer = setTimeout(
|
|
601
|
+
() => stop("Claude timed out; inspect changes before retrying"),
|
|
602
|
+
timeout,
|
|
603
|
+
);
|
|
604
|
+
child.stdout.setEncoding("utf8");
|
|
605
|
+
child.stdout.on("data", (chunk) => {
|
|
606
|
+
bytes += Buffer.byteLength(chunk, "utf8");
|
|
607
|
+
if (bytes > 16 * 1024 * 1024) {
|
|
608
|
+
stop("Claude output limit exceeded");
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
pending += chunk.toString();
|
|
612
|
+
const lines = pending.split("\n");
|
|
613
|
+
pending = lines.pop();
|
|
614
|
+
for (const line of lines) {
|
|
615
|
+
if (!line.trim()) continue;
|
|
616
|
+
try {
|
|
617
|
+
events.push(JSON.parse(line));
|
|
618
|
+
} catch {
|
|
619
|
+
stop("Invalid Claude JSON stream");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
// Raw model logs can include customer data; do not echo or persist them.
|
|
624
|
+
child.stderr.on("data", () => {});
|
|
625
|
+
child.stdin.on("error", () =>
|
|
626
|
+
stop("Cannot send the brief; inspect project edits before retrying"),
|
|
627
|
+
);
|
|
628
|
+
child.stdin.end(prompt);
|
|
629
|
+
let code;
|
|
630
|
+
try {
|
|
631
|
+
code = await new Promise((res, rej) => {
|
|
632
|
+
child.once("error", rej);
|
|
633
|
+
child.once("close", res);
|
|
634
|
+
});
|
|
635
|
+
} finally {
|
|
636
|
+
clearTimeout(timer);
|
|
637
|
+
// The direct child may close while a descendant ignores SIGTERM and
|
|
638
|
+
// closes its pipes. Still finish process-group escalation before cleanup.
|
|
639
|
+
if (stopped) await stopped;
|
|
640
|
+
clearTimeout(killTimer);
|
|
641
|
+
process.removeListener("SIGINT", onInterrupt);
|
|
642
|
+
process.removeListener("SIGTERM", onTerminate);
|
|
643
|
+
}
|
|
644
|
+
if (stopReason) throw Error(stopReason);
|
|
645
|
+
if (pending.trim()) events.push(JSON.parse(pending));
|
|
646
|
+
if (code !== 0)
|
|
647
|
+
throw Error(
|
|
648
|
+
"Claude exited unsuccessfully; inspect project changes before retrying",
|
|
649
|
+
);
|
|
650
|
+
stage = "contract";
|
|
651
|
+
const sensitiveValues = Object.entries(process.env)
|
|
652
|
+
.filter(([key]) =>
|
|
653
|
+
/(?:^|_)(?:TOKEN|SECRET|PASSWORD|PASSWD|API_KEY|PRIVATE_KEY|COOKIE|CREDENTIAL)(?:$|_)/i.test(
|
|
654
|
+
key,
|
|
655
|
+
),
|
|
656
|
+
)
|
|
657
|
+
.map(([, value]) => value);
|
|
658
|
+
const result = {
|
|
659
|
+
release,
|
|
660
|
+
checksum_sha256: digest,
|
|
661
|
+
...parseRun(events, { sensitiveValues }),
|
|
662
|
+
};
|
|
663
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
664
|
+
if (result.summary.status !== "completed") process.exitCode = 2;
|
|
665
|
+
} finally {
|
|
666
|
+
rmSync(dir, { recursive: true, force: true });
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
let interruptionExitCode;
|
|
671
|
+
|
|
672
|
+
export async function main() {
|
|
673
|
+
stage = "input";
|
|
674
|
+
interruptionExitCode = undefined;
|
|
675
|
+
await run().catch(() => {
|
|
676
|
+
// Model and SDK errors can contain secrets. Emit a fixed diagnostic instead.
|
|
677
|
+
const hints = {
|
|
678
|
+
input: "Check macOS/Linux, required paths, App ID, budget, and timeout.",
|
|
679
|
+
notice:
|
|
680
|
+
"Cannot verify or emit the publisher notice. Check unzip, the release notice/metadata and stderr; update the bridge if the publisher notice changed. No plugin process was started.",
|
|
681
|
+
prerequisites:
|
|
682
|
+
"Update to Claude Code 2.1.304 or later and check Claude login.",
|
|
683
|
+
release: "Check GitHub access, selected release, and checksum.",
|
|
684
|
+
execution:
|
|
685
|
+
"Process failed, timed out, or exceeded limits; inspect project edits.",
|
|
686
|
+
contract:
|
|
687
|
+
"Plugin loading, summary schema, or terminal success check failed; inspect project edits.",
|
|
688
|
+
};
|
|
689
|
+
process.stderr.write(
|
|
690
|
+
interruptionExitCode
|
|
691
|
+
? "Client handoff interrupted (E_CLIENT_INTERRUPTED): child stopped; inspect project edits before retrying.\n"
|
|
692
|
+
: `Client handoff failed (E_CLIENT_${stage.toUpperCase()}): ${hints[stage]} Do not blindly retry.\n`,
|
|
693
|
+
);
|
|
694
|
+
process.exitCode = interruptionExitCode ?? 1;
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
if (
|
|
699
|
+
process.argv[1] &&
|
|
700
|
+
import.meta.url === pathToFileURL(realpathSync(process.argv[1])).href
|
|
701
|
+
) {
|
|
702
|
+
await main();
|
|
703
|
+
}
|