@agentsky/sky 2.18.0 → 2.19.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/{chunk-PL5KNPCX.js → chunk-4MVWXHZY.js} +2 -2
- package/dist/{chunk-MUHFB3LC.js → chunk-MOYCAI4S.js} +1 -1
- package/dist/{chunk-ZJVFWFOY.js → chunk-UJ3D24IG.js} +1480 -153
- package/dist/index.js +733 -27
- package/dist/products.d.ts +3 -0
- package/dist/products.js +1 -1
- package/dist/{src-TJJKOFXZ.js → src-5HKXRXLU.js} +1 -1
- package/dist/{trace-QLJH7VK6.js → trace-VZX6UIOF.js} +2 -2
- package/dist/{trace-library-FS5PCBJC.js → trace-library-EBH2GYDA.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
resolveSessionRef,
|
|
10
10
|
streamEvents,
|
|
11
11
|
uploadFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-4MVWXHZY.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-
|
|
23
|
+
} from "./chunk-MOYCAI4S.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-
|
|
45
|
+
} from "./chunk-UJ3D24IG.js";
|
|
45
46
|
import "./chunk-MLKGABMK.js";
|
|
46
47
|
|
|
47
48
|
// src/index.ts
|
|
48
|
-
import { existsSync as existsSync10, readFileSync as readFileSync15, writeFileSync as
|
|
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((thread) => thread.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 thread of record.threads) {
|
|
4146
|
+
for (const event of record.events[thread.id] ?? []) {
|
|
4147
|
+
if (event.type === "human.decision" || event.type === "outcome.observed") out.push({ ...event, thread_id: thread.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((thread) => thread.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((thread) => thread.relation === "primary") ?? record.threads[0];
|
|
4281
|
+
const harnesses = [...new Set(record.threads.map((thread) => thread.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 thread of threads) events[thread.id] = await pagedEvents(thread.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((thread) => thread.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(thread) {
|
|
4425
|
+
const spend = (Number(thread.usage.spent_usd_micros) / 1e6).toFixed(4);
|
|
4426
|
+
const env = thread.environment?.repo ? ` ${thread.environment.repo}${thread.environment.commit ? `@${thread.environment.commit.slice(0, 7)}` : ""}` : "";
|
|
4427
|
+
return `${thread.id} ${thread.relation.padEnd(7)} ${thread.source.padEnd(12)} ${thread.agent.harness}/${thread.agent.model} ${thread.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,45 @@ 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
|
+
|
|
4324
4707
|
// src/harness.ts
|
|
4325
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
4326
|
-
import { dirname as
|
|
4708
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync5, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
4709
|
+
import { dirname as dirname8, join as join16, resolve as resolve8 } from "path";
|
|
4327
4710
|
var HARNESS_FILE = "harness.json";
|
|
4328
4711
|
function harnessFilePath(dir) {
|
|
4329
|
-
return
|
|
4712
|
+
return join16(resolve8(dir), HARNESS_FILE);
|
|
4330
4713
|
}
|
|
4331
4714
|
function readHarnessFile(dir) {
|
|
4332
4715
|
const path = harnessFilePath(dir);
|
|
@@ -4364,8 +4747,8 @@ function harnessInit(dir) {
|
|
|
4364
4747
|
if (existsSync9(path)) {
|
|
4365
4748
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
4366
4749
|
}
|
|
4367
|
-
|
|
4368
|
-
|
|
4750
|
+
mkdirSync5(dirname8(path), { recursive: true });
|
|
4751
|
+
writeFileSync7(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
4369
4752
|
`);
|
|
4370
4753
|
console.log(`Created ${path}`);
|
|
4371
4754
|
console.log(INIT_COMMENT);
|
|
@@ -5070,7 +5453,7 @@ sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1
|
|
|
5070
5453
|
run(async (ref, path, opts) => {
|
|
5071
5454
|
const bytes = await readFile(await resolveSessionRef(ref), path);
|
|
5072
5455
|
if (opts.out) {
|
|
5073
|
-
|
|
5456
|
+
writeFileSync8(opts.out, bytes);
|
|
5074
5457
|
console.error(`saved ${bytes.length} bytes to ${opts.out}`);
|
|
5075
5458
|
} else process.stdout.write(bytes);
|
|
5076
5459
|
})
|
|
@@ -5087,7 +5470,7 @@ sessionWorkspace.command("patch <ref> [repo]").description("the work in a reposi
|
|
|
5087
5470
|
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
5471
|
if (!patch.length) console.error("nothing changed");
|
|
5089
5472
|
else if (opts.out) {
|
|
5090
|
-
|
|
5473
|
+
writeFileSync8(opts.out, patch);
|
|
5091
5474
|
console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
|
|
5092
5475
|
} else process.stdout.write(patch);
|
|
5093
5476
|
})
|
|
@@ -5098,6 +5481,43 @@ sessionWorkspace.command("import <ref> <dir>").description("copy a local folder
|
|
|
5098
5481
|
console.log(`${imported.path} (${imported.files} files)`);
|
|
5099
5482
|
})
|
|
5100
5483
|
);
|
|
5484
|
+
var sessionBrowser = session.command("browser").description("the sandbox browser for a session (start, show status, stop)");
|
|
5485
|
+
sessionBrowser.command("show <ref>").description("show the sandbox browser status for a session").option("--json", "print as JSON").action(
|
|
5486
|
+
run(async (ref, opts) => {
|
|
5487
|
+
const id = await resolveSessionRef(ref);
|
|
5488
|
+
const { browser } = await api("GET", `/sessions/${encodeURIComponent(id)}/browser`);
|
|
5489
|
+
if (opts.json) {
|
|
5490
|
+
console.log(JSON.stringify(browser, null, 2));
|
|
5491
|
+
return;
|
|
5492
|
+
}
|
|
5493
|
+
if (!browser) {
|
|
5494
|
+
console.log("no browser");
|
|
5495
|
+
return;
|
|
5496
|
+
}
|
|
5497
|
+
const b = browser;
|
|
5498
|
+
console.log(`${b.status ?? "unknown"}${b.url ? ` ${b.url}` : ""}${b.entry_url ? ` entry: ${b.entry_url}` : ""}`);
|
|
5499
|
+
})
|
|
5500
|
+
);
|
|
5501
|
+
sessionBrowser.command("start <ref>").description("start a browser for a session (provisions it if not running)").option("--json", "print as JSON").action(
|
|
5502
|
+
run(async (ref, opts) => {
|
|
5503
|
+
const id = await resolveSessionRef(ref);
|
|
5504
|
+
const { browser } = await api("POST", `/sessions/${encodeURIComponent(id)}/browser`, {});
|
|
5505
|
+
if (opts.json) {
|
|
5506
|
+
console.log(JSON.stringify(browser, null, 2));
|
|
5507
|
+
return;
|
|
5508
|
+
}
|
|
5509
|
+
const b = browser;
|
|
5510
|
+
console.log(`${b.status ?? "starting"}${b.entry_url ? ` entry: ${b.entry_url}` : ""}`);
|
|
5511
|
+
})
|
|
5512
|
+
);
|
|
5513
|
+
sessionBrowser.command("stop <ref>").description("stop the browser for a session").action(
|
|
5514
|
+
run(async (ref) => {
|
|
5515
|
+
const id = await resolveSessionRef(ref);
|
|
5516
|
+
const { browser } = await api("DELETE", `/sessions/${encodeURIComponent(id)}/browser`);
|
|
5517
|
+
const b = browser;
|
|
5518
|
+
console.log(b ? b.status ?? "stopped" : "stopped");
|
|
5519
|
+
})
|
|
5520
|
+
);
|
|
5101
5521
|
function interactionMode(opts) {
|
|
5102
5522
|
if (opts.plan && opts.build) throw new ApiError(400, "invalid_request", "pass --plan or --build, not both");
|
|
5103
5523
|
return opts.plan ? "plan" : opts.build ? "build" : void 0;
|
|
@@ -5558,7 +5978,7 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
5558
5978
|
run(async (taskId, path, opts) => {
|
|
5559
5979
|
const bytes = await readTaskFile(taskId, path);
|
|
5560
5980
|
if (opts.out) {
|
|
5561
|
-
|
|
5981
|
+
writeFileSync8(opts.out, bytes);
|
|
5562
5982
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
5563
5983
|
} else process.stdout.write(bytes);
|
|
5564
5984
|
})
|
|
@@ -5575,6 +5995,15 @@ task.command("rm <task> <path>").description("remove a path from the task's file
|
|
|
5575
5995
|
console.log(`Removed ${path}`);
|
|
5576
5996
|
})
|
|
5577
5997
|
);
|
|
5998
|
+
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(
|
|
5999
|
+
run(async (taskId, opts) => {
|
|
6000
|
+
const tier = opts.tier === "gold" || opts.tier === "exploratory" ? opts.tier : "silver";
|
|
6001
|
+
const outDir = opts.out ?? `./harbor-${taskId.replace(/[^A-Za-z0-9_-]/g, "_")}`;
|
|
6002
|
+
const { written, record } = await exportTaskHarbor(taskId, outDir, { tier, redactNames: parseRedactNames(opts.redact) });
|
|
6003
|
+
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"})`);
|
|
6004
|
+
if (!written.includes("environment/Dockerfile")) console.log("environment/: not resettable from the record \u2014 see environment/README.md before running with Harbor");
|
|
6005
|
+
})
|
|
6006
|
+
);
|
|
5578
6007
|
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
6008
|
run(async (taskId, opts) => {
|
|
5580
6009
|
const found = await getTask(taskId);
|
|
@@ -5593,6 +6022,42 @@ task.command("answer <task> <request>").description("answer an open request; the
|
|
|
5593
6022
|
console.log(`Answered ${requestId}`);
|
|
5594
6023
|
})
|
|
5595
6024
|
);
|
|
6025
|
+
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(
|
|
6026
|
+
run(async (taskId, opts) => {
|
|
6027
|
+
const threads = await listThreads(taskId);
|
|
6028
|
+
if (opts.json) {
|
|
6029
|
+
console.log(JSON.stringify({ threads }));
|
|
6030
|
+
return;
|
|
6031
|
+
}
|
|
6032
|
+
for (const thread of threads) console.log(renderThread(thread));
|
|
6033
|
+
})
|
|
6034
|
+
);
|
|
6035
|
+
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(
|
|
6036
|
+
run(async (sessionId, kind, opts) => {
|
|
6037
|
+
const value = opts.yes ? true : opts.no ? false : void 0;
|
|
6038
|
+
const { event } = await recordDecision(sessionId, kind, { target: opts.target, value, reason: opts.reason, idempotencyKey: opts.key });
|
|
6039
|
+
console.log(`Recorded ${kind} on ${event.sessionId} (${event.id})`);
|
|
6040
|
+
})
|
|
6041
|
+
);
|
|
6042
|
+
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(
|
|
6043
|
+
run(async (sessionId, kind, opts) => {
|
|
6044
|
+
const { event } = await recordOutcome(sessionId, kind, { ref: opts.ref, idempotencyKey: opts.key });
|
|
6045
|
+
console.log(`Recorded ${kind} on ${event.sessionId} (${event.id})`);
|
|
6046
|
+
})
|
|
6047
|
+
);
|
|
6048
|
+
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(
|
|
6049
|
+
run(async (taskId, opts) => {
|
|
6050
|
+
const { task: updated } = await api("POST", `/tasks/${encodeURIComponent(taskId)}/agent-fee/dispute`, { reason: opts.reason, ...opts.session ? { session_id: opts.session } : {} });
|
|
6051
|
+
console.log(`Disputed fee on task ${updated.id}`);
|
|
6052
|
+
})
|
|
6053
|
+
);
|
|
6054
|
+
task.command("verdict <task> <did-the-job>").description("record whether the task did the job: `yes` or `no` (replaces an earlier answer)").action(
|
|
6055
|
+
run(async (taskId, value) => {
|
|
6056
|
+
if (value !== "yes" && value !== "no") throw new Error("value must be yes or no");
|
|
6057
|
+
const { task: updated } = await api("PUT", `/tasks/${encodeURIComponent(taskId)}/completion_answer`, { did_the_job: value === "yes" });
|
|
6058
|
+
console.log(`Recorded verdict on task ${updated.id}: did_the_job=${value === "yes"}`);
|
|
6059
|
+
})
|
|
6060
|
+
);
|
|
5596
6061
|
var skills = program.command("skills").description("the workspace's skill store: upload a skill, then attach it to an agent by id");
|
|
5597
6062
|
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
6063
|
run(async (dir, opts) => {
|
|
@@ -5614,6 +6079,27 @@ skills.command("list").description("the workspace's stored skills").action(
|
|
|
5614
6079
|
for (const skill of list) console.log(`${skill.id} ${skill.displayName ?? ""}`);
|
|
5615
6080
|
})
|
|
5616
6081
|
);
|
|
6082
|
+
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(
|
|
6083
|
+
run(async (opts) => {
|
|
6084
|
+
const summary = await getSkillUsage(opts.since);
|
|
6085
|
+
if (opts.json) {
|
|
6086
|
+
console.log(JSON.stringify(summary, null, 2));
|
|
6087
|
+
return;
|
|
6088
|
+
}
|
|
6089
|
+
if (!summary.skills.length) {
|
|
6090
|
+
console.log("No skill usage recorded in this period.");
|
|
6091
|
+
} else {
|
|
6092
|
+
console.log(`${"NAME".padEnd(32)} ${"VIEWS".padStart(6)} ${"MISSES".padStart(7)} LAST SEEN`);
|
|
6093
|
+
for (const s of summary.skills) {
|
|
6094
|
+
console.log(`${s.name.padEnd(32)} ${String(s.views).padStart(6)} ${String(s.misses).padStart(7)} ${s.lastSeen.slice(0, 10)}`);
|
|
6095
|
+
}
|
|
6096
|
+
}
|
|
6097
|
+
if (summary.topMissed.length) {
|
|
6098
|
+
console.log("\nTop missed:");
|
|
6099
|
+
for (const m of summary.topMissed) console.log(` ${m.name} (${m.misses})`);
|
|
6100
|
+
}
|
|
6101
|
+
})
|
|
6102
|
+
);
|
|
5617
6103
|
async function decideReflection(id, verb) {
|
|
5618
6104
|
const { reflection_proposal: p } = await api("POST", `/reflection_proposals/${encodeURIComponent(id)}/${verb}`);
|
|
5619
6105
|
console.log(`${p.id} ${p.status}`);
|
|
@@ -5653,7 +6139,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
|
|
|
5653
6139
|
run(async (id, path, opts) => {
|
|
5654
6140
|
const bytes = await readArtifactFile(id, path);
|
|
5655
6141
|
if (opts.out) {
|
|
5656
|
-
|
|
6142
|
+
writeFileSync8(opts.out, bytes);
|
|
5657
6143
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
5658
6144
|
} else process.stdout.write(bytes);
|
|
5659
6145
|
})
|
|
@@ -5951,7 +6437,7 @@ usageOptions(
|
|
|
5951
6437
|
run(async (opts) => {
|
|
5952
6438
|
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
5953
6439
|
if (opts.out) {
|
|
5954
|
-
|
|
6440
|
+
writeFileSync8(opts.out, bytes);
|
|
5955
6441
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
5956
6442
|
} else {
|
|
5957
6443
|
process.stdout.write(bytes);
|
|
@@ -6161,6 +6647,226 @@ connector.command("shared").description("everything you share with workspaces, a
|
|
|
6161
6647
|
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
6648
|
})
|
|
6163
6649
|
);
|
|
6650
|
+
var hookRule = program.command("hook-rule").description("workspace hook rules: run a webhook or block at platform-controlled points");
|
|
6651
|
+
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(
|
|
6652
|
+
run(async (opts) => {
|
|
6653
|
+
const slug = opts.workspace ?? await workspaceSlug();
|
|
6654
|
+
let data;
|
|
6655
|
+
if (opts.workspace) {
|
|
6656
|
+
data = await api("GET", `/universes/${encodeURIComponent(slug)}/hooks`);
|
|
6657
|
+
} else {
|
|
6658
|
+
data = await api("GET", "/hook_rules");
|
|
6659
|
+
}
|
|
6660
|
+
console.log(opts.json ? JSON.stringify(data, null, 2) : JSON.stringify(data, null, 2));
|
|
6661
|
+
})
|
|
6662
|
+
);
|
|
6663
|
+
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(
|
|
6664
|
+
run(async (opts) => {
|
|
6665
|
+
const raw = opts.rules ?? await new Promise((resolve10) => {
|
|
6666
|
+
const chunks = [];
|
|
6667
|
+
process.stdin.on("data", (d) => chunks.push(d));
|
|
6668
|
+
process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString()));
|
|
6669
|
+
});
|
|
6670
|
+
const rules = JSON.parse(raw);
|
|
6671
|
+
const slug = opts.workspace ?? await workspaceSlug();
|
|
6672
|
+
let result;
|
|
6673
|
+
if (opts.workspace) {
|
|
6674
|
+
result = await api("PUT", `/universes/${encodeURIComponent(slug)}/hooks`, { hooks: rules });
|
|
6675
|
+
} else {
|
|
6676
|
+
result = await api("PUT", "/hook_rules", { rules });
|
|
6677
|
+
}
|
|
6678
|
+
console.log(JSON.stringify(result, null, 2));
|
|
6679
|
+
})
|
|
6680
|
+
);
|
|
6681
|
+
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"}`;
|
|
6682
|
+
var vendor = program.command("vendor").description("vendor accounts: third-party API keys and agent logins your agents use");
|
|
6683
|
+
vendor.command("list").description("your workspace's vendor accounts").option("--json", "print as JSON").action(
|
|
6684
|
+
run(async (opts) => {
|
|
6685
|
+
const { external_accounts: list } = await api("GET", "/external_accounts");
|
|
6686
|
+
if (opts.json) {
|
|
6687
|
+
console.log(JSON.stringify(list, null, 2));
|
|
6688
|
+
return;
|
|
6689
|
+
}
|
|
6690
|
+
if (!list.length) console.log("no vendor accounts");
|
|
6691
|
+
for (const a of list) console.log(vendorLine(a));
|
|
6692
|
+
})
|
|
6693
|
+
);
|
|
6694
|
+
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(
|
|
6695
|
+
run(async (vendorId, opts) => {
|
|
6696
|
+
let credential = opts.credential ?? "";
|
|
6697
|
+
if (!credential) {
|
|
6698
|
+
credential = await new Promise((resolve10) => {
|
|
6699
|
+
const rl = createInterface6({ input: process.stdin, output: process.stderr });
|
|
6700
|
+
rl.question(`${vendorId} credential: `, (a) => {
|
|
6701
|
+
rl.close();
|
|
6702
|
+
resolve10(a.trim());
|
|
6703
|
+
});
|
|
6704
|
+
});
|
|
6705
|
+
}
|
|
6706
|
+
let parsed = credential;
|
|
6707
|
+
try {
|
|
6708
|
+
parsed = JSON.parse(credential);
|
|
6709
|
+
} catch {
|
|
6710
|
+
}
|
|
6711
|
+
const { external_account: created } = await api("POST", "/external_accounts", { vendor: vendorId, label: opts.label, credential: parsed });
|
|
6712
|
+
console.log(vendorLine(created));
|
|
6713
|
+
})
|
|
6714
|
+
);
|
|
6715
|
+
vendor.command("rm <id>").description("disconnect a vendor account").action(
|
|
6716
|
+
run(async (id) => {
|
|
6717
|
+
await api("DELETE", `/external_accounts/${encodeURIComponent(id)}`);
|
|
6718
|
+
console.log(`Removed ${id}`);
|
|
6719
|
+
})
|
|
6720
|
+
);
|
|
6721
|
+
vendor.command("usage <id>").description("the agents and tasks that used this vendor account").option("--json", "print as JSON").action(
|
|
6722
|
+
run(async (id, opts) => {
|
|
6723
|
+
const { usage } = await api("GET", `/external_accounts/${encodeURIComponent(id)}/usage`);
|
|
6724
|
+
if (opts.json) {
|
|
6725
|
+
console.log(JSON.stringify(usage, null, 2));
|
|
6726
|
+
return;
|
|
6727
|
+
}
|
|
6728
|
+
console.log(`${usage.agents.length} agent${usage.agents.length === 1 ? "" : "s"}, ${usage.tasks.length} task${usage.tasks.length === 1 ? "" : "s"}`);
|
|
6729
|
+
const agents = usage.agents;
|
|
6730
|
+
for (const a of agents) console.log(` agent ${a.id} ${a.name}`);
|
|
6731
|
+
const tasks = usage.tasks;
|
|
6732
|
+
for (const t of tasks) console.log(` task ${t.session_id} ${t.title ?? "(untitled)"} ${t.last_used_at.slice(0, 10)}`);
|
|
6733
|
+
})
|
|
6734
|
+
);
|
|
6735
|
+
var browserProfile = program.command("browser-profile").description("saved-login profiles: the sites your agents stay signed in to");
|
|
6736
|
+
browserProfile.command("list").description("your workspace's browser profiles (requires BROWSER_PROFILES to be enabled)").option("--json", "print as JSON").action(
|
|
6737
|
+
run(async (opts) => {
|
|
6738
|
+
const profiles = await listBrowserProfiles();
|
|
6739
|
+
if (opts.json) {
|
|
6740
|
+
console.log(JSON.stringify(profiles, null, 2));
|
|
6741
|
+
return;
|
|
6742
|
+
}
|
|
6743
|
+
if (!profiles.length) console.log("no browser profiles");
|
|
6744
|
+
for (const p of profiles) console.log(renderBrowserProfile(p));
|
|
6745
|
+
})
|
|
6746
|
+
);
|
|
6747
|
+
browserProfile.command("sites <scope>").description("list the sites in a profile: workspace or private").option("--json", "print as JSON").action(
|
|
6748
|
+
run(async (scope, opts) => {
|
|
6749
|
+
const sites = await listBrowserProfileSites(scope);
|
|
6750
|
+
if (opts.json) {
|
|
6751
|
+
console.log(JSON.stringify(sites, null, 2));
|
|
6752
|
+
return;
|
|
6753
|
+
}
|
|
6754
|
+
if (!sites.length) console.log("no sites");
|
|
6755
|
+
console.log(`${"SITE".padEnd(40)} ${"STATUS".padEnd(14)} SIGNED IN BY LAST USED`);
|
|
6756
|
+
for (const s of sites) console.log(renderBrowserProfileSite(s));
|
|
6757
|
+
})
|
|
6758
|
+
);
|
|
6759
|
+
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(
|
|
6760
|
+
run(async (scope, opts) => {
|
|
6761
|
+
let raw;
|
|
6762
|
+
if (opts.file) {
|
|
6763
|
+
const { readFileSync: rf } = await import("fs");
|
|
6764
|
+
raw = rf(opts.file, "utf8");
|
|
6765
|
+
} else {
|
|
6766
|
+
raw = await new Promise((resolve10) => {
|
|
6767
|
+
const chunks = [];
|
|
6768
|
+
process.stdin.on("data", (d) => chunks.push(d));
|
|
6769
|
+
process.stdin.on("end", () => resolve10(Buffer.concat(chunks).toString()));
|
|
6770
|
+
});
|
|
6771
|
+
}
|
|
6772
|
+
const state = JSON.parse(raw);
|
|
6773
|
+
const result = await importBrowserProfileState(scope, { state });
|
|
6774
|
+
console.log(`Imported ${result.sites.length} site${result.sites.length === 1 ? "" : "s"}: ${result.sites.join(", ")}`);
|
|
6775
|
+
})
|
|
6776
|
+
);
|
|
6777
|
+
browserProfile.command("remove-site <scope> <site>").description("remove a site from a profile (the agent will need to sign in again)").action(
|
|
6778
|
+
run(async (scope, site) => {
|
|
6779
|
+
await removeBrowserProfileSite(scope, site);
|
|
6780
|
+
console.log(`Removed ${site} from ${scope}`);
|
|
6781
|
+
})
|
|
6782
|
+
);
|
|
6783
|
+
browserProfile.command("move-site <scope> <site> <to>").description("move a site between profiles: <scope> is the current one, <to> is workspace or private").action(
|
|
6784
|
+
run(async (scope, site, to) => {
|
|
6785
|
+
if (to !== "workspace" && to !== "private") throw new ApiError(400, "invalid_request", "to must be workspace or private");
|
|
6786
|
+
const result = await moveBrowserProfileSite(scope, site, to);
|
|
6787
|
+
console.log(`Moved ${site} from ${scope} to ${result.to}`);
|
|
6788
|
+
})
|
|
6789
|
+
);
|
|
6790
|
+
var notifications = program.command("notifications").description("email and browser push notification preferences for tasks");
|
|
6791
|
+
notifications.command("get").description("show the current notification preferences").option("--json", "print as JSON").action(
|
|
6792
|
+
run(async (opts) => {
|
|
6793
|
+
const { notification_preferences: prefs } = await api("GET", "/notification-preferences");
|
|
6794
|
+
if (opts.json) {
|
|
6795
|
+
console.log(JSON.stringify(prefs, null, 2));
|
|
6796
|
+
return;
|
|
6797
|
+
}
|
|
6798
|
+
console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
|
|
6799
|
+
console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
|
|
6800
|
+
})
|
|
6801
|
+
);
|
|
6802
|
+
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(
|
|
6803
|
+
run(async (opts) => {
|
|
6804
|
+
const parseBool = (v) => v === void 0 ? void 0 : v === "true" ? true : v === "false" ? false : (() => {
|
|
6805
|
+
throw new ApiError(400, "invalid_request", "value must be true or false");
|
|
6806
|
+
})();
|
|
6807
|
+
const email = {};
|
|
6808
|
+
const push = {};
|
|
6809
|
+
const ny_e = parseBool(opts.emailNeedsYou);
|
|
6810
|
+
if (ny_e !== void 0) email.needs_you = ny_e;
|
|
6811
|
+
const d_e = parseBool(opts.emailDone);
|
|
6812
|
+
if (d_e !== void 0) email.done = d_e;
|
|
6813
|
+
const ny_p = parseBool(opts.pushNeedsYou);
|
|
6814
|
+
if (ny_p !== void 0) push.needs_you = ny_p;
|
|
6815
|
+
const d_p = parseBool(opts.pushDone);
|
|
6816
|
+
if (d_p !== void 0) push.done = d_p;
|
|
6817
|
+
if (!Object.keys(email).length && !Object.keys(push).length) throw new ApiError(400, "invalid_request", "pass at least one --email-* or --push-* flag");
|
|
6818
|
+
const { notification_preferences: prefs } = await api("PATCH", "/notification-preferences", {
|
|
6819
|
+
...Object.keys(email).length ? { email } : {},
|
|
6820
|
+
...Object.keys(push).length ? { push } : {}
|
|
6821
|
+
});
|
|
6822
|
+
console.log(`email: needs_you=${prefs.email.needs_you} done=${prefs.email.done}`);
|
|
6823
|
+
console.log(`push: needs_you=${prefs.push.needs_you} done=${prefs.push.done}`);
|
|
6824
|
+
})
|
|
6825
|
+
);
|
|
6826
|
+
var helpBase = () => agentUseBase().replace(/\/$/, "");
|
|
6827
|
+
async function helpFetch(path) {
|
|
6828
|
+
const res = await fetch(`${helpBase()}${path}`, { headers: { "user-agent": USER_AGENT } });
|
|
6829
|
+
if (!res.ok) {
|
|
6830
|
+
let msg = `${res.status} ${res.statusText}`;
|
|
6831
|
+
try {
|
|
6832
|
+
const b = await res.json();
|
|
6833
|
+
if (b.error) msg = b.error;
|
|
6834
|
+
} catch {
|
|
6835
|
+
}
|
|
6836
|
+
throw new ApiError(res.status, "http_error", msg);
|
|
6837
|
+
}
|
|
6838
|
+
return await res.json();
|
|
6839
|
+
}
|
|
6840
|
+
var help = program.command("help").description("search the AgentSky help center");
|
|
6841
|
+
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(
|
|
6842
|
+
run(async (words, opts) => {
|
|
6843
|
+
const q = words.join(" ");
|
|
6844
|
+
const topK = Math.min(Math.max(parseInt(opts.top, 10) || 5, 1), 20);
|
|
6845
|
+
const { results } = await helpFetch(`/api/help/search?q=${encodeURIComponent(q)}&top_k=${topK}`);
|
|
6846
|
+
if (opts.json) {
|
|
6847
|
+
console.log(JSON.stringify(results, null, 2));
|
|
6848
|
+
return;
|
|
6849
|
+
}
|
|
6850
|
+
if (!results.length) {
|
|
6851
|
+
console.log("no results");
|
|
6852
|
+
return;
|
|
6853
|
+
}
|
|
6854
|
+
for (const r of results) console.log(`${r.slug.padEnd(40)} [${r.category}] ${r.title}
|
|
6855
|
+
${r.snippet.slice(0, 100)}`);
|
|
6856
|
+
})
|
|
6857
|
+
);
|
|
6858
|
+
help.command("get <slug>").description("read a help article by its slug").option("--json", "print as JSON").action(
|
|
6859
|
+
run(async (slug, opts) => {
|
|
6860
|
+
const article = await helpFetch(`/api/help/${encodeURIComponent(slug)}`);
|
|
6861
|
+
if (opts.json) {
|
|
6862
|
+
console.log(JSON.stringify(article, null, 2));
|
|
6863
|
+
return;
|
|
6864
|
+
}
|
|
6865
|
+
console.log(`# ${article.title}
|
|
6866
|
+
`);
|
|
6867
|
+
console.log(article.body);
|
|
6868
|
+
})
|
|
6869
|
+
);
|
|
6164
6870
|
var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
|
|
6165
6871
|
vault.command("list").description("the workspace's vaults").action(
|
|
6166
6872
|
run(async () => {
|
|
@@ -6250,7 +6956,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
6250
6956
|
}));
|
|
6251
6957
|
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
6958
|
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-
|
|
6959
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-VZX6UIOF.js");
|
|
6254
6960
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
6255
6961
|
const scopes = parseContentScopes(opts.capture);
|
|
6256
6962
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -6285,7 +6991,7 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
6285
6991
|
}));
|
|
6286
6992
|
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
6993
|
try {
|
|
6288
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
6994
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-VZX6UIOF.js");
|
|
6289
6995
|
const chunks = [];
|
|
6290
6996
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
6291
6997
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -6298,14 +7004,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
6298
7004
|
}
|
|
6299
7005
|
});
|
|
6300
7006
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
6301
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
7007
|
+
const { readState, flush, grantSender } = await import("./trace-VZX6UIOF.js");
|
|
6302
7008
|
const state = readState();
|
|
6303
7009
|
if (!state) throw new Error("no trace is running here");
|
|
6304
7010
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
6305
7011
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
6306
7012
|
}));
|
|
6307
7013
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
6308
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
7014
|
+
const { readState, spoolStatus } = await import("./trace-VZX6UIOF.js");
|
|
6309
7015
|
const state = readState();
|
|
6310
7016
|
if (!state) {
|
|
6311
7017
|
console.log("No trace is running here.");
|
|
@@ -6315,7 +7021,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
6315
7021
|
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
7022
|
}));
|
|
6317
7023
|
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-
|
|
7024
|
+
const { readState } = await import("./trace-VZX6UIOF.js");
|
|
6319
7025
|
const ref = id ?? readState()?.trace_id;
|
|
6320
7026
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
6321
7027
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -6342,7 +7048,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
6342
7048
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
6343
7049
|
}));
|
|
6344
7050
|
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-
|
|
7051
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-VZX6UIOF.js");
|
|
6346
7052
|
const state = readState();
|
|
6347
7053
|
if (!state) throw new Error("no trace is running here");
|
|
6348
7054
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -6352,7 +7058,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
6352
7058
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
6353
7059
|
}));
|
|
6354
7060
|
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-
|
|
7061
|
+
const lib = await import("./trace-library-EBH2GYDA.js");
|
|
6356
7062
|
if (opts.hook) {
|
|
6357
7063
|
try {
|
|
6358
7064
|
let raw = payload ?? "";
|
|
@@ -6400,10 +7106,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
6400
7106
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
6401
7107
|
}
|
|
6402
7108
|
if (opts.installSchedule) {
|
|
6403
|
-
const { join:
|
|
7109
|
+
const { join: join17 } = await import("path");
|
|
6404
7110
|
const { homedir: homedir8 } = await import("os");
|
|
6405
7111
|
const { execFileSync } = await import("child_process");
|
|
6406
|
-
const log =
|
|
7112
|
+
const log = join17(homedir8(), ".config", "sky", "trace-sync.log");
|
|
6407
7113
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
6408
7114
|
try {
|
|
6409
7115
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -6414,10 +7120,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
6414
7120
|
}
|
|
6415
7121
|
if (opts.status) {
|
|
6416
7122
|
const { readFileSync: readFileSync16 } = await import("fs");
|
|
6417
|
-
const { join:
|
|
7123
|
+
const { join: join17 } = await import("path");
|
|
6418
7124
|
let state = { sessions: {} };
|
|
6419
7125
|
try {
|
|
6420
|
-
state = JSON.parse(readFileSync16(
|
|
7126
|
+
state = JSON.parse(readFileSync16(join17(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
6421
7127
|
} catch {
|
|
6422
7128
|
}
|
|
6423
7129
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -6450,7 +7156,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
6450
7156
|
})();
|
|
6451
7157
|
});
|
|
6452
7158
|
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-
|
|
7159
|
+
const { libraryRequest } = await import("./trace-library-EBH2GYDA.js");
|
|
6454
7160
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
6455
7161
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
6456
7162
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -6467,7 +7173,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
6467
7173
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
6468
7174
|
}));
|
|
6469
7175
|
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-
|
|
7176
|
+
const { libraryRequest } = await import("./trace-library-EBH2GYDA.js");
|
|
6471
7177
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
6472
7178
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
6473
7179
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|