@agentsky/sky 2.18.0 → 2.20.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/dist/index.js CHANGED
@@ -9,7 +9,7 @@ import {
9
9
  resolveSessionRef,
10
10
  streamEvents,
11
11
  uploadFile
12
- } from "./chunk-PL5KNPCX.js";
12
+ } from "./chunk-ICSGMEG5.js";
13
13
  import {
14
14
  CLI_VERSION,
15
15
  USER_AGENT,
@@ -20,7 +20,7 @@ import {
20
20
  currentUniverse,
21
21
  loadConfig,
22
22
  saveConfig
23
- } from "./chunk-MUHFB3LC.js";
23
+ } from "./chunk-BHW2NAXP.js";
24
24
  import {
25
25
  AGENT_TYPES,
26
26
  MODEL_SUBSCRIPTION_PLAN_PHRASE,
@@ -37,15 +37,16 @@ import {
37
37
  modelSubscriptionProviderFor,
38
38
  reasoningEffortDocLine,
39
39
  redactLine,
40
+ redactPlainText,
40
41
  redactSecretValues,
41
42
  skillRefSchema,
42
43
  splitMcpServers,
43
44
  supersededBy
44
- } from "./chunk-ZJVFWFOY.js";
45
+ } from "./chunk-DC3R6BV2.js";
45
46
  import "./chunk-MLKGABMK.js";
46
47
 
47
48
  // src/index.ts
48
- import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync7 } from "fs";
49
+ import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as writeFileSync8 } from "fs";
49
50
  import { basename as basename8, resolve as resolve9 } from "path";
50
51
  import { createInterface as createInterface6 } from "readline";
51
52
  import { Command } from "commander";
@@ -4101,6 +4102,355 @@ function renderRequest(request) {
4101
4102
  return `${request.id} ${request.kind} from ${request.session_id}
4102
4103
  ${request.question}${options}`;
4103
4104
  }
