dev3000 0.0.181 → 0.0.183
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 +36 -170
- package/bin/d3k-runtime +2 -0
- package/bin/dev3000 +2 -119
- package/dist/cdp-monitor.d.ts +4 -0
- package/dist/cdp-monitor.d.ts.map +1 -1
- package/dist/cdp-monitor.js +21 -5
- package/dist/cdp-monitor.js.map +1 -1
- package/dist/cli.js +4 -53
- package/dist/cli.js.map +1 -1
- package/dist/dev-environment.d.ts.map +1 -1
- package/dist/dev-environment.js +8 -46
- package/dist/dev-environment.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime/browser-events.d.ts +5 -0
- package/dist/runtime/browser-events.d.ts.map +1 -0
- package/dist/runtime/browser-events.js +87 -0
- package/dist/runtime/browser-events.js.map +1 -0
- package/dist/runtime/cli.d.ts +3 -0
- package/dist/runtime/cli.d.ts.map +1 -0
- package/dist/runtime/cli.js +83 -0
- package/dist/runtime/cli.js.map +1 -0
- package/dist/runtime/runtime.d.ts +10 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +346 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/session.d.ts +18 -0
- package/dist/runtime/session.d.ts.map +1 -0
- package/dist/runtime/session.js +61 -0
- package/dist/runtime/session.js.map +1 -0
- package/dist/runtime/timeline.d.ts +40 -0
- package/dist/runtime/timeline.d.ts.map +1 -0
- package/dist/runtime/timeline.js +69 -0
- package/dist/runtime/timeline.js.map +1 -0
- package/dist/skills/d3k/internal-skill.md +20 -131
- package/dist/skills/index.d.ts +54 -0
- package/dist/skills/index.d.ts.map +1 -0
- package/dist/skills/index.js +214 -0
- package/dist/skills/index.js.map +1 -0
- package/dist/utils/d3k-skill-installer.d.ts +4 -0
- package/dist/utils/d3k-skill-installer.d.ts.map +1 -0
- package/dist/utils/d3k-skill-installer.js +30 -0
- package/dist/utils/d3k-skill-installer.js.map +1 -0
- package/dist/utils/version-check.d.ts +1 -21
- package/dist/utils/version-check.d.ts.map +1 -1
- package/dist/utils/version-check.js +2 -97
- package/dist/utils/version-check.js.map +1 -1
- package/package.json +7 -16
- package/skills/d3k/PUBLISH.md +46 -0
- package/skills/d3k/SKILL.md +32 -0
- package/skills/d3k/agents/openai.yaml +4 -0
- package/skills/d3k/scripts/runtime.mjs +21 -0
- package/dist/commands/skill-runner.d.ts +0 -40
- package/dist/commands/skill-runner.d.ts.map +0 -1
- package/dist/commands/skill-runner.js +0 -826
- package/dist/commands/skill-runner.js.map +0 -1
- package/dist/skills/index.test.ts +0 -203
- package/dist/skills/index.ts +0 -259
|
@@ -1,826 +0,0 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
|
-
import { existsSync, readFileSync } from "fs";
|
|
3
|
-
import ora from "ora";
|
|
4
|
-
import { homedir } from "os";
|
|
5
|
-
import { dirname, join, resolve } from "path";
|
|
6
|
-
const DEFAULT_DEV3000_BASE_URL = "https://dev3000.ai";
|
|
7
|
-
const REMOTE_SKILL_OPTIONS = ["team", "project", "branch", "projectDir", "baseUrl", "wait", "json", "yes"];
|
|
8
|
-
export function shouldUseRemoteSkillRunner(name, options) {
|
|
9
|
-
if (!name?.trim())
|
|
10
|
-
return false;
|
|
11
|
-
const hasExplicitRunOption = REMOTE_SKILL_OPTIONS.some((option) => Boolean(options[option])) ||
|
|
12
|
-
options.install === false;
|
|
13
|
-
if (hasExplicitRunOption) {
|
|
14
|
-
return true;
|
|
15
|
-
}
|
|
16
|
-
try {
|
|
17
|
-
return Boolean(findLinkedVercelProject());
|
|
18
|
-
}
|
|
19
|
-
catch {
|
|
20
|
-
return true;
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
export async function runRemoteSkillCommand(name, options) {
|
|
24
|
-
const requestedSkillRunnerId = name?.trim();
|
|
25
|
-
if (!requestedSkillRunnerId) {
|
|
26
|
-
throw new Error("A skill name is required. Example: d3k skill vercel-optimize --team elsigh-pro --project cranio-mom");
|
|
27
|
-
}
|
|
28
|
-
const linkedProject = findLinkedVercelProject();
|
|
29
|
-
const teamInput = options.team?.trim() || linkedProject?.orgId;
|
|
30
|
-
const projectInput = options.project?.trim() || linkedProject?.projectId;
|
|
31
|
-
if (!teamInput || !projectInput) {
|
|
32
|
-
throw new Error("Could not infer the Vercel team/project. Run from a directory linked with `vercel link`, or pass `--team` and `--project`.");
|
|
33
|
-
}
|
|
34
|
-
const token = resolveVercelAccessToken();
|
|
35
|
-
if (!token) {
|
|
36
|
-
throw new Error("No Vercel token found. Run `vercel login`, or set VERCEL_TOKEN to a token that can access the target team.");
|
|
37
|
-
}
|
|
38
|
-
const baseUrl = normalizeBaseUrl(options.baseUrl || process.env.D3K_CLOUD_URL || DEFAULT_DEV3000_BASE_URL);
|
|
39
|
-
log(options, `Preparing ${chalk.bold(requestedSkillRunnerId)} on ${chalk.bold(projectInput)}...`);
|
|
40
|
-
if (linkedProject && (!options.team || !options.project)) {
|
|
41
|
-
log(options, `${chalk.green("✓")} Inferred Vercel project link from ${linkedProject.path}`);
|
|
42
|
-
}
|
|
43
|
-
const [user, teams] = await Promise.all([fetchVercelUser(token), fetchVercelTeams(token)]);
|
|
44
|
-
const team = resolveTeam(teamInput, user, teams);
|
|
45
|
-
const project = await resolveProject(token, team, projectInput);
|
|
46
|
-
const githubRepo = getProjectGitHubRepo(project);
|
|
47
|
-
if (!githubRepo) {
|
|
48
|
-
throw new Error(`Project ${project.name} is not connected to a GitHub repository. Remote skill runs require a GitHub-backed Vercel project.`);
|
|
49
|
-
}
|
|
50
|
-
const latestDeployment = project.latestDeployments?.[0];
|
|
51
|
-
const branch = options.branch?.trim() || latestDeployment?.gitSource?.ref || latestDeployment?.gitSource?.sha || "main";
|
|
52
|
-
const projectDir = options.projectDir?.trim() || project.rootDirectory?.trim() || undefined;
|
|
53
|
-
const productionUrl = latestDeployment?.url ? `https://${latestDeployment.url}` : undefined;
|
|
54
|
-
const teamScope = team.isPersonal ? team.slug : team.id;
|
|
55
|
-
const reportBasePath = `${baseUrl}/${encodeURIComponent(team.slug)}/skill-runner/runs`;
|
|
56
|
-
log(options, `${chalk.green("✓")} Team: ${team.name} (${team.slug})`);
|
|
57
|
-
log(options, `${chalk.green("✓")} Project: ${project.name} (${githubRepo.owner}/${githubRepo.repo})`);
|
|
58
|
-
const skillRunnerId = await resolveSkillRunnerSelection({
|
|
59
|
-
baseUrl,
|
|
60
|
-
token,
|
|
61
|
-
team,
|
|
62
|
-
requestedSkillRunnerId,
|
|
63
|
-
options
|
|
64
|
-
});
|
|
65
|
-
log(options, `${chalk.green("✓")} Skill: ${skillRunnerId}`);
|
|
66
|
-
if (options.install !== false) {
|
|
67
|
-
await ensureWorkerProjectReady(baseUrl, token, team, options);
|
|
68
|
-
}
|
|
69
|
-
const startRunSpinner = startSpinner(options, "Starting run...");
|
|
70
|
-
let startResult;
|
|
71
|
-
try {
|
|
72
|
-
startResult = await startSkillRun({
|
|
73
|
-
baseUrl,
|
|
74
|
-
token,
|
|
75
|
-
user,
|
|
76
|
-
skillRunnerId,
|
|
77
|
-
team,
|
|
78
|
-
teamScope,
|
|
79
|
-
project,
|
|
80
|
-
githubRepo,
|
|
81
|
-
branch,
|
|
82
|
-
projectDir,
|
|
83
|
-
productionUrl
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
catch (error) {
|
|
87
|
-
startRunSpinner?.fail("Failed to start run");
|
|
88
|
-
throw error;
|
|
89
|
-
}
|
|
90
|
-
if (startResult.code === "runner_setup_required" && options.install !== false) {
|
|
91
|
-
startRunSpinner?.stop();
|
|
92
|
-
await confirmWorkerProjectInstall(team, "Team skill runner project needs setup before this run can start.", options);
|
|
93
|
-
await installWorkerProject(baseUrl, token, team, options);
|
|
94
|
-
const retryStartRunSpinner = startSpinner(options, "Starting run...");
|
|
95
|
-
try {
|
|
96
|
-
startResult = await startSkillRun({
|
|
97
|
-
baseUrl,
|
|
98
|
-
token,
|
|
99
|
-
user,
|
|
100
|
-
skillRunnerId,
|
|
101
|
-
team,
|
|
102
|
-
teamScope,
|
|
103
|
-
project,
|
|
104
|
-
githubRepo,
|
|
105
|
-
branch,
|
|
106
|
-
projectDir,
|
|
107
|
-
productionUrl
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
catch (error) {
|
|
111
|
-
retryStartRunSpinner?.fail("Failed to start run");
|
|
112
|
-
throw error;
|
|
113
|
-
}
|
|
114
|
-
if (startResult.success && startResult.runId) {
|
|
115
|
-
retryStartRunSpinner?.stop();
|
|
116
|
-
}
|
|
117
|
-
else {
|
|
118
|
-
retryStartRunSpinner?.fail("Failed to start run");
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else if (startResult.success && startResult.runId) {
|
|
122
|
-
startRunSpinner?.stop();
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
startRunSpinner?.fail("Failed to start run");
|
|
126
|
-
}
|
|
127
|
-
if (!startResult.success || !startResult.runId) {
|
|
128
|
-
const error = startResult.error || "Failed to start DeepSec run.";
|
|
129
|
-
throw new Error(error);
|
|
130
|
-
}
|
|
131
|
-
const reportUrl = `${reportBasePath}/${encodeURIComponent(startResult.runId)}/report`;
|
|
132
|
-
const output = {
|
|
133
|
-
success: true,
|
|
134
|
-
skill: skillRunnerId,
|
|
135
|
-
runId: startResult.runId,
|
|
136
|
-
reportUrl,
|
|
137
|
-
team: team.slug,
|
|
138
|
-
project: project.name
|
|
139
|
-
};
|
|
140
|
-
if (options.json && !options.wait) {
|
|
141
|
-
console.log(JSON.stringify(output, null, 2));
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
log(options, `${chalk.green("✓")} Started run ${startResult.runId}`);
|
|
145
|
-
log(options, `${chalk.bold("Report:")} ${reportUrl}`);
|
|
146
|
-
if (options.wait) {
|
|
147
|
-
const run = await waitForRun(baseUrl, user.id, startResult.runId, options);
|
|
148
|
-
if (options.json) {
|
|
149
|
-
console.log(JSON.stringify({ ...output, run }, null, 2));
|
|
150
|
-
return;
|
|
151
|
-
}
|
|
152
|
-
log(options, formatRunCompletion(run, reportUrl));
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
function resolveVercelAccessToken() {
|
|
156
|
-
const explicitToken = process.env.VERCEL_TOKEN?.trim();
|
|
157
|
-
if (explicitToken) {
|
|
158
|
-
return explicitToken;
|
|
159
|
-
}
|
|
160
|
-
const homeDirectory = homedir();
|
|
161
|
-
const xdgDataHome = process.env.XDG_DATA_HOME || join(homeDirectory, ".local", "share");
|
|
162
|
-
const candidates = [
|
|
163
|
-
join(xdgDataHome, "com.vercel.cli", "auth.json"),
|
|
164
|
-
join(homeDirectory, "Library", "Application Support", "com.vercel.cli", "auth.json"),
|
|
165
|
-
join(homeDirectory, ".now", "auth.json"),
|
|
166
|
-
join(homeDirectory, ".vercel", "auth.json")
|
|
167
|
-
];
|
|
168
|
-
for (const candidate of candidates) {
|
|
169
|
-
try {
|
|
170
|
-
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
171
|
-
const token = parsed.token?.trim();
|
|
172
|
-
const expiresAt = typeof parsed.expiresAt === "number" ? parsed.expiresAt : null;
|
|
173
|
-
if (token && (!expiresAt || expiresAt > Math.floor(Date.now() / 1000) + 60)) {
|
|
174
|
-
return token;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
catch {
|
|
178
|
-
// Try the next known Vercel CLI auth location.
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
return null;
|
|
182
|
-
}
|
|
183
|
-
function findLinkedVercelProject(startDirectory = process.cwd()) {
|
|
184
|
-
let currentDirectory = resolve(startDirectory);
|
|
185
|
-
for (;;) {
|
|
186
|
-
const candidate = join(currentDirectory, ".vercel", "project.json");
|
|
187
|
-
if (existsSync(candidate)) {
|
|
188
|
-
try {
|
|
189
|
-
const parsed = JSON.parse(readFileSync(candidate, "utf8"));
|
|
190
|
-
const orgId = stringValue(parsed.orgId);
|
|
191
|
-
const projectId = stringValue(parsed.projectId);
|
|
192
|
-
if (orgId || projectId) {
|
|
193
|
-
return { orgId, projectId, path: candidate };
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
catch (error) {
|
|
197
|
-
throw new Error(`Could not read ${candidate}. Re-run \`vercel link\` or fix the invalid project.json. ${error instanceof Error ? error.message : String(error)}`);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
const parentDirectory = dirname(currentDirectory);
|
|
201
|
-
if (parentDirectory === currentDirectory) {
|
|
202
|
-
return null;
|
|
203
|
-
}
|
|
204
|
-
currentDirectory = parentDirectory;
|
|
205
|
-
}
|
|
206
|
-
}
|
|
207
|
-
async function fetchVercelUser(token) {
|
|
208
|
-
const data = await fetchJson("https://api.vercel.com/v2/user", {
|
|
209
|
-
headers: authorizationHeaders(token)
|
|
210
|
-
});
|
|
211
|
-
const rawUser = data.user || {};
|
|
212
|
-
const id = stringValue(rawUser.uid) || stringValue(rawUser.id);
|
|
213
|
-
const username = stringValue(rawUser.username);
|
|
214
|
-
if (!id || !username) {
|
|
215
|
-
throw new Error("Could not resolve the signed-in Vercel user from the configured token.");
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
id,
|
|
219
|
-
username,
|
|
220
|
-
email: stringValue(rawUser.email) || "",
|
|
221
|
-
name: stringValue(rawUser.name) || username
|
|
222
|
-
};
|
|
223
|
-
}
|
|
224
|
-
async function fetchVercelTeams(token) {
|
|
225
|
-
const teams = [];
|
|
226
|
-
const seen = new Set();
|
|
227
|
-
let until = null;
|
|
228
|
-
for (let page = 0; page < 20; page++) {
|
|
229
|
-
const apiUrl = new URL("https://api.vercel.com/v2/teams");
|
|
230
|
-
apiUrl.searchParams.set("limit", "100");
|
|
231
|
-
if (until) {
|
|
232
|
-
apiUrl.searchParams.set("until", until);
|
|
233
|
-
}
|
|
234
|
-
const data = await fetchJson(apiUrl.toString(), {
|
|
235
|
-
headers: authorizationHeaders(token)
|
|
236
|
-
});
|
|
237
|
-
const pageTeams = Array.isArray(data.teams) ? data.teams : [];
|
|
238
|
-
for (const rawTeam of pageTeams) {
|
|
239
|
-
const id = stringValue(rawTeam.id);
|
|
240
|
-
const slug = stringValue(rawTeam.slug);
|
|
241
|
-
const name = stringValue(rawTeam.name);
|
|
242
|
-
if (!id || !slug || !name || seen.has(id.toLowerCase())) {
|
|
243
|
-
continue;
|
|
244
|
-
}
|
|
245
|
-
seen.add(id.toLowerCase());
|
|
246
|
-
teams.push({ id, slug, name, isPersonal: false });
|
|
247
|
-
}
|
|
248
|
-
const nextCursor = data.pagination?.next;
|
|
249
|
-
if (!nextCursor || pageTeams.length === 0) {
|
|
250
|
-
break;
|
|
251
|
-
}
|
|
252
|
-
until = String(nextCursor);
|
|
253
|
-
}
|
|
254
|
-
return teams;
|
|
255
|
-
}
|
|
256
|
-
function resolveTeam(input, user, teams) {
|
|
257
|
-
const normalizedInput = input.toLowerCase();
|
|
258
|
-
const personalTeam = {
|
|
259
|
-
id: user.id,
|
|
260
|
-
slug: user.username,
|
|
261
|
-
name: user.name || user.username,
|
|
262
|
-
isPersonal: true
|
|
263
|
-
};
|
|
264
|
-
const allTeams = [personalTeam, ...teams];
|
|
265
|
-
const team = allTeams.find((candidate) => candidate.id.toLowerCase() === normalizedInput) ||
|
|
266
|
-
allTeams.find((candidate) => candidate.slug.toLowerCase() === normalizedInput) ||
|
|
267
|
-
allTeams.find((candidate) => candidate.name.toLowerCase() === normalizedInput);
|
|
268
|
-
if (!team) {
|
|
269
|
-
const available = allTeams.map((candidate) => candidate.slug).join(", ");
|
|
270
|
-
throw new Error(`Could not find Vercel team "${input}". Available teams: ${available}`);
|
|
271
|
-
}
|
|
272
|
-
return team;
|
|
273
|
-
}
|
|
274
|
-
async function resolveProject(token, team, input) {
|
|
275
|
-
const fetchedProjects = await fetchProjects(token, team, input.startsWith("prj_") ? undefined : input);
|
|
276
|
-
const normalizedInput = input.toLowerCase();
|
|
277
|
-
const project = fetchedProjects.find((candidate) => candidate.id?.toLowerCase() === normalizedInput) ||
|
|
278
|
-
fetchedProjects.find((candidate) => candidate.name?.toLowerCase() === normalizedInput);
|
|
279
|
-
if (!project?.id || !project.name) {
|
|
280
|
-
const available = fetchedProjects
|
|
281
|
-
.filter((candidate) => candidate.name && !isSkillRunnerWorkerProject(candidate.name))
|
|
282
|
-
.slice(0, 10)
|
|
283
|
-
.map((candidate) => candidate.name)
|
|
284
|
-
.join(", ");
|
|
285
|
-
throw new Error(`Could not find Vercel project "${input}" in ${team.slug}.${available ? ` Nearby projects: ${available}` : ""}`);
|
|
286
|
-
}
|
|
287
|
-
if (isSkillRunnerWorkerProject(project.name)) {
|
|
288
|
-
throw new Error("The d3k-skill-runner project cannot be used as the scan target.");
|
|
289
|
-
}
|
|
290
|
-
return project;
|
|
291
|
-
}
|
|
292
|
-
async function fetchProjects(token, team, search) {
|
|
293
|
-
const projects = [];
|
|
294
|
-
let until = null;
|
|
295
|
-
for (let page = 0; page < 20; page++) {
|
|
296
|
-
const apiUrl = new URL("https://api.vercel.com/v9/projects");
|
|
297
|
-
if (!team.isPersonal) {
|
|
298
|
-
apiUrl.searchParams.set("teamId", team.id);
|
|
299
|
-
}
|
|
300
|
-
if (search) {
|
|
301
|
-
apiUrl.searchParams.set("search", search);
|
|
302
|
-
}
|
|
303
|
-
apiUrl.searchParams.set("limit", "100");
|
|
304
|
-
if (until) {
|
|
305
|
-
apiUrl.searchParams.set("until", until);
|
|
306
|
-
}
|
|
307
|
-
const data = await fetchJson(apiUrl.toString(), {
|
|
308
|
-
headers: authorizationHeaders(token)
|
|
309
|
-
});
|
|
310
|
-
const pageProjects = Array.isArray(data.projects) ? data.projects : [];
|
|
311
|
-
projects.push(...pageProjects);
|
|
312
|
-
const nextCursor = data.pagination?.next;
|
|
313
|
-
if (!nextCursor || pageProjects.length === 0) {
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
until = String(nextCursor);
|
|
317
|
-
}
|
|
318
|
-
return projects;
|
|
319
|
-
}
|
|
320
|
-
function getProjectGitHubRepo(project) {
|
|
321
|
-
const linkedOwner = project.link?.org?.trim();
|
|
322
|
-
const linkedRepo = project.link?.repo?.trim();
|
|
323
|
-
if (linkedOwner && linkedRepo) {
|
|
324
|
-
return { owner: linkedOwner, repo: linkedRepo };
|
|
325
|
-
}
|
|
326
|
-
const deploymentWithGitHubMeta = project.latestDeployments?.find((deployment) => deployment.meta?.githubOrg?.trim() && deployment.meta?.githubRepo?.trim());
|
|
327
|
-
const metaOwner = deploymentWithGitHubMeta?.meta?.githubOrg?.trim();
|
|
328
|
-
const metaRepo = deploymentWithGitHubMeta?.meta?.githubRepo?.trim();
|
|
329
|
-
return metaOwner && metaRepo ? { owner: metaOwner, repo: metaRepo } : null;
|
|
330
|
-
}
|
|
331
|
-
async function resolveSkillRunnerSelection(input) {
|
|
332
|
-
const url = new URL(`${input.baseUrl}/api/skill-runners/resolve`);
|
|
333
|
-
url.searchParams.set("team", input.team.id);
|
|
334
|
-
url.searchParams.set("q", input.requestedSkillRunnerId);
|
|
335
|
-
const response = await fetchJsonResponse(url.toString(), {
|
|
336
|
-
headers: authorizationHeaders(input.token)
|
|
337
|
-
});
|
|
338
|
-
if (!response.ok || !response.data.success) {
|
|
339
|
-
throw new Error(response.data.error || `Failed to resolve skill runner (${response.status}).`);
|
|
340
|
-
}
|
|
341
|
-
if (response.data.exactMatch?.id) {
|
|
342
|
-
return response.data.exactMatch.id;
|
|
343
|
-
}
|
|
344
|
-
const candidates = response.data.candidates || [];
|
|
345
|
-
if (candidates.length === 0) {
|
|
346
|
-
throw new Error(`No existing skill runner or skills.sh match found for "${input.requestedSkillRunnerId}". Try the full skills.sh path, for example owner/repo/skill-name.`);
|
|
347
|
-
}
|
|
348
|
-
const selected = await confirmSkillRunnerCandidate(input.requestedSkillRunnerId, candidates, input.options);
|
|
349
|
-
return selected.canonicalPath;
|
|
350
|
-
}
|
|
351
|
-
async function confirmSkillRunnerCandidate(requestedSkillRunnerId, candidates, options) {
|
|
352
|
-
if (options.yes) {
|
|
353
|
-
if (candidates.length === 1) {
|
|
354
|
-
const [candidate] = candidates;
|
|
355
|
-
log(options, `${chalk.green("✓")} Matched skills.sh skill: ${candidate.displayName} (${candidate.sourceUrl})`);
|
|
356
|
-
return candidate;
|
|
357
|
-
}
|
|
358
|
-
throw new Error(formatSkillRunnerCandidateRequiredMessage(requestedSkillRunnerId, candidates));
|
|
359
|
-
}
|
|
360
|
-
if (options.json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
361
|
-
throw new Error(formatSkillRunnerCandidateRequiredMessage(requestedSkillRunnerId, candidates));
|
|
362
|
-
}
|
|
363
|
-
if (candidates.length === 1) {
|
|
364
|
-
const [candidate] = candidates;
|
|
365
|
-
console.log(`\n${chalk.bold("Found a skills.sh match for")} ${chalk.cyan(requestedSkillRunnerId)}:`);
|
|
366
|
-
printSkillRunnerCandidate(candidate);
|
|
367
|
-
const answer = await promptLine("Run this skill? [y/N] ");
|
|
368
|
-
if (isYesAnswer(answer)) {
|
|
369
|
-
return candidate;
|
|
370
|
-
}
|
|
371
|
-
throw new Error("Cancelled skill run.");
|
|
372
|
-
}
|
|
373
|
-
console.log(`\n${chalk.bold("Found multiple skills.sh matches for")} ${chalk.cyan(requestedSkillRunnerId)}:`);
|
|
374
|
-
candidates.forEach((candidate, index) => {
|
|
375
|
-
printSkillRunnerCandidate(candidate, index + 1);
|
|
376
|
-
});
|
|
377
|
-
for (;;) {
|
|
378
|
-
const answer = await promptLine(`Select a skill to run [1-${candidates.length}] or q: `);
|
|
379
|
-
if (/^(q|quit|cancel)$/i.test(answer)) {
|
|
380
|
-
throw new Error("Cancelled skill run.");
|
|
381
|
-
}
|
|
382
|
-
const selectedIndex = Number(answer);
|
|
383
|
-
if (Number.isInteger(selectedIndex) && selectedIndex >= 1 && selectedIndex <= candidates.length) {
|
|
384
|
-
return candidates[selectedIndex - 1];
|
|
385
|
-
}
|
|
386
|
-
console.log(chalk.yellow(`Enter a number from 1 to ${candidates.length}, or q to cancel.`));
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
function printSkillRunnerCandidate(candidate, index) {
|
|
390
|
-
const prefix = typeof index === "number" ? `${index}. ` : " ";
|
|
391
|
-
console.log(`${prefix}${chalk.bold(candidate.displayName)}`);
|
|
392
|
-
console.log(` ${chalk.gray(candidate.sourceUrl)}`);
|
|
393
|
-
console.log(` ${chalk.gray(`install: ${candidate.installArg}`)}`);
|
|
394
|
-
if (candidate.installsLabel) {
|
|
395
|
-
console.log(` ${chalk.gray(candidate.installsLabel)}`);
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
function formatSkillRunnerCandidateRequiredMessage(requestedSkillRunnerId, candidates) {
|
|
399
|
-
const choices = candidates
|
|
400
|
-
.slice(0, 5)
|
|
401
|
-
.map((candidate) => `${candidate.displayName} (${candidate.sourceUrl})`)
|
|
402
|
-
.join("; ");
|
|
403
|
-
const suffix = candidates.length === 1
|
|
404
|
-
? "Re-run with the full skill path or pass --yes to accept this single match."
|
|
405
|
-
: "Re-run with the full skill path for the skill you want.";
|
|
406
|
-
return `Skill "${requestedSkillRunnerId}" does not exactly match an existing team skill runner. Candidate${candidates.length === 1 ? "" : "s"}: ${choices}. ${suffix}`;
|
|
407
|
-
}
|
|
408
|
-
async function promptLine(question) {
|
|
409
|
-
const { createInterface } = await import("readline/promises");
|
|
410
|
-
const readline = createInterface({
|
|
411
|
-
input: process.stdin,
|
|
412
|
-
output: process.stdout
|
|
413
|
-
});
|
|
414
|
-
try {
|
|
415
|
-
return (await readline.question(question)).trim();
|
|
416
|
-
}
|
|
417
|
-
finally {
|
|
418
|
-
readline.close();
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
function isYesAnswer(value) {
|
|
422
|
-
return /^(y|yes)$/i.test(value.trim());
|
|
423
|
-
}
|
|
424
|
-
function isNoAnswer(value) {
|
|
425
|
-
return /^(n|no)$/i.test(value.trim());
|
|
426
|
-
}
|
|
427
|
-
function isDefaultYesAnswer(value) {
|
|
428
|
-
const trimmed = value.trim();
|
|
429
|
-
return trimmed.length === 0 || isYesAnswer(trimmed);
|
|
430
|
-
}
|
|
431
|
-
export function getWorkerProjectSetupExplanation(teamName) {
|
|
432
|
-
return [
|
|
433
|
-
`d3k-skill-runner creates a small runner project in ${teamName}. Skill runs execute there so compute, AI Gateway usage, deployments, and runtime logs belong to the team running the scan.`,
|
|
434
|
-
"For first-time setup, choose all projects in this team when Vercel asks for project access. Single-project grants cannot include the new runner project."
|
|
435
|
-
];
|
|
436
|
-
}
|
|
437
|
-
export function getWorkerProjectReadiness(response) {
|
|
438
|
-
const workerStatus = response.settings?.workerStatus;
|
|
439
|
-
const project = response.project;
|
|
440
|
-
const workerBaseUrl = project?.workerBaseUrl || response.settings?.workerBaseUrl;
|
|
441
|
-
if (!response.installed) {
|
|
442
|
-
return {
|
|
443
|
-
ready: false,
|
|
444
|
-
message: response.message || "Team skill runner project is not installed yet."
|
|
445
|
-
};
|
|
446
|
-
}
|
|
447
|
-
if (project?.missingEnvKeys?.length) {
|
|
448
|
-
return {
|
|
449
|
-
ready: false,
|
|
450
|
-
message: `Team skill runner project is missing required environment variables: ${project.missingEnvKeys.join(", ")}.`
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
if (project?.shellVersionStatus === "outdated" || workerStatus === "outdated") {
|
|
454
|
-
return {
|
|
455
|
-
ready: false,
|
|
456
|
-
message: "Team skill runner project needs an update before this run can start."
|
|
457
|
-
};
|
|
458
|
-
}
|
|
459
|
-
if (!workerBaseUrl || workerStatus === "provisioning") {
|
|
460
|
-
return {
|
|
461
|
-
ready: false,
|
|
462
|
-
message: "Team skill runner project exists, but its deployment is not ready yet."
|
|
463
|
-
};
|
|
464
|
-
}
|
|
465
|
-
if (workerStatus === "error") {
|
|
466
|
-
return {
|
|
467
|
-
ready: false,
|
|
468
|
-
message: "Team skill runner project exists, but its latest deployment failed."
|
|
469
|
-
};
|
|
470
|
-
}
|
|
471
|
-
return {
|
|
472
|
-
ready: workerStatus === "ready",
|
|
473
|
-
message: workerStatus === "ready"
|
|
474
|
-
? "Team skill runner project is ready."
|
|
475
|
-
: `Team skill runner project is not ready yet${workerStatus ? ` (status: ${workerStatus})` : ""}.`
|
|
476
|
-
};
|
|
477
|
-
}
|
|
478
|
-
async function confirmWorkerProjectInstall(team, reason, options) {
|
|
479
|
-
const explanation = getWorkerProjectSetupExplanation(team.name);
|
|
480
|
-
if (options.yes) {
|
|
481
|
-
log(options, `${chalk.yellow("!")} ${reason}`);
|
|
482
|
-
for (const line of explanation) {
|
|
483
|
-
log(options, chalk.gray(line));
|
|
484
|
-
}
|
|
485
|
-
return;
|
|
486
|
-
}
|
|
487
|
-
const message = `Install or repair d3k-skill-runner in ${team.name} now?`;
|
|
488
|
-
if (options.json || !process.stdin.isTTY || !process.stdout.isTTY) {
|
|
489
|
-
throw new Error(`${reason}\n${explanation.join("\n")}\n${message} Re-run with --yes to approve runner project setup.`);
|
|
490
|
-
}
|
|
491
|
-
console.log(`\n${chalk.bold("Team skill runner setup required")}`);
|
|
492
|
-
console.log(reason);
|
|
493
|
-
for (const line of explanation) {
|
|
494
|
-
console.log(chalk.gray(line));
|
|
495
|
-
}
|
|
496
|
-
const answer = await promptLine(`Install or repair d3k-skill-runner in ${team.name}? [Y/n] `);
|
|
497
|
-
if (isNoAnswer(answer) || !isDefaultYesAnswer(answer)) {
|
|
498
|
-
throw new Error("Cancelled skill run.");
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
async function validateWorkerProject(baseUrl, token, team) {
|
|
502
|
-
const url = new URL(`${baseUrl}/api/skill-runner-teams/worker`);
|
|
503
|
-
url.searchParams.set("team", team.id);
|
|
504
|
-
const response = await fetchJsonResponse(url.toString(), {
|
|
505
|
-
headers: authorizationHeaders(token)
|
|
506
|
-
});
|
|
507
|
-
if (!response.ok || !response.data?.success) {
|
|
508
|
-
throw new Error(response.data?.error || `Failed to validate runner project (${response.status}).`);
|
|
509
|
-
}
|
|
510
|
-
return response.data;
|
|
511
|
-
}
|
|
512
|
-
async function ensureWorkerProjectReady(baseUrl, token, team, options) {
|
|
513
|
-
const validationSpinner = startSpinner(options, "Checking team skill runner project...");
|
|
514
|
-
let validation;
|
|
515
|
-
try {
|
|
516
|
-
validation = await validateWorkerProject(baseUrl, token, team);
|
|
517
|
-
}
|
|
518
|
-
catch (error) {
|
|
519
|
-
validationSpinner?.fail("Failed to check team skill runner project");
|
|
520
|
-
throw error;
|
|
521
|
-
}
|
|
522
|
-
const readiness = getWorkerProjectReadiness(validation);
|
|
523
|
-
if (readiness.ready) {
|
|
524
|
-
validationSpinner?.stop();
|
|
525
|
-
log(options, `${chalk.green("✓")} Team skill runner project is ready`);
|
|
526
|
-
return;
|
|
527
|
-
}
|
|
528
|
-
validationSpinner?.stop();
|
|
529
|
-
await confirmWorkerProjectInstall(team, readiness.message, options);
|
|
530
|
-
await installWorkerProject(baseUrl, token, team, options);
|
|
531
|
-
}
|
|
532
|
-
async function installWorkerProject(baseUrl, token, team, options) {
|
|
533
|
-
const installSpinner = startSpinner(options, "Installing team skill runner project...");
|
|
534
|
-
if (!installSpinner) {
|
|
535
|
-
log(options, "Installing team skill runner project...");
|
|
536
|
-
}
|
|
537
|
-
let response;
|
|
538
|
-
try {
|
|
539
|
-
response = options.json
|
|
540
|
-
? await installWorkerProjectJson(baseUrl, token, team)
|
|
541
|
-
: await installWorkerProjectStream(baseUrl, token, team, options, installSpinner);
|
|
542
|
-
}
|
|
543
|
-
catch (error) {
|
|
544
|
-
installSpinner?.fail("Failed to install team skill runner project");
|
|
545
|
-
throw error;
|
|
546
|
-
}
|
|
547
|
-
if (!response.ok || !response.data?.success) {
|
|
548
|
-
installSpinner?.fail("Failed to install team skill runner project");
|
|
549
|
-
throw new Error(response.data?.error || `Failed to install runner project (${response.status}).`);
|
|
550
|
-
}
|
|
551
|
-
const workerStatus = response.data.settings?.workerStatus;
|
|
552
|
-
const project = response.data.project;
|
|
553
|
-
const isReady = response.data.installed &&
|
|
554
|
-
Boolean(project?.workerBaseUrl || response.data.settings?.workerBaseUrl) &&
|
|
555
|
-
!project?.missingEnvKeys?.length &&
|
|
556
|
-
project?.shellVersionStatus !== "outdated" &&
|
|
557
|
-
workerStatus === "ready";
|
|
558
|
-
if (!isReady) {
|
|
559
|
-
const message = response.data.message ||
|
|
560
|
-
`Runner project is not ready yet${workerStatus ? ` (status: ${workerStatus})` : ""}. Open the report link after setup completes, then retry.`;
|
|
561
|
-
installSpinner?.fail("Team skill runner project is not ready");
|
|
562
|
-
throw new Error(message);
|
|
563
|
-
}
|
|
564
|
-
if (installSpinner) {
|
|
565
|
-
installSpinner.succeed("Team skill runner project is ready");
|
|
566
|
-
}
|
|
567
|
-
else {
|
|
568
|
-
log(options, `${chalk.green("✓")} Team skill runner project is ready`);
|
|
569
|
-
}
|
|
570
|
-
}
|
|
571
|
-
async function installWorkerProjectJson(baseUrl, token, team) {
|
|
572
|
-
return fetchJsonResponse(`${baseUrl}/api/skill-runner-teams/worker`, {
|
|
573
|
-
method: "POST",
|
|
574
|
-
headers: {
|
|
575
|
-
...authorizationHeaders(token),
|
|
576
|
-
"Content-Type": "application/json"
|
|
577
|
-
},
|
|
578
|
-
body: JSON.stringify({ team: team.id })
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
async function installWorkerProjectStream(baseUrl, token, team, options, installSpinner) {
|
|
582
|
-
const response = await fetch(`${baseUrl}/api/skill-runner-teams/worker?stream=1`, {
|
|
583
|
-
method: "POST",
|
|
584
|
-
headers: {
|
|
585
|
-
...authorizationHeaders(token),
|
|
586
|
-
"Content-Type": "application/json"
|
|
587
|
-
},
|
|
588
|
-
body: JSON.stringify({ team: team.id })
|
|
589
|
-
});
|
|
590
|
-
if (!response.body || !response.headers.get("content-type")?.includes("application/x-ndjson")) {
|
|
591
|
-
const text = await response.text();
|
|
592
|
-
let data;
|
|
593
|
-
try {
|
|
594
|
-
data = text.trim() ? JSON.parse(text) : {};
|
|
595
|
-
}
|
|
596
|
-
catch {
|
|
597
|
-
data = {
|
|
598
|
-
success: false,
|
|
599
|
-
error: `Runner install returned an unexpected response (${response.status}): ${text.slice(0, 300)}`
|
|
600
|
-
};
|
|
601
|
-
}
|
|
602
|
-
return {
|
|
603
|
-
ok: response.ok,
|
|
604
|
-
status: response.status,
|
|
605
|
-
data
|
|
606
|
-
};
|
|
607
|
-
}
|
|
608
|
-
const decoder = new TextDecoder();
|
|
609
|
-
const reader = response.body.getReader();
|
|
610
|
-
let buffer = "";
|
|
611
|
-
let finalResponse = null;
|
|
612
|
-
for (;;) {
|
|
613
|
-
const { done, value } = await reader.read();
|
|
614
|
-
if (value) {
|
|
615
|
-
buffer += decoder.decode(value, { stream: !done });
|
|
616
|
-
const lines = buffer.split("\n");
|
|
617
|
-
buffer = lines.pop() || "";
|
|
618
|
-
for (const line of lines) {
|
|
619
|
-
const event = parseWorkerInstallStreamEvent(line);
|
|
620
|
-
if (!event)
|
|
621
|
-
continue;
|
|
622
|
-
if (event.type === "progress") {
|
|
623
|
-
const message = event.message?.trim();
|
|
624
|
-
if (message) {
|
|
625
|
-
if (installSpinner) {
|
|
626
|
-
installSpinner.text = message;
|
|
627
|
-
}
|
|
628
|
-
else {
|
|
629
|
-
log(options, message);
|
|
630
|
-
}
|
|
631
|
-
}
|
|
632
|
-
continue;
|
|
633
|
-
}
|
|
634
|
-
finalResponse = {
|
|
635
|
-
ok: event.type === "result" && response.ok,
|
|
636
|
-
status: event.status || response.status,
|
|
637
|
-
data: event.data || {}
|
|
638
|
-
};
|
|
639
|
-
}
|
|
640
|
-
}
|
|
641
|
-
if (done)
|
|
642
|
-
break;
|
|
643
|
-
}
|
|
644
|
-
const trailingEvent = parseWorkerInstallStreamEvent(buffer);
|
|
645
|
-
if (trailingEvent?.type === "result" || trailingEvent?.type === "error") {
|
|
646
|
-
finalResponse = {
|
|
647
|
-
ok: trailingEvent.type === "result" && response.ok,
|
|
648
|
-
status: trailingEvent.status || response.status,
|
|
649
|
-
data: trailingEvent.data || {}
|
|
650
|
-
};
|
|
651
|
-
}
|
|
652
|
-
return (finalResponse || {
|
|
653
|
-
ok: false,
|
|
654
|
-
status: response.status,
|
|
655
|
-
data: {
|
|
656
|
-
success: false,
|
|
657
|
-
error: "Runner install stream ended before returning a result."
|
|
658
|
-
}
|
|
659
|
-
});
|
|
660
|
-
}
|
|
661
|
-
function parseWorkerInstallStreamEvent(line) {
|
|
662
|
-
const trimmed = line.trim();
|
|
663
|
-
if (!trimmed)
|
|
664
|
-
return null;
|
|
665
|
-
try {
|
|
666
|
-
const parsed = JSON.parse(trimmed);
|
|
667
|
-
if (parsed.type === "progress" || parsed.type === "result" || parsed.type === "error") {
|
|
668
|
-
return parsed;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
catch {
|
|
672
|
-
return null;
|
|
673
|
-
}
|
|
674
|
-
return null;
|
|
675
|
-
}
|
|
676
|
-
async function startSkillRun(input) {
|
|
677
|
-
const response = await fetchJsonResponse(`${input.baseUrl}/api/cloud/start-fix`, {
|
|
678
|
-
method: "POST",
|
|
679
|
-
headers: {
|
|
680
|
-
...authorizationHeaders(input.token),
|
|
681
|
-
"Content-Type": "application/json"
|
|
682
|
-
},
|
|
683
|
-
body: JSON.stringify({
|
|
684
|
-
userId: input.user.id,
|
|
685
|
-
skillRunnerId: input.skillRunnerId,
|
|
686
|
-
skillRunnerTeam: {
|
|
687
|
-
id: input.team.id,
|
|
688
|
-
slug: input.team.slug,
|
|
689
|
-
name: input.team.name,
|
|
690
|
-
isPersonal: input.team.isPersonal
|
|
691
|
-
},
|
|
692
|
-
projectName: input.project.name,
|
|
693
|
-
projectId: input.project.id,
|
|
694
|
-
teamId: input.teamScope,
|
|
695
|
-
projectDir: input.projectDir,
|
|
696
|
-
repoUrl: `https://github.com/${input.githubRepo.owner}/${input.githubRepo.repo}`,
|
|
697
|
-
repoOwner: input.githubRepo.owner,
|
|
698
|
-
repoName: input.githubRepo.repo,
|
|
699
|
-
repoBranch: input.branch,
|
|
700
|
-
baseBranch: input.branch,
|
|
701
|
-
submitPullRequest: true,
|
|
702
|
-
productionUrl: input.productionUrl,
|
|
703
|
-
useV0DevAgentRunner: true
|
|
704
|
-
})
|
|
705
|
-
});
|
|
706
|
-
return {
|
|
707
|
-
...response.data,
|
|
708
|
-
success: response.ok && response.data?.success === true
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
async function waitForRun(baseUrl, userId, runId, options) {
|
|
712
|
-
log(options, "Waiting for run to finish...");
|
|
713
|
-
for (let attempt = 0; attempt < 720; attempt++) {
|
|
714
|
-
const run = await fetchRun(baseUrl, userId, runId);
|
|
715
|
-
if (run?.status && run.status !== "running") {
|
|
716
|
-
return run;
|
|
717
|
-
}
|
|
718
|
-
await new Promise((resolve) => setTimeout(resolve, 5000));
|
|
719
|
-
}
|
|
720
|
-
throw new Error("Timed out waiting for DeepSec run to finish.");
|
|
721
|
-
}
|
|
722
|
-
async function fetchRun(baseUrl, userId, runId) {
|
|
723
|
-
const apiUrl = new URL(`${baseUrl}/api/dev-agents/runs`);
|
|
724
|
-
apiUrl.searchParams.set("userId", userId);
|
|
725
|
-
const data = await fetchJson(apiUrl.toString());
|
|
726
|
-
const runs = Array.isArray(data.runs) ? data.runs : [];
|
|
727
|
-
return runs.find((run) => run.id === runId) || null;
|
|
728
|
-
}
|
|
729
|
-
function formatRunCompletion(run, reportUrl) {
|
|
730
|
-
const status = run.status || "unknown";
|
|
731
|
-
const cost = run.formattedCost || formatCost(run.cost ?? run.totalCost);
|
|
732
|
-
const duration = run.duration ? `, ${run.duration}` : "";
|
|
733
|
-
const costText = cost ? `, ${cost}` : "";
|
|
734
|
-
const errorText = run.error ? `\n${chalk.red(run.error)}` : "";
|
|
735
|
-
return `${chalk.bold("Run finished:")} ${status}${duration}${costText}\n${chalk.bold("Report:")} ${reportUrl}${errorText}`;
|
|
736
|
-
}
|
|
737
|
-
async function fetchJson(url, init) {
|
|
738
|
-
const response = await fetchJsonResponse(url, init);
|
|
739
|
-
if (!response.ok) {
|
|
740
|
-
throw new Error(formatFetchError(url, response.status, response.data));
|
|
741
|
-
}
|
|
742
|
-
return response.data;
|
|
743
|
-
}
|
|
744
|
-
async function fetchJsonResponse(url, init) {
|
|
745
|
-
const response = await fetch(url, init);
|
|
746
|
-
const text = await response.text();
|
|
747
|
-
let data = null;
|
|
748
|
-
if (text.trim()) {
|
|
749
|
-
try {
|
|
750
|
-
data = JSON.parse(text);
|
|
751
|
-
}
|
|
752
|
-
catch {
|
|
753
|
-
throw new Error(`${url} returned invalid JSON: ${text.slice(0, 300)}`);
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
return {
|
|
757
|
-
ok: response.ok,
|
|
758
|
-
status: response.status,
|
|
759
|
-
data: data
|
|
760
|
-
};
|
|
761
|
-
}
|
|
762
|
-
function formatFetchError(url, status, data) {
|
|
763
|
-
const error = typeof data === "object" && data !== null ? data.error : undefined;
|
|
764
|
-
const message = typeof error === "object" && error !== null
|
|
765
|
-
? stringValue(error.message)
|
|
766
|
-
: stringValue(error) ||
|
|
767
|
-
(typeof data === "object" && data !== null ? stringValue(data.message) : "");
|
|
768
|
-
let hostname = "";
|
|
769
|
-
try {
|
|
770
|
-
hostname = new URL(url).hostname;
|
|
771
|
-
}
|
|
772
|
-
catch {
|
|
773
|
-
hostname = "";
|
|
774
|
-
}
|
|
775
|
-
if (hostname === "api.vercel.com" && status === 401) {
|
|
776
|
-
return "Vercel authentication failed. Run `vercel login`, or set VERCEL_TOKEN to a token that can access the target team.";
|
|
777
|
-
}
|
|
778
|
-
if (hostname === "api.vercel.com" && status === 403) {
|
|
779
|
-
return `Vercel API access was denied${message ? `: ${message}` : "."} Re-authenticate with \`vercel login\`; for SAML teams, re-authenticate that scope in the Vercel CLI, then retry.`;
|
|
780
|
-
}
|
|
781
|
-
return `${url} failed (${status})${message ? `: ${message}` : ""}`;
|
|
782
|
-
}
|
|
783
|
-
function authorizationHeaders(token) {
|
|
784
|
-
return {
|
|
785
|
-
Authorization: `Bearer ${token}`
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
function normalizeBaseUrl(value) {
|
|
789
|
-
return value.replace(/\/+$/, "");
|
|
790
|
-
}
|
|
791
|
-
function stringValue(value) {
|
|
792
|
-
return typeof value === "string" && value.trim().length > 0 ? value.trim() : undefined;
|
|
793
|
-
}
|
|
794
|
-
function isSkillRunnerWorkerProject(name) {
|
|
795
|
-
const normalizedName = name.trim().toLowerCase();
|
|
796
|
-
return normalizedName === "d3k-skill-runner" || normalizedName.startsWith("d3k-skill-runner-");
|
|
797
|
-
}
|
|
798
|
-
function formatCost(value) {
|
|
799
|
-
if (typeof value === "number" && Number.isFinite(value)) {
|
|
800
|
-
return `$${value.toFixed(2)}`;
|
|
801
|
-
}
|
|
802
|
-
if (typeof value !== "string") {
|
|
803
|
-
return null;
|
|
804
|
-
}
|
|
805
|
-
const parsed = Number(value.replace(/[^0-9.]/g, ""));
|
|
806
|
-
if (!Number.isFinite(parsed)) {
|
|
807
|
-
return value;
|
|
808
|
-
}
|
|
809
|
-
return `$${parsed.toFixed(2)}`;
|
|
810
|
-
}
|
|
811
|
-
function log(options, message) {
|
|
812
|
-
if (!options.json) {
|
|
813
|
-
console.log(message);
|
|
814
|
-
}
|
|
815
|
-
}
|
|
816
|
-
function startSpinner(options, message) {
|
|
817
|
-
if (options.json || !process.stderr.isTTY) {
|
|
818
|
-
return null;
|
|
819
|
-
}
|
|
820
|
-
return ora({
|
|
821
|
-
text: message,
|
|
822
|
-
spinner: "dots",
|
|
823
|
-
stream: process.stderr
|
|
824
|
-
}).start();
|
|
825
|
-
}
|
|
826
|
-
//# sourceMappingURL=skill-runner.js.map
|