4105
+ async function getSkillUsage(since) {
4106
+ const qs = since ? `?since=${encodeURIComponent(since)}` : "";
4107
+ return api("GET", `/skills/usage${qs}`);
4108
+ }
4109
+
4110
+ // src/task-export.ts
4111
+ import { createHash as createHash6 } from "crypto";
4112
+ import { mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "fs";
4113
+ import { dirname as dirname7, join as join15 } from "path";
4114
+ import { stringify as stringifyToml2 } from "smol-toml";
4115
+ var TEXT_MAX = 2e4;
4116
+ function scrub(text, names2) {
4117
+ let out = redactPlainText(text).text;
4118
+ for (const { name, alias } of names2) {
4119
+ if (!name) continue;
4120
+ out = out.split(name).join(alias);
4121
+ }
4122
+ return out.length > TEXT_MAX ? `${out.slice(0, TEXT_MAX)}
4123
+ \u2026[cut]` : out;
4124
+ }
4125
+ function textOf2(event) {
4126
+ if (typeof event.text === "string" && event.text.trim()) return event.text;
4127
+ if (Array.isArray(event.content)) {
4128
+ return event.content.map((block) => block.type === "text" && typeof block.text === "string" ? block.text : "").filter(Boolean).join("\n");
4129
+ }
4130
+ return "";
4131
+ }
4132
+ function firstAsk(record) {
4133
+ const root = record.threads.find((thread2) => thread2.relation === "primary") ?? record.threads[0];
4134
+ if (!root) return null;
4135
+ for (const event of record.events[root.id] ?? []) {
4136
+ if (event.type === "user.message") {
4137
+ const text = textOf2(event).trim();
4138
+ if (text) return text;
4139
+ }
4140
+ }
4141
+ return null;
4142
+ }
4143
+ function decisionsOf(record) {
4144
+ const out = [];
4145
+ for (const thread2 of record.threads) {
4146
+ for (const event of record.events[thread2.id] ?? []) {
4147
+ if (event.type === "human.decision" || event.type === "outcome.observed") out.push({ ...event, thread_id: thread2.id });
4148
+ }
4149
+ }
4150
+ return out.sort((a, b) => (a.at ?? "").localeCompare(b.at ?? ""));
4151
+ }
4152
+ function adoptedSession(record) {
4153
+ const decisions = decisionsOf(record).filter((event) => event.type === "human.decision");
4154
+ for (const event of [...decisions].reverse()) {
4155
+ if (event.kind === "adopt") return event.target_session_id ?? event.thread_id;
4156
+ if (event.kind === "vote" && event.target_session_id) return event.target_session_id;
4157
+ }
4158
+ return null;
4159
+ }
4160
+ function stepsOf(record, names2) {
4161
+ const root = record.threads.find((thread2) => thread2.relation === "primary") ?? record.threads[0];
4162
+ if (!root) return [];
4163
+ const events = record.events[root.id] ?? [];
4164
+ const steps = [];
4165
+ events.forEach((event, index) => {
4166
+ if (event.type !== "human.decision" || event.kind !== "revise" && event.kind !== "rerun") return;
4167
+ const next = events.slice(index + 1).find((later) => later.type === "user.message" && textOf2(later).trim());
4168
+ const words = next ? textOf2(next) : event.reason ?? "";
4169
+ if (!words.trim()) return;
4170
+ steps.push({ at: event.at ?? "", instruction: scrub(words, names2) });
4171
+ });
4172
+ return steps;
4173
+ }
4174
+ function rubricOf(record, instruction, names2) {
4175
+ if (record.goal && record.goal.criteria.length) {
4176
+ const verdicts = new Map((record.goal.last_evaluation?.criteria ?? []).map((row) => [row.id, row.verdict]));
4177
+ return {
4178
+ version: 1,
4179
+ source: "goal",
4180
+ criteria: record.goal.criteria.map((criterion) => ({
4181
+ id: criterion.id,
4182
+ text: scrub(criterion.text, names2),
4183
+ must: true,
4184
+ source: `goal criterion ${criterion.id}`,
4185
+ ...criterion.command ? { command: criterion.command } : {},
4186
+ ...verdicts.has(criterion.id) ? { last_verdict: verdicts.get(criterion.id) } : {}
4187
+ }))
4188
+ };
4189
+ }
4190
+ return {
4191
+ version: 1,
4192
+ source: "instruction",
4193
+ criteria: [{ id: "c1", text: `The deliverable does what the instruction asks: ${instruction.split("\n")[0]?.slice(0, 300) ?? ""}`, must: true, source: "derived from the instruction; no goal was set" }]
4194
+ };
4195
+ }
4196
+ var JUDGE_PY = String.raw`#!/usr/bin/env python3
4197
+ """Harbor verifier: grade the agent's output against tests/rubric.json with an LLM judge.
4198
+
4199
+ Reads every file under /workspace/outputs (falling back to /workspace) and asks the judge
4200
+ model, criterion by criterion, for a yes/no with quoted evidence. Writes
4201
+ /logs/verifier/reward.json ({"reward": 0|1, "criteria": [...]}) and reward.txt.
4202
+ Model and key come from the environment (JUDGE_MODEL, JUDGE_API_KEY, JUDGE_BASE_URL);
4203
+ nothing secret is in this file. Reward is 1 only when every MUST criterion passes.
4204
+ """
4205
+ import json, os, pathlib, sys, urllib.request
4206
+
4207
+ HERE = pathlib.Path(__file__).resolve().parent
4208
+ RUBRIC = json.loads((HERE / "rubric.json").read_text())
4209
+ OUT_DIR = pathlib.Path(os.environ.get("HARBOR_OUTPUTS", "/workspace/outputs"))
4210
+ if not OUT_DIR.exists():
4211
+ OUT_DIR = pathlib.Path("/workspace")
4212
+ LOGS = pathlib.Path(os.environ.get("HARBOR_VERIFIER_LOGS", "/logs/verifier"))
4213
+ LOGS.mkdir(parents=True, exist_ok=True)
4214
+
4215
+ def collect(limit=200_000):
4216
+ parts, size = [], 0
4217
+ for path in sorted(p for p in OUT_DIR.rglob("*") if p.is_file()):
4218
+ if any(s in path.parts for s in (".git", "node_modules", "__pycache__")):
4219
+ continue
4220
+ try:
4221
+ text = path.read_text(errors="replace")
4222
+ except OSError:
4223
+ continue
4224
+ chunk = f"\n===== {path.relative_to(OUT_DIR)} =====\n{text}"
4225
+ if size + len(chunk) > limit:
4226
+ break
4227
+ parts.append(chunk); size += len(chunk)
4228
+ return "".join(parts)
4229
+
4230
+ def ask(prompt):
4231
+ base = os.environ.get("JUDGE_BASE_URL", "https://api.openai.com/v1").rstrip("/")
4232
+ body = json.dumps({"model": os.environ.get("JUDGE_MODEL", "gpt-5.5"), "temperature": 0,
4233
+ "messages": [{"role": "user", "content": prompt}]}).encode()
4234
+ req = urllib.request.Request(f"{base}/chat/completions", data=body, headers={
4235
+ "content-type": "application/json", "authorization": f"Bearer {os.environ['JUDGE_API_KEY']}"})
4236
+ with urllib.request.urlopen(req, timeout=120) as res:
4237
+ return json.load(res)["choices"][0]["message"]["content"]
4238
+
4239
+ def grade(criterion, evidence):
4240
+ prompt = (
4241
+ "You grade one requirement of a task against the agent's output.\n"
4242
+ f"Requirement: {criterion['text']}\n"
4243
+ "Answer as JSON only: {\"pass\": true|false, \"evidence\": \"<quote from the output, or why it is missing>\"}.\n"
4244
+ "Pass only when the output itself shows the requirement met.\n\nOUTPUT:\n" + evidence)
4245
+ raw = ask(prompt).strip()
4246
+ start, end = raw.find("{"), raw.rfind("}")
4247
+ verdict = json.loads(raw[start:end + 1]) if start >= 0 else {"pass": False, "evidence": raw[:300]}
4248
+ return {"id": criterion["id"], "must": criterion.get("must", True), "pass": bool(verdict.get("pass")), "evidence": str(verdict.get("evidence", ""))[:1000]}
4249
+
4250
+ def main():
4251
+ evidence = collect()
4252
+ results = [grade(c, evidence) for c in RUBRIC["criteria"]]
4253
+ reward = 1 if results and all(r["pass"] for r in results if r["must"]) else 0
4254
+ (LOGS / "reward.json").write_text(json.dumps({"reward": reward, "criteria": results}, indent=2))
4255
+ (LOGS / "reward.txt").write_text(f"{reward}\n")
4256
+ print(json.dumps({"reward": reward, "passed": sum(r["pass"] for r in results), "total": len(results)}))
4257
+ return 0
4258
+
4259
+ if __name__ == "__main__":
4260
+ sys.exit(main())
4261
+ `;
4262
+ var TEST_SH = `#!/usr/bin/env bash
4263
+ # Harbor verifier entry point: the judge reads tests/rubric.json and writes /logs/verifier/reward.json.
4264
+ set -euo pipefail
4265
+ cd "$(dirname "$0")"
4266
+ exec python3 judge.py
4267
+ `;
4268
+ var SOLVE_SH = `#!/usr/bin/env bash
4269
+ # Reference solution: the files the task's sessions produced, as the person left them.
4270
+ set -euo pipefail
4271
+ cd "$(dirname "$0")"
4272
+ mkdir -p /workspace/outputs
4273
+ if [ -d files ]; then cp -R files/. /workspace/outputs/; fi
4274
+ `;
4275
+ function buildHarborTask(record, opts) {
4276
+ const names2 = opts.redactNames;
4277
+ const out = /* @__PURE__ */ new Map();
4278
+ const ask2 = record.goal?.objective ?? firstAsk(record) ?? record.title ?? "Complete the task described by the workspace.";
4279
+ const instruction = scrub(ask2, names2);
4280
+ const root = record.threads.find((thread2) => thread2.relation === "primary") ?? record.threads[0];
4281
+ const harnesses = [...new Set(record.threads.map((thread2) => thread2.agent.harness))];
4282
+ const decisions = decisionsOf(record);
4283
+ const adopted = adoptedSession(record);
4284
+ const steps = stepsOf(record, names2);
4285
+ const idHash = createHash6("sha256").update(record.task_id).digest("hex").slice(0, 16);
4286
+ out.set("instruction.md", `${instruction.trim()}
4287
+
4288
+ Write your deliverables under /workspace/outputs.
4289
+ `);
4290
+ out.set(
4291
+ "task.toml",
4292
+ stringifyToml2({
4293
+ metadata: {
4294
+ name: `agentsky-${idHash}`,
4295
+ tier: opts.tier,
4296
+ source: "agentsky-session-record",
4297
+ source_task: idHash,
4298
+ exported_at: record.exported_at,
4299
+ harnesses,
4300
+ has_goal: Boolean(record.goal),
4301
+ decisions: decisions.length,
4302
+ steps: steps.length,
4303
+ adopted_session: adopted ?? "",
4304
+ did_the_job: record.completion_answer?.did_the_job ?? "unknown"
4305
+ },
4306
+ verifier: { kind: "rubric_judge", rubric: "tests/rubric.json", reward: "/logs/verifier/reward.json" }
4307
+ })
4308
+ );
4309
+ const env = record.environment ?? root?.environment ?? null;
4310
+ if (env && typeof env.repo === "string" && typeof env.commit === "string") {
4311
+ out.set(
4312
+ "environment/Dockerfile",
4313
+ `FROM node:22-bookworm
4314
+ RUN apt-get update && apt-get install -y --no-install-recommends git python3 && rm -rf /var/lib/apt/lists/*
4315
+ WORKDIR /workspace
4316
+ RUN git clone ${env.repo} repo && cd repo && git checkout ${env.commit}
4317
+ `
4318
+ );
4319
+ } else {
4320
+ out.set(
4321
+ "environment/README.md",
4322
+ "This task is a real execution, not a resettable environment: the record carries no repository and commit to rebuild from.\nAuthor environment/Dockerfile by hand from solution/files (the task's own files at export) before running it with Harbor, or keep it as trajectory data.\n"
4323
+ );
4324
+ }
4325
+ const rubric = rubricOf(record, instruction, names2);
4326
+ out.set("tests/rubric.json", `${JSON.stringify(rubric, null, 2)}
4327
+ `);
4328
+ out.set("tests/judge.py", JUDGE_PY);
4329
+ out.set("tests/test.sh", TEST_SH);
4330
+ out.set("solution/solve.sh", SOLVE_SH);
4331
+ out.set(
4332
+ "solution/deliverables.json",
4333
+ `${JSON.stringify({ files: record.files.map((file) => ({ path: file.path, bytes: file.size_bytes ?? null })), adopted_session: adopted }, null, 2)}
4334
+ `
4335
+ );
4336
+ for (const [path, bytes] of opts.fileBytes ?? []) out.set(`solution/files/${path}`, bytes);
4337
+ const scrubbedEvents = {};
4338
+ for (const [threadId, events] of Object.entries(record.events)) {
4339
+ scrubbedEvents[threadId] = events.map((event) => ({
4340
+ ...event,
4341
+ ...typeof event.text === "string" ? { text: scrub(event.text, names2) } : {},
4342
+ ...Array.isArray(event.content) ? { content: event.content.map((block) => typeof block.text === "string" ? { ...block, text: scrub(block.text, names2) } : block) } : {},
4343
+ ...typeof event.reason === "string" ? { reason: scrub(event.reason, names2) } : {},
4344
+ ..."parts" in event ? { parts: void 0 } : {}
4345
+ }));
4346
+ }
4347
+ out.set("rollout/record.json", `${JSON.stringify({ ...record, title: record.title ? scrub(record.title, names2) : null, events: scrubbedEvents, goal: record.goal ? { ...record.goal, objective: scrub(record.goal.objective, names2) } : null }, null, 2)}
4348
+ `);
4349
+ out.set("rollout/decisions.json", `${JSON.stringify(decisions.map((event) => ({ ...event, reason: typeof event.reason === "string" ? scrub(event.reason, names2) : event.reason })), null, 2)}
4350
+ `);
4351
+ steps.forEach((step, index) => {
4352
+ out.set(`steps/step-${index + 1}/instruction.md`, `${step.instruction.trim()}
4353
+ `);
4354
+ });
4355
+ return out;
4356
+ }
4357
+ async function pagedEvents(sessionId) {
4358
+ const events = [];
4359
+ let cursor = null;
4360
+ for (; ; ) {
4361
+ const query = new URLSearchParams({ limit: "500" });
4362
+ if (cursor) query.set("cursor", cursor);
4363
+ const page = await api("GET", `/sessions/${encodeURIComponent(sessionId)}/events?${query}`);
4364
+ events.push(...page.events);
4365
+ if (!page.hasMore || !page.cursor) return events;
4366
+ cursor = page.cursor;
4367
+ }
4368
+ }
4369
+ async function fetchTaskRecord(taskRef) {
4370
+ const { task: task2 } = await api("GET", `/tasks/${encodeURIComponent(taskRef)}`);
4371
+ const { threads } = await api("GET", `/sessions/${encodeURIComponent(task2.id)}/threads`);
4372
+ const events = {};
4373
+ for (const thread2 of threads) events[thread2.id] = await pagedEvents(thread2.id);
4374
+ let goal2 = null;
4375
+ try {
4376
+ goal2 = (await api("GET", `/sessions/${encodeURIComponent(task2.id)}/goal`)).goal;
4377
+ } catch (error) {
4378
+ if (!(error instanceof ApiError && error.status === 404)) throw error;
4379
+ }
4380
+ let files2 = [];
4381
+ try {
4382
+ files2 = await listTaskFiles(task2.id);
4383
+ } catch (error) {
4384
+ if (!(error instanceof ApiError && (error.status === 404 || error.status === 503))) throw error;
4385
+ }
4386
+ const root = threads.find((thread2) => thread2.relation === "primary") ?? threads[0];
4387
+ return { task_id: task2.id, exported_at: (/* @__PURE__ */ new Date()).toISOString(), title: task2.title ?? null, threads, events, goal: goal2, files: files2, completion_answer: task2.completion_answer ?? null, environment: root?.environment ?? null };
4388
+ }
4389
+ var FILE_BYTES_MAX = 50 * 1024 * 1024;
4390
+ async function exportTaskHarbor(taskRef, outDir, opts) {
4391
+ const record = await fetchTaskRecord(taskRef);
4392
+ const fileBytes = /* @__PURE__ */ new Map();
4393
+ let total = 0;
4394
+ for (const file of record.files) {
4395
+ if (total + (file.size_bytes ?? 0) > FILE_BYTES_MAX) break;
4396
+ const bytes = await readTaskFile(record.task_id, file.path);
4397
+ total += bytes.byteLength;
4398
+ fileBytes.set(file.path, bytes);
4399
+ }
4400
+ const files2 = buildHarborTask(record, { ...opts, fileBytes });
4401
+ const written = [];
4402
+ for (const [path, content] of files2) {
4403
+ const target = join15(outDir, path);
4404
+ mkdirSync4(dirname7(target), { recursive: true });
4405
+ writeFileSync6(target, content, typeof content === "string" ? { mode: path.endsWith(".sh") || path.endsWith(".py") ? 493 : 420 } : void 0);
4406
+ written.push(path);
4407
+ }
4408
+ return { written, record };
4409
+ }
4410
+ function parseRedactNames(values) {
4411
+ return (values ?? []).map((value) => {
4412
+ const index = value.indexOf("=");
4413
+ if (index < 0) return { name: value.trim(), alias: "[redacted]" };
4414
+ return { name: value.slice(0, index).trim(), alias: value.slice(index + 1).trim() || "[redacted]" };
4415
+ }).filter((pair) => pair.name);
4416
+ }
4417
+
4418
+ // src/task-record.ts
4419
+ import { randomUUID as randomUUID2 } from "crypto";
4420
+ var DECISION_KINDS = ["adopt", "rerun", "revise", "discard", "vote", "did_the_job", "approve"];
4421
+ async function listThreads(taskRef) {
4422
+ return (await api("GET", `/sessions/${encodeURIComponent(taskRef)}/threads`)).threads;
4423
+ }
4424
+ function renderThread(thread2) {
4425
+ const spend = (Number(thread2.usage.spent_usd_micros) / 1e6).toFixed(4);
4426
+ const env = thread2.environment?.repo ? ` ${thread2.environment.repo}${thread2.environment.commit ? `@${thread2.environment.commit.slice(0, 7)}` : ""}` : "";
4427
+ return `${thread2.id} ${thread2.relation.padEnd(7)} ${thread2.source.padEnd(12)} ${thread2.agent.harness}/${thread2.agent.model} ${thread2.status} $${spend}${env}`;
4428
+ }
4429
+ async function recordDecision(sessionRef, kind, opts = {}) {
4430
+ if (!DECISION_KINDS.includes(kind)) throw new Error(`kind must be one of ${DECISION_KINDS.join(", ")}`);
4431
+ if ((kind === "did_the_job" || kind === "approve") && typeof opts.value !== "boolean") throw new Error(`${kind} needs --yes or --no`);
4432
+ const key = opts.idempotencyKey ?? randomUUID2();
4433
+ const res = await fetchRecord(sessionRef, {
4434
+ type: "human.decision",
4435
+ payload: {
4436
+ kind,
4437
+ ...opts.target ? { target_session_id: opts.target } : {},
4438
+ ...typeof opts.value === "boolean" ? { value: opts.value } : {},
4439
+ ...opts.reason ? { reason: opts.reason } : {},
4440
+ surface: opts.surface ?? "cli"
4441
+ }
4442
+ }, key);
4443
+ return res;
4444
+ }
4445
+ var OUTCOME_KINDS = ["pr_merged", "deployed", "reopened", "later_failure", "shipped"];
4446
+ async function recordOutcome(sessionRef, kind, opts = {}) {
4447
+ if (!OUTCOME_KINDS.includes(kind)) throw new Error(`kind must be one of ${OUTCOME_KINDS.join(", ")}`);
4448
+ return fetchRecord(sessionRef, { type: "outcome.observed", payload: { kind, ...opts.ref ? { ref: opts.ref } : {}, observer: opts.observer ?? "cli" } }, opts.idempotencyKey ?? randomUUID2());
4449
+ }
4450
+ async function fetchRecord(sessionRef, body, idempotencyKey) {
4451
+ const result = await api("POST", `/sessions/${encodeURIComponent(sessionRef)}/record`, body, { idempotencyKey });
4452
+ return { event: result.event, created: true };
4453
+ }
4104
4454
 
4105
4455
  // src/workspace.ts
4106
4456
  import { createInterface as createInterface4 } from "readline";
@@ -4321,12 +4671,77 @@ function confirmPrompt(question) {
4321
4671
  });
4322
4672
  }
4323
4673
 
4674
+ // src/browser-profiles.ts
4675
+ async function listBrowserProfiles() {
4676
+ const { data } = await api("GET", "/browser_profiles");
4677
+ return data;
4678
+ }
4679
+ async function getBrowserProfile(scope) {
4680
+ const { profile } = await api("GET", `/browser_profiles/${encodeURIComponent(scope)}`);
4681
+ return profile;
4682
+ }
4683
+ async function importBrowserProfileState(scope, state) {
4684
+ return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/imports`, state);
4685
+ }
4686
+ async function listBrowserProfileSites(scope) {
4687
+ const profile = await getBrowserProfile(scope);
4688
+ return profile.sites;
4689
+ }
4690
+ async function removeBrowserProfileSite(scope, site) {
4691
+ await api("DELETE", `/browser_profiles/${encodeURIComponent(scope)}/sites/${encodeURIComponent(site)}`);
4692
+ }
4693
+ async function moveBrowserProfileSite(scope, site, to) {
4694
+ return api("POST", `/browser_profiles/${encodeURIComponent(scope)}/sites/${encodeURIComponent(site)}/move`, { to });
4695
+ }
4696
+ function renderBrowserProfileSite(site) {
4697
+ const who = site.signed_in_by?.email ?? site.signed_in_by?.name ?? "unknown";
4698
+ const used = site.last_used_at ? `used ${site.last_used_at.slice(0, 10)}` : "never used";
4699
+ return `${site.site.padEnd(40)} ${site.status.padEnd(14)} ${who} ${used}`;
4700
+ }
4701
+ function renderBrowserProfile(profile) {
4702
+ const lines = [`scope: ${profile.scope} revision: ${profile.revision} sites: ${profile.sites.length}`];
4703
+ for (const site of profile.sites) lines.push(` ${renderBrowserProfileSite(site)}`);
4704
+ return lines.join("\n");
4705
+ }
4706
+
4707
+ // src/compare.ts
4708
+ async function listComparisons(opts = {}) {
4709
+ const qs = opts.archived ? "?archived=true" : "";
4710
+ const { comparisons } = await api("GET", `/comparisons${qs}`);
4711
+ return comparisons;
4712
+ }
4713
+ async function createComparison(body) {
4714
+ return api("POST", "/comparisons", body);
4715
+ }
4716
+ async function getComparison(id) {
4717
+ return api("GET", `/comparisons/${encodeURIComponent(id)}`);
4718
+ }
4719
+ async function pickWinner(id, opts) {
4720
+ return api("POST", `/comparisons/${encodeURIComponent(id)}/pick`, opts);
4721
+ }
4722
+ async function listThreads2(opts = {}) {
4723
+ const qs = opts.filter ? `?filter=${opts.filter}` : "";
4724
+ const { threads } = await api("GET", `/threads${qs}`);
4725
+ return threads;
4726
+ }
4727
+ function renderComparisonSummary(c) {
4728
+ const agents = c.agents.map((a) => `${a.agentType}/${a.llm}`).join(" vs ");
4729
+ const status = c.statusLine ?? c.attention ?? (c.archived ? "archived" : "active");
4730
+ return `${c.id.slice(0, 12)} ${c.title.slice(0, 40).padEnd(42)} [${agents}] ${status}`;
4731
+ }
4732
+ function renderThreadSummary(t) {
4733
+ const agents = t.agents.map((a) => `${a.agentType}/${a.llm}`).join(", ");
4734
+ const flag = t.pinned ? "\u{1F4CC} " : "";
4735
+ const status = t.working ? "working" : t.attention ?? (t.archived ? "archived" : "idle");
4736
+ return `${t.id.slice(0, 12)} ${flag}${t.title.slice(0, 40).padEnd(44)} [${agents}] ${status}`;
4737
+ }
4738
+
4324
4739
  // src/harness.ts
4325
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync6 } from "fs";
4326
- import { dirname as dirname7, join as join15, resolve as resolve8 } from "path";
4740
+ import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
4741
+ import { dirname as dirname8, join as join16, resolve as resolve8 } from "path";
4327
4742
  var HARNESS_FILE = "harness.json";
4328
4743
  function harnessFilePath(dir) {
4329
- return join15(resolve8(dir), HARNESS_FILE);
4744
+ return join16(resolve8(dir), HARNESS_FILE);
4330
4745
  }
4331
4746
  function readHarnessFile(dir) {
4332
4747
  const path = harnessFilePath(dir);
@@ -4364,8 +4779,8 @@ function harnessInit(dir) {
4364
4779
  if (existsSync9(path)) {
4365
4780
  throw new Error(`${path} already exists \u2014 delete it to re-init`);
4366
4781
  }
4367
- mkdirSync4(dirname7(path), { recursive: true });
4368
- writeFileSync6(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
4782
+ mkdirSync5(dirname8(path), { recursive: true });
4783
+ writeFileSync7(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
4369
4784
  `);
4370
4785
  console.log(`Created ${path}`);
4371
4786
  console.log(INIT_COMMENT);
@@ -5070,7 +5485,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
5070
5485
  run(async (ref, path, opts) => {
5071
5486
  const bytes = await readFile(await resolveSessionRef(ref), path);
5072
5487
  if (opts.out) {
5073
- writeFileSync7(opts.out, bytes);
5488
+ writeFileSync8(opts.out, bytes);
5074
5489
  console.error(`saved ${bytes.length} bytes to ${opts.out}`);
5075
5490
  } else process.stdout.write(bytes);
5076
5491
  })
@@ -5087,7 +5502,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
5087
5502
  console.error(`${meta.repo}: ${meta.commits.length} commit${meta.commits.length === 1 ? "" : "s"} since ${meta.base_branch || "the imported copy"} (${meta.base_commit.slice(0, 12)})`);
5088
5503
  if (!patch.length) console.error("nothing changed");
5089
5504
  else if (opts.out) {
5090
- writeFileSync7(opts.out, patch);
5505
+ writeFileSync8(opts.out, patch);
5091
5506
  console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
5092
5507
  } else process.stdout.write(patch);
5093
5508
  })
@@ -5098,6 +5513,43 @@ sessionWorkspace.command("import <ref> <dir>").description("copy a local folder
5098
5513
  console.log(`${imported.path} (${imported.files} files)`);
5099
5514
  })
5100
5515
  );
5516
+ var sessionBrowser = session.command("browser").description("the sandbox browser for a session (start, show status, stop)");
5517
+ sessionBrowser.command("show <ref>").description("show the sandbox browser status for a session").option("--json", "print as JSON").action(
5518
+ run(async (ref, opts) => {
5519
+ const id = await resolveSessionRef(ref);
5520
+ const { browser } = await api("GET", `/sessions/${encodeURIComponent(id)}/browser`);
5521
+ if (opts.json) {
5522
+ console.log(JSON.stringify(browser, null, 2));
5523
+ return;
5524
+ }
5525
+ if (!browser) {
5526
+ console.log("no browser");
5527
+ return;
5528
+ }
5529
+ const b = browser;
5530
+ console.log(`${b.status ?? "unknown"}${b.url ? ` ${b.url}` : ""}${b.entry_url ? ` entry: ${b.entry_url}` : ""}`);
5531
+ })
5532
+ );
5533
+ sessionBrowser.command("start <ref>").description("start a browser for a session (provisions it if not running)").option("--json", "print as JSON").action(
5534
+ run(async (ref, opts) => {
5535
+ const id = await resolveSessionRef(ref);
5536
+ const { browser } = await api("POST", `/sessions/${encodeURIComponent(id)}/browser`, {});
5537
+ if (opts.json) {
5538
+ console.log(JSON.stringify(browser, null, 2));
5539
+ return;
5540
+ }
5541
+ const b = browser;
5542
+ console.log(`${b.status ?? "starting"}${b.entry_url ? ` entry: ${b.entry_url}` : ""}`);
5543
+ })
5544
+ );
5545
+ sessionBrowser.command("stop <ref>").description("stop the browser for a session").action(
5546
+ run(async (ref) => {
5547
+ const id = await resolveSessionRef(ref);
5548
+ const { browser } = await api("DELETE", `/sessions/${encodeURIComponent(id)}/browser`);
5549
+ const b = browser;
5550
+ console.log(b ? b.status ?? "stopped" : "stopped");
5551
+ })
5552
+ );
5101
5553
  function interactionMode(opts) {
5102
5554
  if (opts.plan && opts.build) throw new ApiError(400, "invalid_request", "pass --plan or --build, not both");
5103
5555
  return opts.plan ? "plan" : opts.build ? "build" : void 0;
@@ -5558,7 +6010,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
5558
6010
  run(async (taskId, path, opts) => {
5559
6011
  const bytes = await readTaskFile(taskId, path);
5560
6012
  if (opts.out) {
5561
- writeFileSync7(opts.out, bytes);
6013
+ writeFileSync8(opts.out, bytes);
5562
6014
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
5563
6015
  } else process.stdout.write(bytes);
5564
6016
  })
@@ -5575,6 +6027,15 @@ task.command("rm <task> <path>").description("remove a path from the task's file
5575
6027
  console.log(`Removed ${path}`);
5576
6028
  })
5577
6029
  );
6030
+ task.command("export <task>").description("write the task's session record as a Harbor task directory: instruction, rubric + judge, files, rollout, and one step per revision").option("-o, --out <dir>", "where to write the directory (default ./harbor-<task>)").option("--tier <tier>", "gold | silver | exploratory (recorded in task.toml)", "silver").option("--redact <name=alias...>", "replace a name everywhere in the export (repeatable)").action(
6031
+ run(async (taskId, opts) => {
6032
+ const tier = opts.tier === "gold" || opts.tier === "exploratory" ? opts.tier : "silver";
6033
+ const outDir = opts.out ?? `./harbor-${taskId.replace(/[^A-Za-z0-9_-]/g, "_")}`;
6034
+ const { written, record } = await exportTaskHarbor(taskId, outDir, { tier, redactNames: parseRedactNames(opts.redact) });
6035
+ console.log(`Wrote ${written.length} files to ${outDir} (${record.threads.length} thread${record.threads.length === 1 ? "" : "s"}, ${record.files.length} task file${record.files.length === 1 ? "" : "s"}${record.goal ? ", goal rubric" : ", no goal: derived rubric"})`);
6036
+ if (!written.includes("environment/Dockerfile")) console.log("environment/: not resettable from the record \u2014 see environment/README.md before running with Harbor");
6037
+ })
6038
+ );
5578
6039
  task.command("show <task>").description("the task's sessions and the requests waiting on you (questions, approvals, apps to connect)").option("--json", "print the task as JSON").action(
5579
6040
  run(async (taskId, opts) => {
5580
6041
  const found = await getTask(taskId);
@@ -5593,6 +6054,42 @@ task.command("answer <task> <request>").description("answer an open request; the
5593
6054
  console.log(`Answered ${requestId}`);
5594
6055
  })
5595
6056
  );
6057
+ task.command("threads <task>").description("the task's threads from the session record: root first, then every helper, handoff and child with its source, agent, status and spend").option("--json", "print the threads as JSON").action(
6058
+ run(async (taskId, opts) => {
6059
+ const threads = await listThreads(taskId);
6060
+ if (opts.json) {
6061
+ console.log(JSON.stringify({ threads }));
6062
+ return;
6063
+ }
6064
+ for (const thread2 of threads) console.log(renderThread(thread2));
6065
+ })
6066
+ );
6067
+ task.command("decide <session> <kind>").description("record what you did with a session's result: adopt | rerun | revise | discard | vote | did_the_job | approve").option("--target <session>", "the session the decision names (a vote's winner, the option you adopted)").option("--yes", "the value for did_the_job and approve").option("--no", "the value for did_the_job and approve").option("--reason <text>", "your own words, kept with the decision").option("--key <key>", "an idempotency key, so a repeat records nothing new").action(
6068
+ run(async (sessionId, kind, opts) => {
6069
+ const value = opts.yes ? true : opts.no ? false : void 0;
6070
+ const { event } = await recordDecision(sessionId, kind, { target: opts.target, value, reason: opts.reason, idempotencyKey: opts.key });
6071
+ console.log(`Recorded ${kind} on ${event.sessionId} (${event.id})`);
6072
+ })
6073
+ );
6074
+ task.command("outcome <session> <kind>").description("record what became of a session's result: pr_merged | deployed | reopened | later_failure | shipped").option("--ref <ref>", "the PR, deployment or task this refers to").option("--key <key>", "an idempotency key, so a repeat records nothing new").action(
6075
+ run(async (sessionId, kind, opts) => {
6076
+ const { event } = await recordOutcome(sessionId, kind, { ref: opts.ref, idempotencyKey: opts.key });
6077
+ console.log(`Recorded ${kind} on ${event.sessionId} (${event.id})`);
6078
+ })
6079
+ );
6080
+ task.command("dispute <task>").description("dispute an agent fee: tell the platform the task was not completed (allowed within 72 hours of the result)").requiredOption("--reason <text>", "why the task was not completed (up to 1000 characters)").option("--session <id>", "the specific session whose fee you dispute (required when the task has more than one)").action(
6081
+ run(async (taskId, opts) => {
6082
+ const { task: updated } = await api("POST", `/tasks/${encodeURIComponent(taskId)}/agent-fee/dispute`, { reason: opts.reason, ...opts.session ? { session_id: opts.session } : {} });
6083
+ console.log(`Disputed fee on task ${updated.id}`);
6084
+ })
6085
+ );
6086
+ task.command("verdict <task> <did-the-job>").description("record whether the task did the job: `yes` or `no` (replaces an earlier answer)").action(
6087
+ run(async (taskId, value) => {
6088
+ if (value !== "yes" && value !== "no") throw new Error("value must be yes or no");
6089
+ const { task: updated } = await api("PUT", `/tasks/${encodeURIComponent(taskId)}/completion_answer`, { did_the_job: value === "yes" });
6090
+ console.log(`Recorded verdict on task ${updated.id}: did_the_job=${value === "yes"}`);
6091
+ })
6092
+ );
5596
6093
  var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent by id");
5597
6094
  skills.command("upload <dir>").description("upload a skill directory (SKILL.md at its root) as a new skill").option("--name <name>", "display name (default: the directory's name)").option("--json", "print the stored skill as JSON").action(
5598
6095
  run(async (dir, opts) => {
@@ -5614,6 +6111,27 @@ skills.command("list").description("the workspace's stored skills").action(
5614
6111
  for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
5615
6112
  })
5616
6113
  );
6114
+ skills.command("usage").description("view and miss counts per skill name in this workspace").option("--since <iso>", `start date (ISO 8601); default is the last 30 days`).option("--json", "print JSON").action(
6115
+ run(async (opts) => {
6116
+ const summary = await getSkillUsage(opts.since);
6117
+ if (opts.json) {
6118
+ console.log(JSON.stringify(summary, null, 2));
6119
+ return;
6120
+ }
6121
+ if (!summary.skills.length) {
6122
+ console.log("No skill usage recorded in this period.");
6123
+ } else {
6124
+ console.log(`${"NAME".padEnd(32)} ${"VIEWS".padStart(6)} ${"MISSES".padStart(7)} LAST SEEN`);
6125
+ for (const s of summary.skills) {
6126
+ console.log(`${s.name.padEnd(32)} ${String(s.views).padStart(6)} ${String(s.misses).padStart(7)} ${s.lastSeen.slice(0, 10)}`);
6127
+ }
6128
+ }
6129
+ if (summary.topMissed.length) {
6130
+ console.log("\nTop missed:");
6131
+ for (const m of summary.topMissed) console.log(` ${m.name} (${m.misses})`);
6132
+ }
6133
+ })
6134
+ );
5617
6135
  async function decideReflection(id, verb) {
5618
6136
  const { reflection_proposal: p } = await api("POST", `/reflection_proposals/${encodeURIComponent(id)}/${verb}`);
5619
6137
  console.log(`${p.id} ${p.status}`);
@@ -5653,7 +6171,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
5653
6171
  run(async (id, path, opts) => {
5654
6172
  const bytes = await readArtifactFile(id, path);
5655
6173
  if (opts.out) {
5656
- writeFileSync7(opts.out, bytes);
6174
+ writeFileSync8(opts.out, bytes);
5657
6175
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
5658
6176
  } else process.stdout.write(bytes);
5659
6177
  })
@@ -5951,7 +6469,7 @@ usageOptions(
5951
6469
  run(async (opts) => {
5952
6470
  const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
5953
6471
  if (opts.out) {
5954
- writeFileSync7(opts.out, bytes);
6472
+ writeFileSync8(opts.out, bytes);
5955
6473
  console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
5956
6474
  } else {
5957
6475
  process.stdout.write(bytes);
@@ -6161,6 +6679,226 @@ connector.command("shared").description("everything you share with workspaces, a
6161
6679
  for (const s of shared) console.log(`${s.connection_id} ${s.app_name} (${s.label}) \u2192 ${s.workspace.slug} ${s.use_count} call${s.use_count === 1 ? "" : "s"}`);
6162
6680
  })
6163
6681
  );
6682
+ var hookRule = program.command("hook-rule").description("workspace hook rules: run a webhook or block at platform-controlled points");
6683
+ hookRule.command("list").description("the workspace's hook rules in evaluation order, or a specific workspace's with --workspace").option("--workspace <slug>", "list the workspace-level hooks for this workspace (PUT/GET /universes/{slug}/hooks)").option("--json", "print as JSON").action(
6684
+ run(async (opts) => {
6685
+ const slug = opts.workspace ?? await workspaceSlug();
6686
+ let data;
6687
+ if (opts.workspace) {
6688
+ data = await api("GET", `/universes/${encodeURIComponent(slug)}/hooks`);
6689
+ } else {
6690
+ data = await api("GET", "/hook_rules");
6691
+ }
6692
+ console.log(opts.json ? JSON.stringify(data, null, 2) : JSON.stringify(data, null, 2));
6693
+ })
6694
+ );
6695
+ hookRule.command("set").description("replace the entire ordered list of workspace-level hook rules (owner only); pass JSON rules on stdin or --rules").option("--workspace <slug>", "act on this workspace's hooks instead of the top-level hook_rules").option("--rules <json>", "JSON array of rule objects (overrides stdin)").action(
6696
+ run(async (opts) => {
6697
+ const raw = opts.rules ?? await new Promise((resolve10) => {
6698
+ const chunks = [];
6699
+ process.stdin.on("data", (d) => chunks.push(d));
6700
+ process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString()));
6701
+ });
6702
+ const rules = JSON.parse(raw);
6703
+ const slug = opts.workspace ?? await workspaceSlug();
6704
+ let result;
6705
+ if (opts.workspace) {
6706
+ result = await api("PUT", `/universes/${encodeURIComponent(slug)}/hooks`, { hooks: rules });
6707
+ } else {
6708
+ result = await api("PUT", "/hook_rules", { rules });
6709
+ }
6710
+ console.log(JSON.stringify(result, null, 2));
6711
+ })
6712
+ );
6713
+ var vendorLine = (a) => `${a.id} ${a.vendor_name.padEnd(18)} ${a.status.padEnd(14)} ${a.label}${a.plan ? ` (${a.plan})` : ""} used ${a.last_used_at?.slice(0, 10) ?? "never"}`;
6714
+ var vendor = program.command("vendor").description("vendor accounts: third-party API keys and agent logins your agents use");
6715
+ vendor.command("list").description("your workspace's vendor accounts").option("--json", "print as JSON").action(
6716
+ run(async (opts) => {
6717
+ const { external_accounts: list } = await api("GET", "/external_accounts");
6718
+ if (opts.json) {
6719
+ console.log(JSON.stringify(list, null, 2));
6720
+ return;
6721
+ }
6722
+ if (!list.length) console.log("no vendor accounts");
6723
+ for (const a of list) console.log(vendorLine(a));
6724
+ })
6725
+ );
6726
+ vendor.command("add <vendor>").description("connect a vendor account by pasting its key or settings JSON").option("--label <label>", "a name for this account").option("--credential <value>", "the key or JSON settings (prompts if omitted)").action(
6727
+ run(async (vendorId, opts) => {
6728
+ let credential = opts.credential ?? "";
6729
+ if (!credential) {
6730
+ credential = await new Promise((resolve10) => {
6731
+ const rl = createInterface6({ input: process.stdin, output: process.stderr });
6732
+ rl.question(`${vendorId} credential: `, (a) => {
6733
+ rl.close();
6734
+ resolve10(a.trim());
6735
+ });
6736
+ });
6737
+ }
6738
+ let parsed = credential;
6739
+ try {
6740
+ parsed = JSON.parse(credential);
6741
+ } catch {
6742
+ }
6743
+ const { external_account: created } = await api("POST", "/external_accounts", { vendor: vendorId, label: opts.label, credential: parsed });
6744
+ console.log(vendorLine(created));
6745
+ })
6746
+ );
6747
+ vendor.command("rm <id>").description("disconnect a vendor account").action(
6748
+ run(async (id) => {
6749
+ await api("DELETE", `/external_accounts/${encodeURIComponent(id)}`);
6750
+ console.log(`Removed ${id}`);
6751
+ })
6752
+ );
6753
+ vendor.command("usage <id>").description("the agents and tasks that used this vendor account").option("--json", "print as JSON").action(
6754
+ run(async (id, opts) => {
6755
+ const { usage } = await api("GET", `/external_accounts/${encodeURIComponent(id)}/usage`);
6756
+ if (opts.json) {
6757
+ console.log(JSON.stringify(usage, null, 2));
6758
+ return;
6759
+ }
6760
+ console.log(`${usage.agents.length} agent${usage.agents.length === 1 ? "" : "s"}, ${usage.tasks.length} task${usage.tasks.length === 1 ? "" : "s"}`);
6761
+ const agents = usage.agents;
6762
+ for (const a of agents) console.log(` agent ${a.id} ${a.name}`);
6763
+ const tasks = usage.tasks;
6764
+ for (const t of tasks) console.log(` task ${t.session_id} ${t.title ?? "(untitled)"} ${t.last_used_at.slice(0, 10)}`);
6765
+ })
6766
+ );
6767
+ var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
6768
+ browserProfile.command("list").description("your workspace's browser profiles: the sites your agents stay signed in to").option("--json", "print as JSON").action(
6769
+ run(async (opts) => {
6770
+ const profiles = await listBrowserProfiles();
6771
+ if (opts.json) {
6772
+ console.log(JSON.stringify(profiles, null, 2));
6773
+ return;
6774
+ }
6775
+ if (!profiles.length) console.log("no browser profiles");
6776
+ for (const p of profiles) console.log(renderBrowserProfile(p));
6777
+ })
6778
+ );
6779
+ browserProfile.command("sites <scope>").description("list the sites in a profile: workspace or private").option("--json", "print as JSON").action(
6780
+ run(async (scope, opts) => {
6781
+ const sites = await listBrowserProfileSites(scope);
6782
+ if (opts.json) {
6783
+ console.log(JSON.stringify(sites, null, 2));
6784
+ return;
6785
+ }
6786
+ if (!sites.length) console.log("no sites");
6787
+ console.log(`${"SITE".padEnd(40)} ${"STATUS".padEnd(14)} SIGNED IN BY LAST USED`);
6788
+ for (const s of sites) console.log(renderBrowserProfileSite(s));
6789
+ })
6790
+ );
6791
+ browserProfile.command("import <scope>").description("import saved browser state into a profile (workspace or private); reads Playwright storage state JSON from stdin or --file").option("--file <path>", "path to a Playwright storage state JSON file (reads stdin if omitted)").action(
6792
+ run(async (scope, opts) => {
6793
+ let raw;
6794
+ if (opts.file) {
6795
+ const { readFileSync: rf } = await import("fs");
6796
+ raw = rf(opts.file, "utf8");
6797
+ } else {
6798
+ raw = await new Promise((resolve10) => {
6799
+ const chunks = [];
6800
+ process.stdin.on("data", (d) => chunks.push(d));
6801
+ process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString()));
6802
+ });
6803
+ }
6804
+ const state = JSON.parse(raw);
6805
+ const result = await importBrowserProfileState(scope, { state });
6806
+ console.log(`Imported ${result.sites.length} site${result.sites.length === 1 ? "" : "s"}: ${result.sites.join(", ")}`);
6807
+ })
6808
+ );
6809
+ browserProfile.command("remove-site <scope> <site>").description("remove a site from a profile (the agent will need to sign in again)").action(
6810
+ run(async (scope, site) => {
6811
+ await removeBrowserProfileSite(scope, site);
6812
+ console.log(`Removed ${site} from ${scope}`);
6813
+ })
6814
+ );
6815
+ browserProfile.command("move-site <scope> <site> <to>").description("move a site between profiles: <scope> is the current one, <to> is workspace or private").action(
6816
+ run(async (scope, site, to) => {
6817
+ if (to !== "workspace" && to !== "private") throw new ApiError(400, "invalid_request", "to must be workspace or private");
6818
+ const result = await moveBrowserProfileSite(scope, site, to);
6819
+ console.log(`Moved ${site} from ${scope} to ${result.to}`);
6820
+ })
6821
+ );
6822
+ var notifications = program.command("notifications").description("email and browser push notification preferences for tasks");
6823
+ notifications.command("get").description("show the current notification preferences").option("--json", "print as JSON").action(
6824
+ run(async (opts) => {
6825
+ const { notification_preferences: prefs } = await api("GET", "/notification-preferences");
6826
+ if (opts.json) {
6827
+ console.log(JSON.stringify(prefs, null, 2));
6828
+ return;
6829
+ }
6830
+ console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
6831
+ console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
6832
+ })
6833
+ );
6834
+ notifications.command("set").description("update notification preferences (pass at least one flag)").option("--email-needs-you <bool>", "email when the task needs your input: true or false").option("--email-done <bool>", "email when the task finishes: true or false").option("--push-needs-you <bool>", "push notification when the task needs your input: true or false").option("--push-done <bool>", "push notification when the task finishes: true or false").action(
6835
+ run(async (opts) => {
6836
+ const parseBool = (v) => v === void 0 ? void 0 : v === "true" ? true : v === "false" ? false : (() => {
6837
+ throw new ApiError(400, "invalid_request", "value must be true or false");
6838
+ })();
6839
+ const email = {};
6840
+ const push = {};
6841
+ const ny_e = parseBool(opts.emailNeedsYou);
6842
+ if (ny_e !== void 0) email.needs_you = ny_e;
6843
+ const d_e = parseBool(opts.emailDone);
6844
+ if (d_e !== void 0) email.done = d_e;
6845
+ const ny_p = parseBool(opts.pushNeedsYou);
6846
+ if (ny_p !== void 0) push.needs_you = ny_p;
6847
+ const d_p = parseBool(opts.pushDone);
6848
+ if (d_p !== void 0) push.done = d_p;
6849
+ if (!Object.keys(email).length && !Object.keys(push).length) throw new ApiError(400, "invalid_request", "pass at least one --email-* or --push-* flag");
6850
+ const { notification_preferences: prefs } = await api("PATCH", "/notification-preferences", {
6851
+ ...Object.keys(email).length ? { email } : {},
6852
+ ...Object.keys(push).length ? { push } : {}
6853
+ });
6854
+ console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
6855
+ console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
6856
+ })
6857
+ );
6858
+ var helpBase = () => agentUseBase().replace(/\/$/, "");
6859
+ async function helpFetch(path) {
6860
+ const res = await fetch(`${helpBase()}${path}`, { headers: { "user-agent": USER_AGENT } });
6861
+ if (!res.ok) {
6862
+ let msg = `${res.status} ${res.statusText}`;
6863
+ try {
6864
+ const b = await res.json();
6865
+ if (b.error) msg = b.error;
6866
+ } catch {
6867
+ }
6868
+ throw new ApiError(res.status, "http_error", msg);
6869
+ }
6870
+ return await res.json();
6871
+ }
6872
+ var help = program.command("help").description("search the AgentSky help center");
6873
+ help.command("search <query...>").description("search help articles by keyword").option("--top <n>", "how many results (1\u201320, default 5)", "5").option("--json", "print as JSON").action(
6874
+ run(async (words, opts) => {
6875
+ const q = words.join(" ");
6876
+ const topK = Math.min(Math.max(parseInt(opts.top, 10) || 5, 1), 20);
6877
+ const { results } = await helpFetch(`/api/help/search?q=${encodeURIComponent(q)}&top_k=${topK}`);
6878
+ if (opts.json) {
6879
+ console.log(JSON.stringify(results, null, 2));
6880
+ return;
6881
+ }
6882
+ if (!results.length) {
6883
+ console.log("no results");
6884
+ return;
6885
+ }
6886
+ for (const r of results) console.log(`${r.slug.padEnd(40)} [${r.category}] ${r.title}
6887
+ ${r.snippet.slice(0, 100)}`);
6888
+ })
6889
+ );
6890
+ help.command("get <slug>").description("read a help article by its slug").option("--json", "print as JSON").action(
6891
+ run(async (slug, opts) => {
6892
+ const article = await helpFetch(`/api/help/${encodeURIComponent(slug)}`);
6893
+ if (opts.json) {
6894
+ console.log(JSON.stringify(article, null, 2));
6895
+ return;
6896
+ }
6897
+ console.log(`# ${article.title}
6898
+ `);
6899
+ console.log(article.body);
6900
+ })
6901
+ );
6164
6902
  var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
6165
6903
  vault.command("list").description("the workspace's vaults").action(
6166
6904
  run(async () => {
@@ -6250,7 +6988,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
6250
6988
  }));
6251
6989
  var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
6252
6990
  traceCmd.command("start").description("start a trace: a grant for this machine, a root span, and the spool the hooks write to").option("--name <name>", "a name for the task").option("--source <source>", "how this machine reports itself", "claude-code").option("--hooks [settings]", "also install the Claude Code hooks (default: ./.claude/settings.local.json)").option("--capture <scopes>", "also send content, redacted on this machine first: any of prompts,tool_output,files").option("--file <path>", "a file the files scope may send (repeatable)", collect).option("--dir <path>", "a folder whose files the files scope may send when a file tool touches them (repeatable)", collect).action(run(async (opts) => {
6253
- const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-QLJH7VK6.js");
6991
+ const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-DI2SQI7M.js");
6254
6992
  if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
6255
6993
  const scopes = parseContentScopes(opts.capture);
6256
6994
  const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
@@ -6285,7 +7023,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
6285
7023
  }));
6286
7024
  traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
6287
7025
  try {
6288
- const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-QLJH7VK6.js");
7026
+ const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-DI2SQI7M.js");
6289
7027
  const chunks = [];
6290
7028
  for await (const chunk of process.stdin) chunks.push(chunk);
6291
7029
  const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
@@ -6298,14 +7036,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
6298
7036
  }
6299
7037
  });
6300
7038
  traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
6301
- const { readState, flush, grantSender } = await import("./trace-QLJH7VK6.js");
7039
+ const { readState, flush, grantSender } = await import("./trace-DI2SQI7M.js");
6302
7040
  const state = readState();
6303
7041
  if (!state) throw new Error("no trace is running here");
6304
7042
  const outcome = await flush(grantSender(state), { attempts: 4 });
6305
7043
  console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
6306
7044
  }));
6307
7045
  traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
6308
- const { readState, spoolStatus } = await import("./trace-QLJH7VK6.js");
7046
+ const { readState, spoolStatus } = await import("./trace-DI2SQI7M.js");
6309
7047
  const state = readState();
6310
7048
  if (!state) {
6311
7049
  console.log("No trace is running here.");
@@ -6315,7 +7053,7 @@ traceCmd.command("status").description("the running trace and its spool").action
6315
7053
  console.log(`${state.trace_id} as ${state.source}: ${spool.pending} pending, ${spool.dropped_full} dropped (spool full), ${spool.dropped_expired} given up, ${spool.refused} refused`);
6316
7054
  }));
6317
7055
  traceCmd.command("show [id]").description("the trace bundle: spans, the sessions they reach, cost counted once per session").option("--json", "print the bundle as JSON").action(run(async (id, opts) => {
6318
- const { readState } = await import("./trace-QLJH7VK6.js");
7056
+ const { readState } = await import("./trace-DI2SQI7M.js");
6319
7057
  const ref = id ?? readState()?.trace_id;
6320
7058
  if (!ref) throw new Error("name a trace, or start one here");
6321
7059
  const bundle = await api("GET", `/traces/${ref}`);
@@ -6342,7 +7080,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
6342
7080
  for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
6343
7081
  }));
6344
7082
  traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
6345
- const { readState, record, flush, grantSender, clearState } = await import("./trace-QLJH7VK6.js");
7083
+ const { readState, record, flush, grantSender, clearState } = await import("./trace-DI2SQI7M.js");
6346
7084
  const state = readState();
6347
7085
  if (!state) throw new Error("no trace is running here");
6348
7086
  if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
@@ -6352,7 +7090,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
6352
7090
  console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
6353
7091
  }));
6354
7092
  traceCmd.command("sync [payload]").description("upload this machine's Claude Code and Codex sessions to the matching workspace's trace library, redacted here first").option("--enable", "turn sync on for this machine").option("--disable", "turn sync off; nothing more is uploaded").option("--route <match=workspace>", "send sessions whose folder path contains <match> to <workspace> (repeatable; replaces the saved rules)", collect).option("--default <workspace>", "where a session no rule matches goes").option("--keep-local", "keep sessions no rule matches on this machine (the default)").option("--session <ref>", "only this session: its id or transcript path (repeatable)", collect).option("--full", "re-read every session, not only files that changed").option("--limit <n>", "at most this many changed sessions this run", (v) => Number.parseInt(v, 10)).option("--dry-run", "show what would be uploaded without uploading").option("--hook", "run from a Claude Code SessionEnd hook or Codex notify: upload that session in the background and return at once").option("--install-schedule", "also sweep every 15 minutes (macOS launchd)").option("--status", "what sync has done on this machine").option("--quiet", "print only failures").action(async (payload, opts) => {
6355
- const lib = await import("./trace-library-FS5PCBJC.js");
7093
+ const lib = await import("./trace-library-4WWKCSVU.js");
6356
7094
  if (opts.hook) {
6357
7095
  try {
6358
7096
  let raw = payload ?? "";
@@ -6400,10 +7138,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
6400
7138
  console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
6401
7139
  }
6402
7140
  if (opts.installSchedule) {
6403
- const { join: join16 } = await import("path");
7141
+ const { join: join17 } = await import("path");
6404
7142
  const { homedir: homedir8 } = await import("os");
6405
7143
  const { execFileSync } = await import("child_process");
6406
- const log = join16(homedir8(), ".config", "sky", "trace-sync.log");
7144
+ const log = join17(homedir8(), ".config", "sky", "trace-sync.log");
6407
7145
  const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
6408
7146
  try {
6409
7147
  execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
@@ -6414,10 +7152,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
6414
7152
  }
6415
7153
  if (opts.status) {
6416
7154
  const { readFileSync: readFileSync16 } = await import("fs");
6417
- const { join: join16 } = await import("path");
7155
+ const { join: join17 } = await import("path");
6418
7156
  let state = { sessions: {} };
6419
7157
  try {
6420
- state = JSON.parse(readFileSync16(join16(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
7158
+ state = JSON.parse(readFileSync16(join17(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
6421
7159
  } catch {
6422
7160
  }
6423
7161
  const counts = /* @__PURE__ */ new Map();
@@ -6450,7 +7188,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
6450
7188
  })();
6451
7189
  });
6452
7190
  traceCmd.command("search <query...>").description("search every session in a workspace's trace library (literal substring)").option("--workspace <slug>", "the workspace to search (default: the current one)").option("--limit <n>", "hits per page", (v) => Number.parseInt(v, 10), 20).option("--cursor <cursor>", "the next page of an earlier search").option("--json", "print the page as JSON").action(run(async (words, opts) => {
6453
- const { libraryRequest } = await import("./trace-library-FS5PCBJC.js");
7191
+ const { libraryRequest } = await import("./trace-library-4WWKCSVU.js");
6454
7192
  const workspace2 = opts.workspace ?? currentUniverse();
6455
7193
  if (!workspace2) throw new Error("name a workspace with --workspace");
6456
7194
  const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
@@ -6467,7 +7205,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
6467
7205
  if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
6468
7206
  }));
6469
7207
  traceCmd.command("library").description("list the sessions in a workspace's trace library, most recently updated first").option("--workspace <slug>", "the workspace (default: the current one)").option("--limit <n>", "how many", (v) => Number.parseInt(v, 10), 20).option("--json", "print the page as JSON").action(run(async (opts) => {
6470
- const { libraryRequest } = await import("./trace-library-FS5PCBJC.js");
7208
+ const { libraryRequest } = await import("./trace-library-4WWKCSVU.js");
6471
7209
  const workspace2 = opts.workspace ?? currentUniverse();
6472
7210
  if (!workspace2) throw new Error("name a workspace with --workspace");
6473
7211
  const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
@@ -6497,5 +7235,162 @@ workerCmd.command("start <pool>").description("start a worker connected to the g
6497
7235
  process.on("SIGTERM", () => ac.abort());
6498
7236
  await startWorker({ poolId, token, label: options.label, apiBase: base, signal: ac.signal });
6499
7237
  });
7238
+ var compare = program.command("compare").description("side-by-side agent comparisons: create, list, read, pick a winner");
7239
+ compare.command("create").description("run one task on two or more agents simultaneously and start a comparison").requiredOption("--task <text>", "the task to send to every agent").requiredOption("--agent <type>", "the main agent's harness type (e.g. claude_code, hermes)").requiredOption("--llm <model>", "the main agent's model").option("--vs <type:model...>", "one or more competing agents in type:model form (e.g. codex:gpt-6)", (val, acc) => [...acc, val], []).option("--effort <level>", "reasoning effort for every agent (low|medium|high)").option("--json", "print the created comparison as JSON").action(
7240
+ run(async (opts) => {
7241
+ const competitors = (opts.vs ?? []).map((v) => {
7242
+ const [agentType, llm] = v.split(":");
7243
+ if (!agentType || !llm) throw new ApiError(400, "invalid_request", `--vs must be type:model, got "${v}"`);
7244
+ return { agentType, llm, ...opts.effort ? { reasoningEffort: opts.effort } : {} };
7245
+ });
7246
+ if (!competitors.length) throw new ApiError(400, "invalid_request", "Provide at least one --vs type:model competitor.");
7247
+ const requestId = `sky-compare-${Date.now()}`;
7248
+ const result = await createComparison({
7249
+ requestId,
7250
+ agentType: opts.agent,
7251
+ llm: opts.llm,
7252
+ task: opts.task,
7253
+ compare: competitors,
7254
+ ...opts.effort ? { reasoningEffort: opts.effort } : {}
7255
+ });
7256
+ if (opts.json) {
7257
+ console.log(JSON.stringify(result, null, 2));
7258
+ return;
7259
+ }
7260
+ console.log(`created ${result.comparisonId}`);
7261
+ for (const col of result.columns) console.log(` column ${col.columnId} session ${col.sessionId}`);
7262
+ })
7263
+ );
7264
+ compare.command("list").description("your workspace comparisons, most recently updated first").option("--archived", "list archived comparisons instead").option("--json", "print as JSON").action(
7265
+ run(async (opts) => {
7266
+ const comparisons = await listComparisons({ archived: opts.archived });
7267
+ if (opts.json) {
7268
+ console.log(JSON.stringify(comparisons, null, 2));
7269
+ return;
7270
+ }
7271
+ if (!comparisons.length) console.log("no comparisons");
7272
+ for (const c of comparisons) console.log(renderComparisonSummary(c));
7273
+ })
7274
+ );
7275
+ compare.command("show <id>").description("one comparison: task, agents, status, and the vote verdict").option("--json", "print as JSON").action(
7276
+ run(async (id, opts) => {
7277
+ const detail = await getComparison(id);
7278
+ if (opts.json) {
7279
+ console.log(JSON.stringify(detail, null, 2));
7280
+ return;
7281
+ }
7282
+ console.log(`comparison ${detail.id}`);
7283
+ if (detail.title) console.log(` title: ${detail.title}`);
7284
+ if (detail.openerText) console.log(` task: ${detail.openerText}`);
7285
+ console.log(` created ${detail.createdAt}`);
7286
+ for (const col of detail.columns) {
7287
+ const masked = !col.agentType;
7288
+ const identity = masked ? "(hidden \u2014 Arena match pending)" : `${col.agentType ?? "?"}/${col.llm ?? "?"}`;
7289
+ console.log(` column ${col.columnId} session ${col.sessionId ?? "-"} ${identity} ${col.podStatus ?? ""}`);
7290
+ }
7291
+ if (detail.arena) console.log(` arena kind=${detail.arena.kind} status=${detail.arena.status} hidden=${detail.arena.hidden}`);
7292
+ if (detail.vote) console.log(` vote outcome=${detail.vote.outcome} winner=${detail.vote.winnerSessionId ?? "none"} sealed=${detail.vote.sealed}`);
7293
+ })
7294
+ );
7295
+ compare.command("pick <id>").description('record the winning column \u2014 or a tie or "both_bad" \u2014 for this comparison').option("--winner <columnId>", "the columnId of the winning column").option("--tie", "record a tie instead of a winner").option("--both-bad", "record that neither agent did the job").option("--json", "print the verdict as JSON").action(
7296
+ run(async (id, opts) => {
7297
+ const choices = [opts.winner, opts.tie, opts.bothBad].filter(Boolean).length;
7298
+ if (choices !== 1) throw new ApiError(400, "invalid_request", "Provide exactly one of --winner, --tie or --both-bad.");
7299
+ const outcome = opts.tie ? "tie" : opts.bothBad ? "both_bad" : void 0;
7300
+ const result = await pickWinner(id, { winnerColumnId: opts.winner, outcome });
7301
+ if (opts.json) {
7302
+ console.log(JSON.stringify(result, null, 2));
7303
+ return;
7304
+ }
7305
+ if (result.winnerSessionId) console.log(`winner: session ${result.winnerSessionId}`);
7306
+ else console.log("recorded: " + (outcome ?? "pick"));
7307
+ if (result.revealed) console.log("identities revealed:");
7308
+ for (const s of result.seats) console.log(` ${s.sessionId} ${s.agentType}/${s.llm}`);
7309
+ })
7310
+ );
7311
+ var thread = program.command("thread").description("workspace threads: the conversation history across all agents");
7312
+ thread.command("list").description("your workspace threads, most recently updated first (includes comparisons)").option("--archived", "list archived threads only").option("--all", "include both active and archived").option("--json", "print as JSON").action(
7313
+ run(async (opts) => {
7314
+ const filter = opts.all ? "all" : opts.archived ? "archived" : "active";
7315
+ const threads = await listThreads2({ filter });
7316
+ if (opts.json) {
7317
+ console.log(JSON.stringify(threads, null, 2));
7318
+ return;
7319
+ }
7320
+ if (!threads.length) console.log("no threads");
7321
+ for (const t of threads) console.log(renderThreadSummary(t));
7322
+ })
7323
+ );
7324
+ session.command("feedback <id>").description("read the thumbs-up/down feedback on this session's messages").option("--json", "print as JSON").action(
7325
+ run(async (id, opts) => {
7326
+ const result = await api(
7327
+ "GET",
7328
+ `/sessions/${encodeURIComponent(id)}/feedback`
7329
+ );
7330
+ if (opts.json) {
7331
+ console.log(JSON.stringify(result, null, 2));
7332
+ return;
7333
+ }
7334
+ const entries = Object.entries(result.reports);
7335
+ if (!entries.length) {
7336
+ console.log("no feedback recorded");
7337
+ return;
7338
+ }
7339
+ for (const [msgId, r] of entries) console.log(`${msgId.slice(0, 12)} ${r.vote} ${(r.reasons ?? []).join(", ")}`);
7340
+ })
7341
+ );
7342
+ session.command("side-chat <id>").description("ask a question about this session's transcript (answered beside it, never sent to the agent)").requiredOption("--question <text>", "the question to ask").requiredOption("--transcript <text>", "the session transcript or relevant excerpt").option("--quote <text>", "a specific passage the question targets").option("--json", "print as JSON").action(
7343
+ run(async (id, opts) => {
7344
+ const result = await api("POST", `/sessions/${encodeURIComponent(id)}/side-chat`, {
7345
+ transcript: opts.transcript,
7346
+ history: [],
7347
+ question: opts.question,
7348
+ ...opts.quote ? { quote: opts.quote } : {}
7349
+ });
7350
+ if (opts.json) {
7351
+ console.log(JSON.stringify(result, null, 2));
7352
+ return;
7353
+ }
7354
+ console.log(result.answer);
7355
+ if (result.looked?.length) console.log(`
7356
+ (sources: ${result.looked.join(", ")})`);
7357
+ })
7358
+ );
7359
+ session.command("code-review <id>").description("run the code-review assistant on this session's diff or file").requiredOption("--diff <text>", "the diff or file content to review").option("--filename <name>", "the filename for language-aware review").option("--json", "print as JSON").action(
7360
+ run(async (id, opts) => {
7361
+ const result = await api(
7362
+ "POST",
7363
+ `/sessions/${encodeURIComponent(id)}/code-review`,
7364
+ { diff: opts.diff, ...opts.filename ? { filename: opts.filename } : {} }
7365
+ );
7366
+ if (opts.json) {
7367
+ console.log(JSON.stringify(result, null, 2));
7368
+ return;
7369
+ }
7370
+ console.log(result.review);
7371
+ if (result.issues?.length) {
7372
+ console.log("\nIssues:");
7373
+ for (const issue of result.issues) {
7374
+ const loc = issue.line ? ` line ${issue.line}` : "";
7375
+ console.log(` [${issue.severity}]${loc} ${issue.message}`);
7376
+ }
7377
+ }
7378
+ })
7379
+ );
7380
+ session.command("snapshot <id>").description("read or revoke the public share link for a session").requiredOption("--thread <threadId>", "the thread/comparison that owns this session").option("--revoke", "revoke the snapshot link instead of reading it").option("--json", "print as JSON").action(
7381
+ run(async (id, opts) => {
7382
+ const path = `/sessions/${encodeURIComponent(id)}/snapshot?thread=${encodeURIComponent(opts.thread)}`;
7383
+ const result = await api(opts.revoke ? "DELETE" : "GET", path);
7384
+ if (opts.json) {
7385
+ console.log(JSON.stringify(result, null, 2));
7386
+ return;
7387
+ }
7388
+ if (opts.revoke) {
7389
+ console.log(result.url === null ? "snapshot link revoked" : `still set: ${result.url}`);
7390
+ return;
7391
+ }
7392
+ console.log(result.url ?? "no snapshot link set");
7393
+ })
7394
+ );
6500
7395
  var ARGUMENTS_FOLLOW_THE_SCRIPT_PATH_ON_EVERY_RUNTIME = { from: "node" };
6501
7396
  program.parseAsync(process.argv, ARGUMENTS_FOLLOW_THE_SCRIPT_PATH_ON_EVERY_RUNTIME).catch(fail);