@agentsky/sky 2.14.0 → 2.16.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-VJEE2ALN.js → chunk-5JHQH7WH.js} +1 -1
- package/dist/{chunk-FOJP33RS.js → chunk-MQECNHD2.js} +17 -5
- package/dist/{chunk-7OM2B5SE.js → chunk-RHRREQMF.js} +11 -0
- package/dist/index.js +1198 -238
- package/dist/products.js +1 -1
- package/dist/{src-EJU5U2GO.js → src-KLP774OA.js} +1 -1
- package/dist/{trace-YKEGRVJN.js → trace-IOARVWJ6.js} +2 -2
- package/dist/{trace-library-6BK5GJVD.js → trace-library-K3NE26GH.js} +3 -3
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -4,12 +4,12 @@ import {
|
|
|
4
4
|
api,
|
|
5
5
|
apiBytes,
|
|
6
6
|
apiForm,
|
|
7
|
-
|
|
7
|
+
downloadFileTo,
|
|
8
8
|
postEvents,
|
|
9
9
|
resolveSessionRef,
|
|
10
10
|
streamEvents,
|
|
11
11
|
uploadFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-MQECNHD2.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-5JHQH7WH.js";
|
|
24
24
|
import {
|
|
25
25
|
AGENT_TYPES,
|
|
26
26
|
MODEL_SUBSCRIPTION_PLAN_PHRASE,
|
|
@@ -40,18 +40,122 @@ import {
|
|
|
40
40
|
skillRefSchema,
|
|
41
41
|
splitMcpServers,
|
|
42
42
|
supersededBy
|
|
43
|
-
} from "./chunk-
|
|
43
|
+
} from "./chunk-RHRREQMF.js";
|
|
44
44
|
import "./chunk-MLKGABMK.js";
|
|
45
45
|
|
|
46
46
|
// src/index.ts
|
|
47
|
-
import { existsSync as existsSync10, readFileSync as
|
|
48
|
-
import { basename as
|
|
49
|
-
import { createInterface as
|
|
47
|
+
import { existsSync as existsSync10, readFileSync as readFileSync14, writeFileSync as writeFileSync7 } from "fs";
|
|
48
|
+
import { basename as basename8, resolve as resolve9 } from "path";
|
|
49
|
+
import { createInterface as createInterface6 } from "readline";
|
|
50
50
|
import { Command } from "commander";
|
|
51
51
|
|
|
52
52
|
// src/files.ts
|
|
53
|
-
import { readFileSync } from "fs";
|
|
54
|
-
import { basename, extname } from "path";
|
|
53
|
+
import { readFileSync as readFileSync2, statSync as statSync2 } from "fs";
|
|
54
|
+
import { basename as basename2, extname } from "path";
|
|
55
|
+
|
|
56
|
+
// src/resumable-upload.ts
|
|
57
|
+
import { createHash } from "crypto";
|
|
58
|
+
import { mkdirSync, openSync, readFileSync, readSync, closeSync, rmSync, statSync, writeFileSync } from "fs";
|
|
59
|
+
import { basename, dirname, join, resolve } from "path";
|
|
60
|
+
var SINGLE_REQUEST_MAX_BYTES = 32 * 1024 * 1024;
|
|
61
|
+
var MAX_ATTEMPTS = 8;
|
|
62
|
+
function statePath(path, size, mtimeMs) {
|
|
63
|
+
const key = createHash("sha256").update(`${resolve(path)}\0${size}\0${mtimeMs}`).digest("hex").slice(0, 32);
|
|
64
|
+
return join(dirname(configPath()), "uploads", `${key}.json`);
|
|
65
|
+
}
|
|
66
|
+
function loadSession(file, expiresInSeconds) {
|
|
67
|
+
try {
|
|
68
|
+
const saved = JSON.parse(readFileSync(file, "utf8"));
|
|
69
|
+
if (saved.expiresInSeconds !== expiresInSeconds) return null;
|
|
70
|
+
if (Date.parse(saved.expires_at) - Date.now() < 3600 * 1e3) return null;
|
|
71
|
+
return saved;
|
|
72
|
+
} catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function acknowledged(res) {
|
|
77
|
+
const range = res.headers.get("range")?.match(/^bytes=0-(\d+)$/);
|
|
78
|
+
return range ? Number(range[1]) + 1 : 0;
|
|
79
|
+
}
|
|
80
|
+
async function probe(session2) {
|
|
81
|
+
const res = await fetch(session2.upload_url, {
|
|
82
|
+
method: "PUT",
|
|
83
|
+
headers: { "user-agent": USER_AGENT, "content-range": `bytes */${session2.size_bytes}` }
|
|
84
|
+
});
|
|
85
|
+
if (res.status === 200 || res.status === 201) return "done";
|
|
86
|
+
if (res.status === 308) return acknowledged(res);
|
|
87
|
+
if (res.status === 404 || res.status === 410) throw new ApiError(res.status, "upload_expired", "the upload session is gone");
|
|
88
|
+
throw new ApiError(res.status, "upload_failed", `upload status check failed: ${res.status} ${res.statusText}`);
|
|
89
|
+
}
|
|
90
|
+
async function sendChunk(session2, fd, offset) {
|
|
91
|
+
const length = Math.min(session2.chunk_size, session2.size_bytes - offset);
|
|
92
|
+
const chunk = Buffer.alloc(length);
|
|
93
|
+
let read = 0;
|
|
94
|
+
while (read < length) read += readSync(fd, chunk, read, length - read, offset + read);
|
|
95
|
+
const res = await fetch(session2.upload_url, {
|
|
96
|
+
method: "PUT",
|
|
97
|
+
headers: {
|
|
98
|
+
"user-agent": USER_AGENT,
|
|
99
|
+
"content-range": `bytes ${offset}-${offset + length - 1}/${session2.size_bytes}`
|
|
100
|
+
},
|
|
101
|
+
body: chunk
|
|
102
|
+
});
|
|
103
|
+
if (res.status === 200 || res.status === 201) return "done";
|
|
104
|
+
if (res.status === 308) return acknowledged(res);
|
|
105
|
+
if (res.status >= 500 || res.status === 429) throw new Error(`transient ${res.status}`);
|
|
106
|
+
throw new ApiError(res.status, "upload_failed", `chunk upload failed: ${res.status} ${(await res.text()).slice(0, 200)}`);
|
|
107
|
+
}
|
|
108
|
+
async function transfer(session2, path, onProgress) {
|
|
109
|
+
const fd = openSync(path, "r");
|
|
110
|
+
try {
|
|
111
|
+
let failures = 0;
|
|
112
|
+
let state = await probe(session2);
|
|
113
|
+
while (state !== "done") {
|
|
114
|
+
onProgress(state, session2.size_bytes);
|
|
115
|
+
try {
|
|
116
|
+
state = await sendChunk(session2, fd, state);
|
|
117
|
+
failures = 0;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
if (error instanceof ApiError || ++failures >= MAX_ATTEMPTS) throw error;
|
|
120
|
+
await new Promise((done) => setTimeout(done, Math.min(3e4, 500 * 2 ** failures)));
|
|
121
|
+
state = await probe(session2);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
onProgress(session2.size_bytes, session2.size_bytes);
|
|
125
|
+
} finally {
|
|
126
|
+
closeSync(fd);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
async function uploadLargeFile(path, mediaType, expiresInSeconds, onProgress) {
|
|
130
|
+
const { size, mtimeMs } = statSync(path);
|
|
131
|
+
const stateFile = statePath(path, size, mtimeMs);
|
|
132
|
+
let session2 = loadSession(stateFile, expiresInSeconds);
|
|
133
|
+
if (session2) {
|
|
134
|
+
try {
|
|
135
|
+
await probe(session2);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
if (!(error instanceof ApiError) || error.status !== 404 && error.status !== 410) throw error;
|
|
138
|
+
session2 = null;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (!session2) {
|
|
142
|
+
session2 = await api("POST", "/files/uploads", {
|
|
143
|
+
filename: basename(path),
|
|
144
|
+
size_bytes: size,
|
|
145
|
+
mime_type: mediaType,
|
|
146
|
+
...expiresInSeconds ? { expires_in_seconds: expiresInSeconds } : {}
|
|
147
|
+
});
|
|
148
|
+
mkdirSync(dirname(stateFile), { recursive: true });
|
|
149
|
+
const saved = { ...session2, path: resolve(path), mtimeMs, expiresInSeconds };
|
|
150
|
+
writeFileSync(stateFile, JSON.stringify(saved), { mode: 384 });
|
|
151
|
+
}
|
|
152
|
+
await transfer(session2, path, onProgress);
|
|
153
|
+
const file = await api("POST", `/files/uploads/${encodeURIComponent(session2.id)}/complete`);
|
|
154
|
+
rmSync(stateFile, { force: true });
|
|
155
|
+
return file;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// src/files.ts
|
|
55
159
|
var MEDIA_TYPES = {
|
|
56
160
|
".png": "image/png",
|
|
57
161
|
".jpg": "image/jpeg",
|
|
@@ -71,20 +175,24 @@ var MEDIA_TYPES = {
|
|
|
71
175
|
function mediaTypeFor(path) {
|
|
72
176
|
return MEDIA_TYPES[extname(path).toLowerCase()] ?? "application/octet-stream";
|
|
73
177
|
}
|
|
74
|
-
async function uploadLocalFile(path, expiresInSeconds) {
|
|
75
|
-
|
|
76
|
-
|
|
178
|
+
async function uploadLocalFile(path, expiresInSeconds, onProgress = () => {
|
|
179
|
+
}) {
|
|
180
|
+
if (statSync2(path).size > SINGLE_REQUEST_MAX_BYTES) {
|
|
181
|
+
return uploadLargeFile(path, mediaTypeFor(path), expiresInSeconds, onProgress);
|
|
182
|
+
}
|
|
183
|
+
const bytes = readFileSync2(path);
|
|
184
|
+
return uploadFile(new Uint8Array(bytes), basename2(path), mediaTypeFor(path), expiresInSeconds);
|
|
77
185
|
}
|
|
78
186
|
function fileBlock(file) {
|
|
79
187
|
const source = { type: "file", file_id: file.id };
|
|
80
188
|
return file.mime_type.startsWith("image/") ? { type: "image", source } : { type: "document", source, title: file.filename };
|
|
81
189
|
}
|
|
82
|
-
function eventsFor(text, files2, system) {
|
|
190
|
+
function eventsFor(text, files2, system, interactionMode2) {
|
|
83
191
|
const content = [];
|
|
84
192
|
if (text.trim()) content.push({ type: "text", text });
|
|
85
193
|
for (const file of files2) content.push(fileBlock(file));
|
|
86
194
|
if (content.length === 0) throw new Error("nothing to send: pass text, --file or --image");
|
|
87
|
-
const events = [{ type: "user.message", content }];
|
|
195
|
+
const events = [{ type: "user.message", content, ...interactionMode2 ? { interaction_mode: interactionMode2 } : {} }];
|
|
88
196
|
if (system?.trim()) events.push({ type: "system.message", content: [{ type: "text", text: system }] });
|
|
89
197
|
return events;
|
|
90
198
|
}
|
|
@@ -286,8 +394,8 @@ function renderContextPage(page) {
|
|
|
286
394
|
}
|
|
287
395
|
|
|
288
396
|
// src/agent-file.ts
|
|
289
|
-
import { existsSync, readFileSync as
|
|
290
|
-
import { dirname, resolve } from "path";
|
|
397
|
+
import { existsSync, readFileSync as readFileSync3, writeFileSync as writeFileSync2 } from "fs";
|
|
398
|
+
import { dirname as dirname2, resolve as resolve2 } from "path";
|
|
291
399
|
import { parse as parseToml, stringify as stringifyToml } from "smol-toml";
|
|
292
400
|
var fileInstructionSchema = external_exports.object({
|
|
293
401
|
name: external_exports.string(),
|
|
@@ -319,7 +427,7 @@ var agentFileSchema = external_exports.object({
|
|
|
319
427
|
memory_mb: external_exports.number().int().optional()
|
|
320
428
|
});
|
|
321
429
|
function readAgentFile(path) {
|
|
322
|
-
const raw =
|
|
430
|
+
const raw = readFileSync3(path, "utf8");
|
|
323
431
|
const parsed = agentFileSchema.safeParse(parseToml(raw));
|
|
324
432
|
if (!parsed.success) {
|
|
325
433
|
const issue = parsed.error.issues[0];
|
|
@@ -331,11 +439,11 @@ function readAgentFile(path) {
|
|
|
331
439
|
`${path}: reasoning_effort '${file.reasoning_effort}' is not available for ${file.type} \u2014 ${reasoningEffortDocLine()}`
|
|
332
440
|
);
|
|
333
441
|
}
|
|
334
|
-
const base =
|
|
442
|
+
const base = dirname2(resolve2(path));
|
|
335
443
|
const inline = (ref, inlineContent) => {
|
|
336
444
|
if (inlineContent !== void 0) return inlineContent;
|
|
337
445
|
if (!ref) return void 0;
|
|
338
|
-
return
|
|
446
|
+
return readFileSync3(resolve2(base, ref), "utf8");
|
|
339
447
|
};
|
|
340
448
|
const spec = {
|
|
341
449
|
name: file.name,
|
|
@@ -361,14 +469,14 @@ function readAgentFile(path) {
|
|
|
361
469
|
return { file, spec };
|
|
362
470
|
}
|
|
363
471
|
function stampId(path, id) {
|
|
364
|
-
const doc = parseToml(
|
|
472
|
+
const doc = parseToml(readFileSync3(path, "utf8"));
|
|
365
473
|
doc.id = id;
|
|
366
474
|
delete doc.slug;
|
|
367
|
-
|
|
475
|
+
writeFileSync2(path, stringifyToml(doc));
|
|
368
476
|
}
|
|
369
477
|
function scaffoldAgentFile(path, name) {
|
|
370
478
|
if (existsSync(path)) throw new Error(`${path} already exists`);
|
|
371
|
-
|
|
479
|
+
writeFileSync2(
|
|
372
480
|
path,
|
|
373
481
|
`# agent.toml \u2014 everything the create wizard can set. Only \`name\` is required.
|
|
374
482
|
name = "${name}"
|
|
@@ -454,18 +562,18 @@ function renderCatalogAgent(agent2) {
|
|
|
454
562
|
|
|
455
563
|
// src/clone.ts
|
|
456
564
|
import { spawn } from "child_process";
|
|
457
|
-
import { existsSync as existsSync2, readFileSync as
|
|
565
|
+
import { existsSync as existsSync2, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
458
566
|
import { homedir } from "os";
|
|
459
|
-
import { basename as
|
|
567
|
+
import { basename as basename3, join as join2 } from "path";
|
|
460
568
|
import { createInterface } from "readline";
|
|
461
569
|
var PROMPT_CAP = 1e5;
|
|
462
570
|
var HARNESSES = ["claude_code", "codex", "openclaw"];
|
|
463
571
|
function claudeConfigRoot() {
|
|
464
|
-
return process.env.CLAUDE_CONFIG_DIR ??
|
|
572
|
+
return process.env.CLAUDE_CONFIG_DIR ?? join2(homedir(), ".claude");
|
|
465
573
|
}
|
|
466
574
|
function readIfExists(path) {
|
|
467
575
|
try {
|
|
468
|
-
return
|
|
576
|
+
return readFileSync4(path, "utf8");
|
|
469
577
|
} catch {
|
|
470
578
|
return void 0;
|
|
471
579
|
}
|
|
@@ -481,9 +589,9 @@ function readJson(path) {
|
|
|
481
589
|
}
|
|
482
590
|
function claudeModel(configRoot) {
|
|
483
591
|
const candidates = [
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
592
|
+
join2(process.cwd(), ".claude", "settings.local.json"),
|
|
593
|
+
join2(process.cwd(), ".claude", "settings.json"),
|
|
594
|
+
join2(configRoot, "settings.json")
|
|
487
595
|
];
|
|
488
596
|
for (const path of candidates) {
|
|
489
597
|
const model = readJson(path)?.model;
|
|
@@ -493,11 +601,11 @@ function claudeModel(configRoot) {
|
|
|
493
601
|
}
|
|
494
602
|
function claudeMcpServers() {
|
|
495
603
|
const merged = {};
|
|
496
|
-
const userConfig = readJson(
|
|
604
|
+
const userConfig = readJson(join2(homedir(), ".claude.json"));
|
|
497
605
|
Object.assign(merged, userConfig?.mcpServers ?? {});
|
|
498
606
|
Object.assign(merged, userConfig?.projects?.[process.cwd()]?.mcpServers ?? {});
|
|
499
607
|
const projectConfig = readJson(
|
|
500
|
-
|
|
608
|
+
join2(process.cwd(), ".mcp.json")
|
|
501
609
|
);
|
|
502
610
|
Object.assign(merged, projectConfig?.mcpServers ?? {});
|
|
503
611
|
return merged;
|
|
@@ -514,13 +622,13 @@ function readClaudeCode() {
|
|
|
514
622
|
const configRoot = claudeConfigRoot();
|
|
515
623
|
const parts = [];
|
|
516
624
|
const sources = [];
|
|
517
|
-
const global = readIfExists(
|
|
625
|
+
const global = readIfExists(join2(configRoot, "CLAUDE.md"));
|
|
518
626
|
if (global?.trim()) {
|
|
519
627
|
parts.push({ label: "Global CLAUDE.md", content: global });
|
|
520
628
|
sources.push("global CLAUDE.md");
|
|
521
629
|
}
|
|
522
630
|
for (const name of ["CLAUDE.md", "AGENTS.md"]) {
|
|
523
|
-
const project = readIfExists(
|
|
631
|
+
const project = readIfExists(join2(process.cwd(), name));
|
|
524
632
|
if (project?.trim()) {
|
|
525
633
|
parts.push({ label: `Project ${name}`, content: project });
|
|
526
634
|
sources.push(`project ${name}`);
|
|
@@ -561,8 +669,8 @@ function detectHarness(explicit) {
|
|
|
561
669
|
return explicit;
|
|
562
670
|
}
|
|
563
671
|
if (existsSync2(claudeConfigRoot())) return "claude_code";
|
|
564
|
-
if (existsSync2(
|
|
565
|
-
if (existsSync2(
|
|
672
|
+
if (existsSync2(join2(homedir(), ".codex"))) return "codex";
|
|
673
|
+
if (existsSync2(join2(homedir(), ".openclaw"))) return "openclaw";
|
|
566
674
|
throw new Error(
|
|
567
675
|
"no local harness found (~/.claude, ~/.codex, ~/.openclaw) \u2014 pass --harness to override"
|
|
568
676
|
);
|
|
@@ -574,14 +682,14 @@ function readLocal(harness2) {
|
|
|
574
682
|
);
|
|
575
683
|
}
|
|
576
684
|
function linkPath(configRoot) {
|
|
577
|
-
return
|
|
685
|
+
return join2(configRoot, "agentsky-link.json");
|
|
578
686
|
}
|
|
579
687
|
function readLink(configRoot) {
|
|
580
688
|
const link = readJson(linkPath(configRoot));
|
|
581
689
|
return link?.agentId ? link : void 0;
|
|
582
690
|
}
|
|
583
691
|
function writeLink(configRoot, link) {
|
|
584
|
-
|
|
692
|
+
writeFileSync3(linkPath(configRoot), `${JSON.stringify(link, null, 2)}
|
|
585
693
|
`);
|
|
586
694
|
}
|
|
587
695
|
var DEFAULT_VAULT_NAME = "default";
|
|
@@ -596,10 +704,10 @@ async function ensureDefaultVaultId() {
|
|
|
596
704
|
const existing = await fetchDefaultVaultId();
|
|
597
705
|
if (existing) return existing;
|
|
598
706
|
try {
|
|
599
|
-
const { vault } = await api("POST", "/vaults", {
|
|
707
|
+
const { vault: vault2 } = await api("POST", "/vaults", {
|
|
600
708
|
display_name: DEFAULT_VAULT_NAME
|
|
601
709
|
});
|
|
602
|
-
return
|
|
710
|
+
return vault2.id;
|
|
603
711
|
} catch (error) {
|
|
604
712
|
if (error instanceof ApiError && error.status === 409) {
|
|
605
713
|
const raced = await fetchDefaultVaultId();
|
|
@@ -683,7 +791,7 @@ async function createClone(local, opts) {
|
|
|
683
791
|
`model ${local.model ?? "(harness default)"}`,
|
|
684
792
|
`${keptNames.length} MCP server${keptNames.length === 1 ? "" : "s"}${keptNames.length ? ` (${keptNames.join(", ")})` : ""}`
|
|
685
793
|
].join(" \xB7 ");
|
|
686
|
-
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${
|
|
794
|
+
console.log(`Detected ${local.harness} at ${local.configRoot} (project ${basename3(process.cwd())})`);
|
|
687
795
|
console.log(`Will clone: ${summary}`);
|
|
688
796
|
if (local.truncated) console.log(" note: instructions exceeded 100k chars and were truncated");
|
|
689
797
|
if (local.droppedServers.length) {
|
|
@@ -820,22 +928,22 @@ async function clone(opts) {
|
|
|
820
928
|
}
|
|
821
929
|
|
|
822
930
|
// src/import-local.ts
|
|
823
|
-
import { closeSync, existsSync as existsSync5, openSync, readdirSync as readdirSync2, readFileSync as
|
|
931
|
+
import { closeSync as closeSync2, existsSync as existsSync5, openSync as openSync2, readdirSync as readdirSync2, readFileSync as readFileSync7, readSync as readSync2, statSync as statSync5 } from "fs";
|
|
824
932
|
import { homedir as homedir4 } from "os";
|
|
825
|
-
import { join as
|
|
826
|
-
import { createHash } from "crypto";
|
|
933
|
+
import { join as join5 } from "path";
|
|
934
|
+
import { createHash as createHash2 } from "crypto";
|
|
827
935
|
|
|
828
936
|
// src/import-files.ts
|
|
829
937
|
import { isUtf8 } from "buffer";
|
|
830
|
-
import { existsSync as existsSync4, readFileSync as
|
|
938
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6, statSync as statSync4 } from "fs";
|
|
831
939
|
import { homedir as homedir3 } from "os";
|
|
832
|
-
import { dirname as
|
|
940
|
+
import { dirname as dirname4, extname as extname2, isAbsolute, join as join4, relative as relative2, resolve as resolve4, sep as sep2, basename as basename5 } from "path";
|
|
833
941
|
|
|
834
942
|
// src/import-workspace.ts
|
|
835
943
|
import { spawnSync } from "child_process";
|
|
836
|
-
import { existsSync as existsSync3, readdirSync, readFileSync as
|
|
944
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync5, realpathSync, statSync as statSync3 } from "fs";
|
|
837
945
|
import { homedir as homedir2 } from "os";
|
|
838
|
-
import { basename as
|
|
946
|
+
import { basename as basename4, dirname as dirname3, join as join3, relative, sep } from "path";
|
|
839
947
|
import { parse as parseToml2 } from "smol-toml";
|
|
840
948
|
var MAX_TREE = 5e3;
|
|
841
949
|
var SKILL_LINE = /Base directory for this skill: (\/[^\s"'\\<>]+)/g;
|
|
@@ -845,26 +953,26 @@ function git(cwd, ...args) {
|
|
|
845
953
|
}
|
|
846
954
|
var isFile = (path) => {
|
|
847
955
|
try {
|
|
848
|
-
return
|
|
956
|
+
return statSync3(path).isFile();
|
|
849
957
|
} catch {
|
|
850
958
|
return false;
|
|
851
959
|
}
|
|
852
960
|
};
|
|
853
961
|
var isDir = (path) => {
|
|
854
962
|
try {
|
|
855
|
-
return
|
|
963
|
+
return statSync3(path).isDirectory();
|
|
856
964
|
} catch {
|
|
857
965
|
return false;
|
|
858
966
|
}
|
|
859
967
|
};
|
|
860
968
|
function sharedInstructionPaths(home = homedir2()) {
|
|
861
|
-
const rules =
|
|
969
|
+
const rules = join3(home, ".agents", "rules");
|
|
862
970
|
let ruleFiles = [];
|
|
863
971
|
try {
|
|
864
|
-
ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) =>
|
|
972
|
+
ruleFiles = readdirSync(rules).filter((name) => name.endsWith(".md")).sort().map((name) => join3(rules, name));
|
|
865
973
|
} catch {
|
|
866
974
|
}
|
|
867
|
-
return [
|
|
975
|
+
return [join3(home, "AGENTS.md"), join3(home, "MEMORY.md"), ...ruleFiles].filter(isFile);
|
|
868
976
|
}
|
|
869
977
|
function strings(value, out, depth = 0) {
|
|
870
978
|
if (depth > 12) return;
|
|
@@ -873,18 +981,18 @@ function strings(value, out, depth = 0) {
|
|
|
873
981
|
else if (value && typeof value === "object") for (const item of Object.values(value)) strings(item, out, depth + 1);
|
|
874
982
|
}
|
|
875
983
|
function skillRoots(cwd, home) {
|
|
876
|
-
const claude = process.env.CLAUDE_CONFIG_DIR ??
|
|
877
|
-
const codex = process.env.CODEX_HOME ??
|
|
984
|
+
const claude = process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude");
|
|
985
|
+
const codex = process.env.CODEX_HOME ?? join3(home, ".codex");
|
|
878
986
|
return [
|
|
879
|
-
...cwd ? [
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
987
|
+
...cwd ? [join3(cwd, ".claude", "skills"), join3(cwd, ".agents", "skills")] : [],
|
|
988
|
+
join3(home, ".agents", "skills"),
|
|
989
|
+
join3(claude, "skills"),
|
|
990
|
+
join3(codex, "skills"),
|
|
991
|
+
join3(home, ".hermes", "skills")
|
|
884
992
|
];
|
|
885
993
|
}
|
|
886
994
|
function pluginSkill(plugin, skill, home) {
|
|
887
|
-
const base =
|
|
995
|
+
const base = join3(process.env.CLAUDE_CONFIG_DIR ?? join3(home, ".claude"), "plugins");
|
|
888
996
|
const walk = (dir, depth) => {
|
|
889
997
|
if (depth > 7) return null;
|
|
890
998
|
let entries;
|
|
@@ -895,8 +1003,8 @@ function pluginSkill(plugin, skill, home) {
|
|
|
895
1003
|
}
|
|
896
1004
|
for (const entry2 of entries) {
|
|
897
1005
|
if (!entry2.isDirectory() || entry2.name === "node_modules" || entry2.name === ".git") continue;
|
|
898
|
-
const path =
|
|
899
|
-
if (entry2.name === skill &&
|
|
1006
|
+
const path = join3(dir, entry2.name);
|
|
1007
|
+
if (entry2.name === skill && basename4(dir) === "skills" && path.includes(`${sep}${plugin}${sep}`) && isFile(join3(path, "SKILL.md"))) return path;
|
|
900
1008
|
const found = walk(path, depth + 1);
|
|
901
1009
|
if (found) return found;
|
|
902
1010
|
}
|
|
@@ -908,16 +1016,16 @@ function findSkill(name, cwd, home) {
|
|
|
908
1016
|
const [scope, skill] = name.includes(":") ? name.split(":", 2) : [null, name];
|
|
909
1017
|
if (scope && skill) return pluginSkill(scope, skill, home);
|
|
910
1018
|
for (const root of skillRoots(cwd, home)) {
|
|
911
|
-
const direct =
|
|
912
|
-
if (isFile(
|
|
1019
|
+
const direct = join3(root, name);
|
|
1020
|
+
if (isFile(join3(direct, "SKILL.md"))) return realpathSync(direct);
|
|
913
1021
|
}
|
|
914
1022
|
return null;
|
|
915
1023
|
}
|
|
916
1024
|
function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
917
1025
|
const dirs = /* @__PURE__ */ new Map();
|
|
918
1026
|
const missing = /* @__PURE__ */ new Set();
|
|
919
|
-
const add = (dir, name =
|
|
920
|
-
if (!isFile(
|
|
1027
|
+
const add = (dir, name = basename4(dir)) => {
|
|
1028
|
+
if (!isFile(join3(dir, "SKILL.md"))) return false;
|
|
921
1029
|
const real = realpathSync(dir);
|
|
922
1030
|
if (![...dirs.values()].includes(real)) dirs.set(name, real);
|
|
923
1031
|
return true;
|
|
@@ -937,13 +1045,13 @@ function loadedSkills(records, touched, cwd, home = homedir2()) {
|
|
|
937
1045
|
if (!dir || !add(dir, name)) missing.add(name);
|
|
938
1046
|
}
|
|
939
1047
|
}
|
|
940
|
-
for (const path of touched) if (
|
|
1048
|
+
for (const path of touched) if (basename4(path) === "SKILL.md") add(dirname3(path));
|
|
941
1049
|
for (const name of dirs.keys()) missing.delete(name);
|
|
942
1050
|
return { found: [...dirs].map(([name, dir]) => ({ name: name.replace(/[^A-Za-z0-9._:-]/g, "-"), dir })), missing: [...missing] };
|
|
943
1051
|
}
|
|
944
1052
|
function readJson2(path) {
|
|
945
1053
|
try {
|
|
946
|
-
const value = JSON.parse(
|
|
1054
|
+
const value = JSON.parse(readFileSync5(path, "utf8"));
|
|
947
1055
|
return value && typeof value === "object" ? value : null;
|
|
948
1056
|
} catch {
|
|
949
1057
|
return null;
|
|
@@ -988,16 +1096,16 @@ function mcpServers(harness2, cwd, home = homedir2()) {
|
|
|
988
1096
|
}
|
|
989
1097
|
};
|
|
990
1098
|
if (harness2 === "claude_code") {
|
|
991
|
-
const config = readJson2(
|
|
1099
|
+
const config = readJson2(join3(process.env.CLAUDE_CONFIG_DIR ? dirname3(process.env.CLAUDE_CONFIG_DIR) : home, ".claude.json"));
|
|
992
1100
|
const projects = config?.projects && typeof config.projects === "object" ? config.projects : {};
|
|
993
1101
|
const root = cwd ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || cwd : "";
|
|
994
1102
|
if (cwd) push(projects[cwd]?.mcpServers ?? projects[root]?.mcpServers, "local");
|
|
995
|
-
if (root) push(readJson2(
|
|
1103
|
+
if (root) push(readJson2(join3(root, ".mcp.json"))?.mcpServers, "project");
|
|
996
1104
|
push(config?.mcpServers, "user");
|
|
997
1105
|
}
|
|
998
1106
|
if (harness2 === "codex") {
|
|
999
1107
|
try {
|
|
1000
|
-
const config = parseToml2(
|
|
1108
|
+
const config = parseToml2(readFileSync5(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "config.toml"), "utf8"));
|
|
1001
1109
|
push(config.mcp_servers, "user");
|
|
1002
1110
|
} catch {
|
|
1003
1111
|
}
|
|
@@ -1007,7 +1115,7 @@ function mcpServers(harness2, cwd, home = homedir2()) {
|
|
|
1007
1115
|
function remoteWithoutCredentials(remote) {
|
|
1008
1116
|
return remote.trim().replace(/\/\/[^@/]+@/, "//").replace(/^[^@/]+@([^:]+):/, "$1:");
|
|
1009
1117
|
}
|
|
1010
|
-
function workspaceState(cwd, harness2, skills2, home = homedir2()) {
|
|
1118
|
+
function workspaceState(cwd, harness2, skills2, home = homedir2(), title = null) {
|
|
1011
1119
|
const present = !!cwd && isDir(cwd);
|
|
1012
1120
|
const root = present ? git(cwd, "rev-parse", "--show-toplevel")?.trim() || null : null;
|
|
1013
1121
|
let gitState = null;
|
|
@@ -1033,8 +1141,9 @@ function workspaceState(cwd, harness2, skills2, home = homedir2()) {
|
|
|
1033
1141
|
format: "agentsky.import-workspace",
|
|
1034
1142
|
format_version: 1,
|
|
1035
1143
|
harness: harness2,
|
|
1144
|
+
title: title?.trim().slice(0, 120) || null,
|
|
1036
1145
|
cwd: cwd ? cwd.replace(home, "~") : null,
|
|
1037
|
-
project: cwd ?
|
|
1146
|
+
project: cwd ? basename4(root ?? cwd) : null,
|
|
1038
1147
|
git: gitState,
|
|
1039
1148
|
tree: tree.slice(0, MAX_TREE),
|
|
1040
1149
|
tree_truncated: tree.length > MAX_TREE,
|
|
@@ -1056,7 +1165,7 @@ function skillFiles(skill) {
|
|
|
1056
1165
|
}
|
|
1057
1166
|
for (const entry2 of entries) {
|
|
1058
1167
|
if (entry2.name === "node_modules" || entry2.name === ".git" || entry2.name === "__pycache__" || entry2.name === ".venv") continue;
|
|
1059
|
-
const path =
|
|
1168
|
+
const path = join3(dir, entry2.name);
|
|
1060
1169
|
if (entry2.isDirectory()) walk(path, depth + 1);
|
|
1061
1170
|
else if (entry2.isFile()) out.push({ path, name: `skills/${skill.name}/${relative(skill.dir, path).split(sep).join("/")}` });
|
|
1062
1171
|
}
|
|
@@ -1064,6 +1173,21 @@ function skillFiles(skill) {
|
|
|
1064
1173
|
if (existsSync3(skill.dir)) walk(skill.dir, 0);
|
|
1065
1174
|
return out;
|
|
1066
1175
|
}
|
|
1176
|
+
function codexThreadName(sessionId, home = homedir2()) {
|
|
1177
|
+
let name = null;
|
|
1178
|
+
try {
|
|
1179
|
+
for (const line of readFileSync5(join3(process.env.CODEX_HOME ?? join3(home, ".codex"), "session_index.jsonl"), "utf8").split("\n")) {
|
|
1180
|
+
if (!line.includes(sessionId)) continue;
|
|
1181
|
+
try {
|
|
1182
|
+
const entry2 = JSON.parse(line);
|
|
1183
|
+
if (entry2.id === sessionId && typeof entry2.thread_name === "string" && entry2.thread_name.trim()) name = entry2.thread_name.trim();
|
|
1184
|
+
} catch {
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
} catch {
|
|
1188
|
+
}
|
|
1189
|
+
return name;
|
|
1190
|
+
}
|
|
1067
1191
|
|
|
1068
1192
|
// src/import-files.ts
|
|
1069
1193
|
var MAX_ATTACHED_FILE_BYTES = 10 * 1024 * 1024;
|
|
@@ -1114,7 +1238,7 @@ function classify(path) {
|
|
|
1114
1238
|
if (DOCUMENTS[ext]) return { kind: "document", mediaType: DOCUMENTS[ext] };
|
|
1115
1239
|
if (IMAGES[ext]) return { kind: "image", mediaType: IMAGES[ext] };
|
|
1116
1240
|
if (TEXT[ext]) return { kind: "text", mediaType: TEXT[ext] };
|
|
1117
|
-
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(
|
|
1241
|
+
if (PLAIN_TEXT.has(ext) || PLAIN_NAMES.has(basename5(path))) return { kind: "text", mediaType: "text/plain" };
|
|
1118
1242
|
return null;
|
|
1119
1243
|
}
|
|
1120
1244
|
var SHELL_READ = /(?:^|[\s;&|(`"'])(?:cat|head|tail|nl|less|more|wc|bat|sed\s+-n\s+(?:'[^']*'|"[^"]*"|\S+))(?:\s+-[\w-]+(?:\s+\d+)?)*\s+((?:~|\.{0,2}\/)?[\w@%+./~-]*[\w-]\.[\w]{1,10}|\/[\w@%+./-]+)/g;
|
|
@@ -1133,8 +1257,8 @@ function touchedPaths(records, cwd, home = homedir3()) {
|
|
|
1133
1257
|
const add = (raw, base) => {
|
|
1134
1258
|
if (!raw || raw.length > 1e3) return;
|
|
1135
1259
|
const path = raw.replace(/^~(?=\/)/, home);
|
|
1136
|
-
if (isAbsolute(path)) out.add(
|
|
1137
|
-
else if (base) out.add(
|
|
1260
|
+
if (isAbsolute(path)) out.add(resolve4(path));
|
|
1261
|
+
else if (base) out.add(resolve4(base, path));
|
|
1138
1262
|
};
|
|
1139
1263
|
const call = (name, input) => {
|
|
1140
1264
|
const i = obj(input);
|
|
@@ -1171,33 +1295,33 @@ function touchedPaths(records, cwd, home = homedir3()) {
|
|
|
1171
1295
|
return [...out];
|
|
1172
1296
|
}
|
|
1173
1297
|
function gitRoot(dir) {
|
|
1174
|
-
for (let d = dir; ; d =
|
|
1175
|
-
if (existsSync4(
|
|
1176
|
-
if (d ===
|
|
1298
|
+
for (let d = dir; ; d = dirname4(d)) {
|
|
1299
|
+
if (existsSync4(join4(d, ".git"))) return d;
|
|
1300
|
+
if (d === dirname4(d)) return null;
|
|
1177
1301
|
}
|
|
1178
1302
|
}
|
|
1179
1303
|
function instructionPaths(cwd, harness2, home = homedir3()) {
|
|
1180
1304
|
const out = [];
|
|
1181
1305
|
if (cwd && existsSync4(cwd)) {
|
|
1182
1306
|
const root = gitRoot(cwd) ?? cwd;
|
|
1183
|
-
for (let d =
|
|
1184
|
-
for (const name of INSTRUCTION_FILES) out.push(
|
|
1185
|
-
if (d === root || d ===
|
|
1307
|
+
for (let d = resolve4(cwd); ; d = dirname4(d)) {
|
|
1308
|
+
for (const name of INSTRUCTION_FILES) out.push(join4(d, name));
|
|
1309
|
+
if (d === root || d === dirname4(d)) break;
|
|
1186
1310
|
}
|
|
1187
1311
|
}
|
|
1188
|
-
if (harness2 === "claude_code") out.push(
|
|
1189
|
-
if (harness2 === "codex") out.push(
|
|
1312
|
+
if (harness2 === "claude_code") out.push(join4(process.env.CLAUDE_CONFIG_DIR ?? join4(home, ".claude"), "CLAUDE.md"));
|
|
1313
|
+
if (harness2 === "codex") out.push(join4(process.env.CODEX_HOME ?? join4(home, ".codex"), "AGENTS.md"));
|
|
1190
1314
|
out.push(...sharedInstructionPaths(home));
|
|
1191
1315
|
return out.filter((path) => {
|
|
1192
1316
|
try {
|
|
1193
|
-
return
|
|
1317
|
+
return statSync4(path).isFile();
|
|
1194
1318
|
} catch {
|
|
1195
1319
|
return false;
|
|
1196
1320
|
}
|
|
1197
1321
|
});
|
|
1198
1322
|
}
|
|
1199
1323
|
function resourceName(path, cwd, home = homedir3()) {
|
|
1200
|
-
const inside = cwd ? relative2(
|
|
1324
|
+
const inside = cwd ? relative2(resolve4(cwd), path) : "";
|
|
1201
1325
|
if (cwd && inside && !inside.startsWith("..") && !isAbsolute(inside)) return `workspace/${inside.split(sep2).join("/")}`;
|
|
1202
1326
|
const underHome = relative2(home, path);
|
|
1203
1327
|
const shown = !underHome.startsWith("..") && !isAbsolute(underHome) ? `~/${underHome}` : path.replace(/^\/+/, "");
|
|
@@ -1209,7 +1333,7 @@ function resourceLine(name, mediaType, data) {
|
|
|
1209
1333
|
}
|
|
1210
1334
|
function collectSessionFiles(records, opts) {
|
|
1211
1335
|
const home = opts.home ?? homedir3();
|
|
1212
|
-
const exclude = new Set((opts.exclude ?? []).map((path) =>
|
|
1336
|
+
const exclude = new Set((opts.exclude ?? []).map((path) => resolve4(path)));
|
|
1213
1337
|
const leftOut = [];
|
|
1214
1338
|
const candidates = [];
|
|
1215
1339
|
const seen = /* @__PURE__ */ new Set();
|
|
@@ -1224,7 +1348,7 @@ function collectSessionFiles(records, opts) {
|
|
|
1224
1348
|
}
|
|
1225
1349
|
let size;
|
|
1226
1350
|
try {
|
|
1227
|
-
const stat =
|
|
1351
|
+
const stat = statSync4(path);
|
|
1228
1352
|
if (!stat.isFile()) return;
|
|
1229
1353
|
size = stat.size;
|
|
1230
1354
|
} catch {
|
|
@@ -1248,8 +1372,8 @@ function collectSessionFiles(records, opts) {
|
|
|
1248
1372
|
const lines = [];
|
|
1249
1373
|
const attached = [];
|
|
1250
1374
|
let bytes = 0;
|
|
1251
|
-
const state = workspaceState(opts.cwd, opts.harness, skills2, home);
|
|
1252
|
-
const described = !!opts.cwd || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
1375
|
+
const state = workspaceState(opts.cwd, opts.harness, skills2, home, opts.title ?? null);
|
|
1376
|
+
const described = !!opts.cwd || !!state.manifest.title || state.manifest.skills.length > 0 || state.manifest.mcp_servers.length > 0;
|
|
1253
1377
|
const generated = [
|
|
1254
1378
|
...described ? [{ name: "agentsky/workspace.json", mediaType: "application/json", data: Buffer.from(JSON.stringify(state.manifest, null, 2)) }] : [],
|
|
1255
1379
|
...state.patch ? [{ name: "agentsky/uncommitted.patch", mediaType: "text/x-diff", data: Buffer.from(state.patch) }] : []
|
|
@@ -1274,7 +1398,7 @@ function collectSessionFiles(records, opts) {
|
|
|
1274
1398
|
for (const file of candidates) {
|
|
1275
1399
|
let data;
|
|
1276
1400
|
try {
|
|
1277
|
-
data =
|
|
1401
|
+
data = readFileSync6(file.path);
|
|
1278
1402
|
} catch {
|
|
1279
1403
|
leftOut.push({ path: file.path, reason: "missing" });
|
|
1280
1404
|
continue;
|
|
@@ -1320,12 +1444,12 @@ function decodeWorkspace(encoded) {
|
|
|
1320
1444
|
}
|
|
1321
1445
|
}
|
|
1322
1446
|
function readPrefix(filePath) {
|
|
1323
|
-
const fd =
|
|
1447
|
+
const fd = openSync2(filePath, "r");
|
|
1324
1448
|
try {
|
|
1325
1449
|
const bytes = Buffer.alloc(256 * 1024);
|
|
1326
|
-
return bytes.subarray(0,
|
|
1450
|
+
return bytes.subarray(0, readSync2(fd, bytes, 0, bytes.length, 0)).toString("utf8");
|
|
1327
1451
|
} finally {
|
|
1328
|
-
|
|
1452
|
+
closeSync2(fd);
|
|
1329
1453
|
}
|
|
1330
1454
|
}
|
|
1331
1455
|
function sniffTitle(filePath) {
|
|
@@ -1380,13 +1504,13 @@ function sniffTitle(filePath) {
|
|
|
1380
1504
|
}
|
|
1381
1505
|
}
|
|
1382
1506
|
function listClaudeCodeSessions(limit) {
|
|
1383
|
-
const root = process.env.CLAUDE_CONFIG_DIR ??
|
|
1384
|
-
const projectsDir =
|
|
1507
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join5(homedir4(), ".claude");
|
|
1508
|
+
const projectsDir = join5(root, "projects");
|
|
1385
1509
|
if (!existsSync5(projectsDir)) return [];
|
|
1386
1510
|
const sessions = [];
|
|
1387
1511
|
try {
|
|
1388
1512
|
for (const encoded of readdirSync2(projectsDir)) {
|
|
1389
|
-
const projectDir =
|
|
1513
|
+
const projectDir = join5(projectsDir, encoded);
|
|
1390
1514
|
let entries;
|
|
1391
1515
|
try {
|
|
1392
1516
|
entries = readdirSync2(projectDir);
|
|
@@ -1397,22 +1521,22 @@ function listClaudeCodeSessions(limit) {
|
|
|
1397
1521
|
for (const entry2 of entries) {
|
|
1398
1522
|
if (!entry2.endsWith(".jsonl")) continue;
|
|
1399
1523
|
const sessionId = entry2.slice(0, -6);
|
|
1400
|
-
const mainFile =
|
|
1524
|
+
const mainFile = join5(projectDir, entry2);
|
|
1401
1525
|
let stat;
|
|
1402
1526
|
try {
|
|
1403
|
-
stat =
|
|
1527
|
+
stat = statSync5(mainFile);
|
|
1404
1528
|
} catch {
|
|
1405
1529
|
continue;
|
|
1406
1530
|
}
|
|
1407
1531
|
if (!stat.isFile()) continue;
|
|
1408
1532
|
let discoveryError;
|
|
1409
1533
|
const subagentFiles = [];
|
|
1410
|
-
const subagentDir =
|
|
1534
|
+
const subagentDir = join5(projectDir, sessionId, "subagents");
|
|
1411
1535
|
if (existsSync5(subagentDir)) {
|
|
1412
1536
|
try {
|
|
1413
1537
|
const subFiles = readdirSync2(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
1414
1538
|
for (const f of subFiles) {
|
|
1415
|
-
subagentFiles.push(
|
|
1539
|
+
subagentFiles.push(join5(subagentDir, f));
|
|
1416
1540
|
}
|
|
1417
1541
|
} catch {
|
|
1418
1542
|
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
@@ -1434,7 +1558,7 @@ function listClaudeCodeSessions(limit) {
|
|
|
1434
1558
|
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
1435
1559
|
}
|
|
1436
1560
|
function listCodexSessions(limit) {
|
|
1437
|
-
const sessionsDir =
|
|
1561
|
+
const sessionsDir = join5(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join5(homedir4(), ".codex"), "sessions");
|
|
1438
1562
|
const records = [];
|
|
1439
1563
|
function walk(dir) {
|
|
1440
1564
|
let entries;
|
|
@@ -1444,7 +1568,7 @@ function listCodexSessions(limit) {
|
|
|
1444
1568
|
return;
|
|
1445
1569
|
}
|
|
1446
1570
|
for (const entry2 of entries) {
|
|
1447
|
-
const full =
|
|
1571
|
+
const full = join5(dir, entry2.name);
|
|
1448
1572
|
if (entry2.isDirectory()) {
|
|
1449
1573
|
walk(full);
|
|
1450
1574
|
continue;
|
|
@@ -1462,7 +1586,7 @@ function listCodexSessions(limit) {
|
|
|
1462
1586
|
sessionId: data.id,
|
|
1463
1587
|
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
1464
1588
|
title: sniffTitle(full),
|
|
1465
|
-
mtimeMs:
|
|
1589
|
+
mtimeMs: statSync5(full).mtimeMs,
|
|
1466
1590
|
files: [full]
|
|
1467
1591
|
},
|
|
1468
1592
|
...typeof parent === "string" ? { parent } : {}
|
|
@@ -1531,7 +1655,7 @@ async function mintTrace(base, harness2) {
|
|
|
1531
1655
|
function loadSummaryFile(summaryPath) {
|
|
1532
1656
|
let raw;
|
|
1533
1657
|
try {
|
|
1534
|
-
raw =
|
|
1658
|
+
raw = readFileSync7(summaryPath, "utf8");
|
|
1535
1659
|
} catch {
|
|
1536
1660
|
throw new Error(`Cannot read summary file: ${summaryPath}`);
|
|
1537
1661
|
}
|
|
@@ -1563,7 +1687,7 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1563
1687
|
let inputBytes = 0;
|
|
1564
1688
|
let outputBytes = 0;
|
|
1565
1689
|
const lines = [];
|
|
1566
|
-
const digest =
|
|
1690
|
+
const digest = createHash2("sha256");
|
|
1567
1691
|
let nativeId;
|
|
1568
1692
|
let redactionCount = 0;
|
|
1569
1693
|
let encryptedReasoningCount = 0;
|
|
@@ -1572,9 +1696,9 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1572
1696
|
const source = new SessionSourceCollector();
|
|
1573
1697
|
const records = [];
|
|
1574
1698
|
for (const path of session2.files) {
|
|
1575
|
-
const stat =
|
|
1699
|
+
const stat = statSync5(path);
|
|
1576
1700
|
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
1577
|
-
const text =
|
|
1701
|
+
const text = readFileSync7(path, "utf8");
|
|
1578
1702
|
for (const raw of text.split("\n")) {
|
|
1579
1703
|
if (!raw.trim()) continue;
|
|
1580
1704
|
let record;
|
|
@@ -1613,7 +1737,13 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1613
1737
|
const sourceId = nativeId ?? session2.sessionId;
|
|
1614
1738
|
if (!/^[A-Za-z0-9_-]{1,200}$/.test(sourceId) || redactLine2(JSON.stringify(sourceId)) !== JSON.stringify(sourceId)) throw new Error("The source conversation has an invalid identifier.");
|
|
1615
1739
|
const meta = source.result();
|
|
1616
|
-
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : collectSessionFiles(records, {
|
|
1740
|
+
const files2 = options.files === false ? { lines: [], bytes: 0, attached: [], leftOut: [], setup: { skills: [], missingSkills: [], mcpServers: [], git: false } } : collectSessionFiles(records, {
|
|
1741
|
+
harness: session2.harness,
|
|
1742
|
+
cwd: meta.cwd ?? (session2.harness === "claude_code" ? session2.workspace : ""),
|
|
1743
|
+
budget: MAX_IMPORT_BYTES - outputBytes - SUMMARY_RESERVE_BYTES,
|
|
1744
|
+
exclude: session2.files,
|
|
1745
|
+
title: meta.title ?? (session2.harness === "codex" ? codexThreadName(session2.sessionId) : null)
|
|
1746
|
+
});
|
|
1617
1747
|
const attachedNote = files2.attached.length ? ` The ${files2.attached.length} local ${files2.attached.length === 1 ? "file" : "files"} it read, wrote or ran under, the skills it loaded and its workspace description (repository state and tool servers, agentsky/workspace.json) are attached as well (listed in artifacts).` : "";
|
|
1618
1748
|
let summaryJson;
|
|
1619
1749
|
if (summaryPath) {
|
|
@@ -1650,8 +1780,8 @@ function prepareImport(session2, summaryPath, options = {}) {
|
|
|
1650
1780
|
}
|
|
1651
1781
|
async function importSession(opts) {
|
|
1652
1782
|
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
1653
|
-
const
|
|
1654
|
-
if (
|
|
1783
|
+
const destination2 = new URL(base);
|
|
1784
|
+
if (destination2.username || destination2.password || destination2.protocol !== "https:" && !(destination2.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(destination2.hostname))) {
|
|
1655
1785
|
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
1656
1786
|
}
|
|
1657
1787
|
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
@@ -1694,8 +1824,59 @@ async function importSession(opts) {
|
|
|
1694
1824
|
};
|
|
1695
1825
|
}
|
|
1696
1826
|
|
|
1827
|
+
// src/channels.ts
|
|
1828
|
+
function connectionLine(c) {
|
|
1829
|
+
const route = c.bound_session ? `\u2192 ${c.bound_session.id}${c.bound_session.label ? ` (${c.bound_session.label})` : ""}` : "unrouted";
|
|
1830
|
+
return `${c.id} ${c.platform.padEnd(9)} ${c.status.padEnd(12)} ${c.label ?? ""} ${route}`;
|
|
1831
|
+
}
|
|
1832
|
+
function connectSteps(c) {
|
|
1833
|
+
const steps = [];
|
|
1834
|
+
if (c.connect?.url) steps.push(`Open: ${c.connect.url}`);
|
|
1835
|
+
if (c.connect?.phone && c.connect.code) steps.push(`Text ${c.connect.code} to ${c.connect.phone}`);
|
|
1836
|
+
else if (c.connect?.app_id && c.connect.code) steps.push(`Send ${c.connect.code} to the bot (app ${c.connect.app_id})`);
|
|
1837
|
+
else if (c.connect?.code) steps.push(`Send ${c.connect.code} to the bot`);
|
|
1838
|
+
if (c.connect?.expires_at) steps.push(`Expires ${c.connect.expires_at.replace("T", " ").slice(0, 16)}`);
|
|
1839
|
+
return steps;
|
|
1840
|
+
}
|
|
1841
|
+
function destination(opts) {
|
|
1842
|
+
if (Boolean(opts.session) === Boolean(opts.webhook)) {
|
|
1843
|
+
throw new ApiError(400, "invalid_destination", "pass exactly one of --session or --webhook");
|
|
1844
|
+
}
|
|
1845
|
+
return opts.session ? { session: opts.session } : { webhook: opts.webhook };
|
|
1846
|
+
}
|
|
1847
|
+
async function listConnections() {
|
|
1848
|
+
return (await api("GET", "/channels/connections")).connections;
|
|
1849
|
+
}
|
|
1850
|
+
async function connect(platform, opts) {
|
|
1851
|
+
return api("POST", "/channels/connections", {
|
|
1852
|
+
platform,
|
|
1853
|
+
...opts.label ? { label: opts.label } : {},
|
|
1854
|
+
...opts.app ? { app: opts.app } : {},
|
|
1855
|
+
...opts.session || opts.webhook ? { destination: destination(opts) } : {}
|
|
1856
|
+
});
|
|
1857
|
+
}
|
|
1858
|
+
async function waitConnected(id, opts = {}) {
|
|
1859
|
+
const deadline = Date.now() + (opts.timeoutMs ?? 10 * 6e4);
|
|
1860
|
+
for (; ; ) {
|
|
1861
|
+
const found = (await listConnections()).find((c) => c.id === id);
|
|
1862
|
+
if (!found) throw new ApiError(404, "not_found", `connection ${id} is gone`);
|
|
1863
|
+
if (found.status !== "PENDING") return found;
|
|
1864
|
+
if (Date.now() > deadline) throw new ApiError(408, "connect_timeout", "the connection was not claimed within 10 minutes");
|
|
1865
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 3e3));
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
async function bind(connectionId, opts) {
|
|
1869
|
+
await api("PUT", `/channels/connections/${encodeURIComponent(connectionId)}/binding`, { destination: destination(opts) });
|
|
1870
|
+
}
|
|
1871
|
+
async function send(threadId, text) {
|
|
1872
|
+
await api("POST", `/channels/threads/${encodeURIComponent(threadId)}/messages`, { parts: [{ type: "markdown", text }] });
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
// src/connectors.ts
|
|
1876
|
+
import { createServer as createServer2 } from "http";
|
|
1877
|
+
|
|
1697
1878
|
// src/login.ts
|
|
1698
|
-
import { createHash as
|
|
1879
|
+
import { createHash as createHash3, randomBytes } from "crypto";
|
|
1699
1880
|
import { createServer } from "http";
|
|
1700
1881
|
import { hostname } from "os";
|
|
1701
1882
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -1711,7 +1892,7 @@ function openBrowser2(url) {
|
|
|
1711
1892
|
}
|
|
1712
1893
|
async function login(opts) {
|
|
1713
1894
|
const verifier = b64url(randomBytes(32));
|
|
1714
|
-
const challenge = b64url(
|
|
1895
|
+
const challenge = b64url(createHash3("sha256").update(verifier).digest());
|
|
1715
1896
|
const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
|
|
1716
1897
|
saveConfig({
|
|
1717
1898
|
...loadConfig(),
|
|
@@ -1727,7 +1908,7 @@ async function login(opts) {
|
|
|
1727
1908
|
}
|
|
1728
1909
|
async function browserLogin(verifier, challenge) {
|
|
1729
1910
|
const state = b64url(randomBytes(16));
|
|
1730
|
-
const code = await new Promise((
|
|
1911
|
+
const code = await new Promise((resolve10, reject) => {
|
|
1731
1912
|
const server = createServer((req, res) => {
|
|
1732
1913
|
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
1733
1914
|
if (url.pathname !== "/callback") {
|
|
@@ -1740,7 +1921,7 @@ async function browserLogin(verifier, challenge) {
|
|
|
1740
1921
|
if (url.searchParams.get("state") !== state || !got) {
|
|
1741
1922
|
reject(new Error("login callback state mismatch"));
|
|
1742
1923
|
} else {
|
|
1743
|
-
|
|
1924
|
+
resolve10(got);
|
|
1744
1925
|
}
|
|
1745
1926
|
server.close();
|
|
1746
1927
|
});
|
|
@@ -1793,18 +1974,244 @@ async function headlessLogin(verifier, challenge) {
|
|
|
1793
1974
|
throw new Error("login timed out after 10 minutes");
|
|
1794
1975
|
}
|
|
1795
1976
|
|
|
1977
|
+
// src/connectors.ts
|
|
1978
|
+
async function listApps(query, limit = 50) {
|
|
1979
|
+
const apps = [];
|
|
1980
|
+
let cursor = null;
|
|
1981
|
+
do {
|
|
1982
|
+
const params = new URLSearchParams();
|
|
1983
|
+
if (query) params.set("q", query);
|
|
1984
|
+
if (cursor) params.set("cursor", cursor);
|
|
1985
|
+
const qs = params.toString();
|
|
1986
|
+
const page = await api("GET", `/connectors/apps${qs ? `?${qs}` : ""}`);
|
|
1987
|
+
apps.push(...page.apps);
|
|
1988
|
+
cursor = page.next_cursor;
|
|
1989
|
+
} while (cursor && apps.length < limit);
|
|
1990
|
+
return apps.slice(0, limit);
|
|
1991
|
+
}
|
|
1992
|
+
function connectionLine2(c) {
|
|
1993
|
+
return `${c.id} ${c.app_name} (${c.app}) ${c.label} since ${c.created_at.slice(0, 10)}`;
|
|
1994
|
+
}
|
|
1995
|
+
function workspaceConnectionLine(c) {
|
|
1996
|
+
const owner = c.workspace_owned ? "the workspace" : c.mine ? "you" : c.shared_by?.name ?? c.shared_by?.user_id ?? "someone";
|
|
1997
|
+
return `${c.connection_id} ${c.app_name} (${c.app}) ${c.label} shared by ${owner}`;
|
|
1998
|
+
}
|
|
1999
|
+
function browserReturn(start, opts = {}) {
|
|
2000
|
+
return new Promise((resolve10, reject) => {
|
|
2001
|
+
const server = createServer2((req, res) => {
|
|
2002
|
+
const url = new URL(req.url ?? "/", "http://127.0.0.1");
|
|
2003
|
+
if (url.pathname !== "/done" && url.pathname !== "/failed") {
|
|
2004
|
+
res.writeHead(404).end();
|
|
2005
|
+
return;
|
|
2006
|
+
}
|
|
2007
|
+
const ok = url.pathname === "/done";
|
|
2008
|
+
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
|
|
2009
|
+
res.end(ok ? "<p>Connected \u2014 you can close this tab and return to your terminal.</p>" : "<p>The app was not connected. Return to your terminal.</p>");
|
|
2010
|
+
server.close();
|
|
2011
|
+
if (ok) resolve10(url.searchParams.get("account_id") ?? void 0);
|
|
2012
|
+
else reject(new ApiError(400, "connect_failed", "the app was not connected"));
|
|
2013
|
+
});
|
|
2014
|
+
server.listen(0, "127.0.0.1", () => {
|
|
2015
|
+
const address = server.address();
|
|
2016
|
+
const port = typeof address === "object" && address ? address.port : 0;
|
|
2017
|
+
const base = `http://127.0.0.1:${port}`;
|
|
2018
|
+
start(`${base}/done`, `${base}/failed`).then(
|
|
2019
|
+
(authorizeUrl) => {
|
|
2020
|
+
console.log(`Authorize access in your browser:
|
|
2021
|
+
${authorizeUrl}`);
|
|
2022
|
+
if (opts.open !== false) openBrowser2(authorizeUrl);
|
|
2023
|
+
},
|
|
2024
|
+
(err) => {
|
|
2025
|
+
server.close();
|
|
2026
|
+
reject(err);
|
|
2027
|
+
}
|
|
2028
|
+
);
|
|
2029
|
+
});
|
|
2030
|
+
setTimeout(() => {
|
|
2031
|
+
server.close();
|
|
2032
|
+
reject(new ApiError(408, "connect_timeout", "nobody finished the authorization within 10 minutes"));
|
|
2033
|
+
}, opts.timeoutMs ?? 10 * 6e4).unref();
|
|
2034
|
+
});
|
|
2035
|
+
}
|
|
2036
|
+
async function connectApp(app, opts = {}) {
|
|
2037
|
+
const base = opts.workspace ? `/universes/${encodeURIComponent(opts.workspace)}/connections` : "/connectors/connections";
|
|
2038
|
+
const accountId = await browserReturn(async (success_url, error_url) => {
|
|
2039
|
+
const { url } = await api("POST", base, { app, success_url, error_url });
|
|
2040
|
+
return url;
|
|
2041
|
+
}, { open: opts.open });
|
|
2042
|
+
const done = await api("POST", `${base}/complete`, {
|
|
2043
|
+
app,
|
|
2044
|
+
...accountId ? { account_id: accountId } : {}
|
|
2045
|
+
});
|
|
2046
|
+
return done.connection;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
// src/routine-triggers.ts
|
|
2050
|
+
var triggerPath = (id, resource) => `/routines/${encodeURIComponent(id)}/${resource}`;
|
|
2051
|
+
function inboundLine(u) {
|
|
2052
|
+
if (!u) return "no inbound URL \u2014 make one with --create";
|
|
2053
|
+
return u.url ?? `inbound URL ${u.prefix}\u2026 made ${u.created_at.slice(0, 10)}${u.last_used_at ? `, last used ${u.last_used_at.slice(0, 16).replace("T", " ")}` : ", never used"}`;
|
|
2054
|
+
}
|
|
2055
|
+
function precheckLine(p) {
|
|
2056
|
+
if (!p) return "no precheck: every scheduled fire runs";
|
|
2057
|
+
const last = p.last_result ? `last check ${p.last_result}${p.last_error ? ` (${p.last_error.type}: ${p.last_error.message})` : ""}` : "not checked yet";
|
|
2058
|
+
return `fires only when ${p.url} changes \xB7 ${last}`;
|
|
2059
|
+
}
|
|
2060
|
+
function triggerLine(t, available = true) {
|
|
2061
|
+
if (!t) return "no chat trigger";
|
|
2062
|
+
const where = t.thread_id ? `thread ${t.thread_id} on ${t.connection_id}` : `any conversation on ${t.connection_id}`;
|
|
2063
|
+
const what = t.text_prefix ? `messages starting "${t.text_prefix}"` : "every message";
|
|
2064
|
+
return `${what} in ${where} start a run${available ? "" : " (chat messages are not offered to routines in this deployment)"}`;
|
|
2065
|
+
}
|
|
2066
|
+
function deliverLine(d) {
|
|
2067
|
+
return d ? `answers post to thread ${d.thread_id} on ${d.connection_id}` : "answers are posted nowhere";
|
|
2068
|
+
}
|
|
2069
|
+
function triggerBody(opts) {
|
|
2070
|
+
if (!opts.connection) throw new ApiError(400, "invalid_request", "--connection is required (see `sky channel list`)");
|
|
2071
|
+
return { connection_id: opts.connection, ...opts.thread ? { thread_id: opts.thread } : {}, ...opts.prefix ? { text_prefix: opts.prefix } : {} };
|
|
2072
|
+
}
|
|
2073
|
+
function deliverBody(opts) {
|
|
2074
|
+
if (!opts.connection) throw new ApiError(400, "invalid_request", "--connection is required (see `sky channel list`)");
|
|
2075
|
+
return { type: "channel_thread", connection_id: opts.connection, ...opts.thread ? { thread_id: opts.thread } : {} };
|
|
2076
|
+
}
|
|
2077
|
+
async function triggerRequest(id, resource, change) {
|
|
2078
|
+
const path = triggerPath(id, resource);
|
|
2079
|
+
if (change.remove) return api("DELETE", path);
|
|
2080
|
+
if (change.create) return api("POST", path);
|
|
2081
|
+
if (change.body) return api("PUT", path, change.body);
|
|
2082
|
+
return api("GET", path);
|
|
2083
|
+
}
|
|
2084
|
+
|
|
2085
|
+
// src/session-workspace.ts
|
|
2086
|
+
import { spawnSync as spawnSync2 } from "child_process";
|
|
2087
|
+
import { createHash as createHash4, randomUUID } from "crypto";
|
|
2088
|
+
import { readdirSync as readdirSync3, statSync as statSync6 } from "fs";
|
|
2089
|
+
import { basename as basename6, join as join6, relative as relative3, resolve as resolve5 } from "path";
|
|
2090
|
+
var SINGLE_IMPORT_MAX = 25 * 1024 * 1024;
|
|
2091
|
+
var PART_SIZE = 8 * 1024 * 1024;
|
|
2092
|
+
var SKIPPED = /* @__PURE__ */ new Set([".git", "node_modules"]);
|
|
2093
|
+
var workspacePath = (id) => `/sessions/${encodeURIComponent(id)}/workspace`;
|
|
2094
|
+
async function listFiles(id, path, hidden) {
|
|
2095
|
+
const query = new URLSearchParams();
|
|
2096
|
+
if (path) query.set("path", path);
|
|
2097
|
+
if (hidden) query.set("hidden", "true");
|
|
2098
|
+
const qs = query.toString();
|
|
2099
|
+
return api("GET", `${workspacePath(id)}/files${qs ? `?${qs}` : ""}`);
|
|
2100
|
+
}
|
|
2101
|
+
function entryLine(e) {
|
|
2102
|
+
const size = e.type === "directory" ? "-" : String(e.size ?? 0);
|
|
2103
|
+
return `${e.type === "directory" ? "d" : "-"} ${size.padStart(10)} ${(e.mtime ?? "").slice(0, 16).replace("T", " ")} ${e.name}${e.type === "directory" ? "/" : ""}`;
|
|
2104
|
+
}
|
|
2105
|
+
async function readFile(id, path) {
|
|
2106
|
+
return (await apiBytes("GET", `${workspacePath(id)}/files/content?path=${encodeURIComponent(path)}`)).bytes;
|
|
2107
|
+
}
|
|
2108
|
+
function renderChanges(changes) {
|
|
2109
|
+
if (!changes.repos.length) return [`no git repository under ${changes.root}`];
|
|
2110
|
+
return changes.repos.flatMap((r) => [
|
|
2111
|
+
`${r.path === "." ? changes.root : r.path}: ${r.status ? "changed" : "clean"}`,
|
|
2112
|
+
...(r.stat ?? r.status ?? "").split("\n").filter(Boolean).map((line) => ` ${line}`)
|
|
2113
|
+
]);
|
|
2114
|
+
}
|
|
2115
|
+
async function workspacePatch(id, repo) {
|
|
2116
|
+
const { patch, ...meta } = await api("POST", `${workspacePath(id)}/patch`, { repo });
|
|
2117
|
+
return { meta, patch: Buffer.from(patch, "base64") };
|
|
2118
|
+
}
|
|
2119
|
+
function folderFiles(dir) {
|
|
2120
|
+
const git2 = spawnSync2("git", ["-C", dir, "ls-files", "-z", "--cached", "--others", "--exclude-standard"], { encoding: "utf8" });
|
|
2121
|
+
const top = spawnSync2("git", ["-C", dir, "rev-parse", "--show-prefix"], { encoding: "utf8" });
|
|
2122
|
+
if (git2.status === 0 && top.status === 0) {
|
|
2123
|
+
return git2.stdout.split("\0").filter((path) => path && statSync6(join6(dir, path), { throwIfNoEntry: false })?.isFile());
|
|
2124
|
+
}
|
|
2125
|
+
const files2 = [];
|
|
2126
|
+
const walk = (at) => {
|
|
2127
|
+
for (const entry2 of readdirSync3(at, { withFileTypes: true })) {
|
|
2128
|
+
if (SKIPPED.has(entry2.name)) continue;
|
|
2129
|
+
const full = join6(at, entry2.name);
|
|
2130
|
+
if (entry2.isDirectory()) walk(full);
|
|
2131
|
+
else if (entry2.isFile()) files2.push(relative3(dir, full));
|
|
2132
|
+
}
|
|
2133
|
+
};
|
|
2134
|
+
walk(dir);
|
|
2135
|
+
return files2;
|
|
2136
|
+
}
|
|
2137
|
+
function folderArchive(dir, files2) {
|
|
2138
|
+
const tar = spawnSync2("tar", ["--format=ustar", "-czf", "-", "-C", dir, "-T", "-"], {
|
|
2139
|
+
input: files2.join("\n"),
|
|
2140
|
+
env: { ...process.env, COPYFILE_DISABLE: "1" },
|
|
2141
|
+
maxBuffer: 2 * 1024 * 1024 * 1024
|
|
2142
|
+
});
|
|
2143
|
+
if (tar.status !== 0) throw new ApiError(400, "archive_failed", `tar failed: ${tar.stderr.toString().trim()}`);
|
|
2144
|
+
return tar.stdout;
|
|
2145
|
+
}
|
|
2146
|
+
async function importFolder(id, dir, opts = {}) {
|
|
2147
|
+
const root = resolve5(dir);
|
|
2148
|
+
const name = opts.name ?? basename6(root);
|
|
2149
|
+
const archive = opts.archive ?? folderArchive(root, folderFiles(root));
|
|
2150
|
+
if (archive.length <= SINGLE_IMPORT_MAX) {
|
|
2151
|
+
const res = await apiBytes("POST", `${workspacePath(id)}/import?name=${encodeURIComponent(name)}`, { bytes: archive, mediaType: "application/gzip" });
|
|
2152
|
+
return res.json();
|
|
2153
|
+
}
|
|
2154
|
+
const sha256 = createHash4("sha256").update(archive).digest("hex");
|
|
2155
|
+
let upload = await api("POST", `${workspacePath(id)}/imports`, { name, size: archive.length, sha256, part_size: PART_SIZE });
|
|
2156
|
+
const uploadPath = `${workspacePath(id)}/imports/${encodeURIComponent(upload.id)}`;
|
|
2157
|
+
for (let part = 0; part < upload.parts; part += 1) {
|
|
2158
|
+
if (upload.received.includes(part)) continue;
|
|
2159
|
+
const bytes = archive.subarray(part * upload.part_size, (part + 1) * upload.part_size);
|
|
2160
|
+
await apiBytes("PUT", `${uploadPath}/parts/${part}`, { bytes, mediaType: "application/octet-stream" });
|
|
2161
|
+
}
|
|
2162
|
+
upload = await api("POST", `${uploadPath}/complete`);
|
|
2163
|
+
while (upload.state !== "imported" && upload.state !== "failed") {
|
|
2164
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
2165
|
+
upload = await api("GET", uploadPath);
|
|
2166
|
+
}
|
|
2167
|
+
if (upload.state === "failed") throw new ApiError(422, upload.error?.code ?? "import_failed", upload.error?.message ?? "the import failed");
|
|
2168
|
+
return { path: upload.path ?? name, files: upload.files ?? 0 };
|
|
2169
|
+
}
|
|
2170
|
+
function renderAccess(a) {
|
|
2171
|
+
const lines = [
|
|
2172
|
+
`${a.session_id}${a.title ? ` \u2014 ${a.title}` : ""}`,
|
|
2173
|
+
` ${a.visibility === "private" ? "private to its owner" : `visible to ${a.workspace.name} (${a.workspace.slug})`} \xB7 you are its ${a.access}`,
|
|
2174
|
+
` owner: ${a.owner.name ?? a.owner.email ?? "unknown"}`
|
|
2175
|
+
];
|
|
2176
|
+
if (a.viewers) lines.push(a.viewers.length ? ` viewers: ${a.viewers.map((v) => v.email).join(", ")}` : " viewers: none");
|
|
2177
|
+
return lines;
|
|
2178
|
+
}
|
|
2179
|
+
function accessPatch(opts) {
|
|
2180
|
+
if (opts.private && opts.members) throw new ApiError(400, "invalid_request", "pass --private or --members, not both");
|
|
2181
|
+
return {
|
|
2182
|
+
...opts.private ? { visibility: "private" } : opts.members ? { visibility: "workspace" } : {},
|
|
2183
|
+
...opts.addViewer?.length ? { add_viewers: opts.addViewer } : {},
|
|
2184
|
+
...opts.removeViewer?.length ? { remove_viewers: opts.removeViewer } : {}
|
|
2185
|
+
};
|
|
2186
|
+
}
|
|
2187
|
+
var TERMINAL = /* @__PURE__ */ new Set(["completed", "failed", "cancelled"]);
|
|
2188
|
+
async function handoff(id, targetAgent, opts = {}) {
|
|
2189
|
+
let { handoff: h } = await api("POST", `/sessions/${encodeURIComponent(id)}/handoff`, {
|
|
2190
|
+
target_agent: targetAgent,
|
|
2191
|
+
...opts.fromMessage ? { last_message_id: opts.fromMessage } : {}
|
|
2192
|
+
}, { idempotencyKey: opts.idempotencyKey ?? randomUUID() });
|
|
2193
|
+
opts.onStatus?.(h);
|
|
2194
|
+
while (opts.wait && !TERMINAL.has(h.status)) {
|
|
2195
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
2196
|
+
const next = (await api("GET", `/session-handoffs/${encodeURIComponent(h.id)}`)).handoff;
|
|
2197
|
+
if (next.status !== h.status) opts.onStatus?.(next);
|
|
2198
|
+
h = next;
|
|
2199
|
+
}
|
|
2200
|
+
return h;
|
|
2201
|
+
}
|
|
2202
|
+
|
|
1796
2203
|
// src/local-agents.ts
|
|
1797
2204
|
import { existsSync as existsSync6 } from "fs";
|
|
1798
2205
|
import { homedir as homedir5 } from "os";
|
|
1799
|
-
import { join as
|
|
2206
|
+
import { join as join7 } from "path";
|
|
1800
2207
|
function localAgents(env = process.env, home = homedir5()) {
|
|
1801
|
-
const claude = env.CLAUDE_CONFIG_DIR ||
|
|
1802
|
-
const codex = env.CODEX_HOME ||
|
|
1803
|
-
const cursor =
|
|
2208
|
+
const claude = env.CLAUDE_CONFIG_DIR || join7(home, ".claude");
|
|
2209
|
+
const codex = env.CODEX_HOME || join7(home, ".codex");
|
|
2210
|
+
const cursor = join7(home, ".cursor");
|
|
1804
2211
|
return [
|
|
1805
|
-
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir:
|
|
1806
|
-
{ id: "codex", name: "Codex", root: codex, skillsDir:
|
|
1807
|
-
{ id: "cursor", name: "Cursor", root: cursor, skillsDir:
|
|
2212
|
+
{ id: "claude_code", name: "Claude Code", root: claude, skillsDir: join7(claude, "skills") },
|
|
2213
|
+
{ id: "codex", name: "Codex", root: codex, skillsDir: join7(codex, "skills") },
|
|
2214
|
+
{ id: "cursor", name: "Cursor", root: cursor, skillsDir: join7(cursor, "skills") }
|
|
1808
2215
|
];
|
|
1809
2216
|
}
|
|
1810
2217
|
function detectLocalAgents(env = process.env, home = homedir5()) {
|
|
@@ -1904,21 +2311,21 @@ async function readTurn(sessionId, opts = {}) {
|
|
|
1904
2311
|
for (; ; ) {
|
|
1905
2312
|
const reading = latestTurn(await history(sessionId));
|
|
1906
2313
|
if (reading.finished || !opts.wait || Date.now() >= deadline) return reading;
|
|
1907
|
-
await new Promise((
|
|
2314
|
+
await new Promise((resolve10) => setTimeout(resolve10, opts.intervalMs ?? 3e3));
|
|
1908
2315
|
}
|
|
1909
2316
|
}
|
|
1910
2317
|
|
|
1911
2318
|
// src/setup.ts
|
|
1912
|
-
import { mkdirSync as
|
|
1913
|
-
import { dirname as
|
|
2319
|
+
import { mkdirSync as mkdirSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync5 } from "fs";
|
|
2320
|
+
import { dirname as dirname6, join as join11 } from "path";
|
|
1914
2321
|
import { createInterface as createInterface3 } from "readline";
|
|
1915
2322
|
|
|
1916
2323
|
// src/mcp-register.ts
|
|
1917
|
-
import { spawnSync as
|
|
2324
|
+
import { spawnSync as spawnSync3 } from "child_process";
|
|
1918
2325
|
|
|
1919
2326
|
// src/skill.ts
|
|
1920
|
-
import { mkdirSync, readFileSync as
|
|
1921
|
-
import { join as
|
|
2327
|
+
import { mkdirSync as mkdirSync2, readFileSync as readFileSync8, writeFileSync as writeFileSync4 } from "fs";
|
|
2328
|
+
import { join as join8 } from "path";
|
|
1922
2329
|
|
|
1923
2330
|
// src/catalog-file.ts
|
|
1924
2331
|
var CATALOG_FILE = "catalog.md";
|
|
@@ -2359,7 +2766,7 @@ To set a per-repo default, create \`.agentsky.json\` at the git root:
|
|
|
2359
2766
|
}
|
|
2360
2767
|
function installedVersion(dir) {
|
|
2361
2768
|
try {
|
|
2362
|
-
const marker = JSON.parse(
|
|
2769
|
+
const marker = JSON.parse(readFileSync8(join8(dir, SKILL_MARKER), "utf8"));
|
|
2363
2770
|
return typeof marker.version === "string" ? marker.version : null;
|
|
2364
2771
|
} catch {
|
|
2365
2772
|
return null;
|
|
@@ -2369,25 +2776,25 @@ function isInstalledSkill(dir) {
|
|
|
2369
2776
|
return installedVersion(dir) !== null;
|
|
2370
2777
|
}
|
|
2371
2778
|
function installSkill(agent2, opts = {}) {
|
|
2372
|
-
const dir =
|
|
2779
|
+
const dir = join8(agent2.skillsDir, SKILL_NAME);
|
|
2373
2780
|
const previous = installedVersion(dir);
|
|
2374
2781
|
const outcome = previous === CLI_VERSION ? "current" : previous ? "updated" : "installed";
|
|
2375
2782
|
if (opts.dryRun) return { agent: agent2.id, path: dir, outcome };
|
|
2376
2783
|
if (outcome !== "current") {
|
|
2377
|
-
|
|
2378
|
-
|
|
2379
|
-
|
|
2784
|
+
mkdirSync2(dir, { recursive: true });
|
|
2785
|
+
writeFileSync4(join8(dir, "SKILL.md"), skillDocument());
|
|
2786
|
+
writeFileSync4(join8(dir, SKILL_MARKER), `${JSON.stringify({ version: CLI_VERSION }, null, 2)}
|
|
2380
2787
|
`);
|
|
2381
2788
|
}
|
|
2382
2789
|
if (opts.catalog) {
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
}
|
|
2386
|
-
const refsDir =
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2790
|
+
mkdirSync2(dir, { recursive: true });
|
|
2791
|
+
writeFileSync4(join8(dir, CATALOG_FILE), renderCatalog(opts.catalog, opts.now ?? /* @__PURE__ */ new Date()));
|
|
2792
|
+
}
|
|
2793
|
+
const refsDir = join8(dir, REFERENCES_DIR);
|
|
2794
|
+
mkdirSync2(refsDir, { recursive: true });
|
|
2795
|
+
writeFileSync4(join8(refsDir, "import.md"), importReferenceDoc());
|
|
2796
|
+
writeFileSync4(join8(refsDir, "workspaces.md"), workspacesReferenceDoc());
|
|
2797
|
+
writeFileSync4(join8(refsDir, "focused-agents.md"), focusedAgentsReferenceDoc());
|
|
2391
2798
|
return { agent: agent2.id, path: dir, outcome };
|
|
2392
2799
|
}
|
|
2393
2800
|
|
|
@@ -2396,9 +2803,9 @@ var MCP_SERVER_NAME = SKILL_NAME;
|
|
|
2396
2803
|
var CODEX_TOKEN_ENV = "AGENTSKY_API_KEY";
|
|
2397
2804
|
function systemCommandRunner() {
|
|
2398
2805
|
return {
|
|
2399
|
-
has: (command) =>
|
|
2806
|
+
has: (command) => spawnSync3(command, ["--version"], { stdio: "ignore" }).status === 0,
|
|
2400
2807
|
run: (command, args) => {
|
|
2401
|
-
const result =
|
|
2808
|
+
const result = spawnSync3(command, args, { encoding: "utf8" });
|
|
2402
2809
|
return { status: result.status, stderr: result.stderr ?? "" };
|
|
2403
2810
|
}
|
|
2404
2811
|
};
|
|
@@ -2439,9 +2846,9 @@ function registerMcpServer(agent2, opts, runner = systemCommandRunner()) {
|
|
|
2439
2846
|
|
|
2440
2847
|
// src/setup-local.ts
|
|
2441
2848
|
import { isUtf8 as isUtf82 } from "buffer";
|
|
2442
|
-
import { existsSync as existsSync7, readdirSync as
|
|
2849
|
+
import { existsSync as existsSync7, readdirSync as readdirSync4, readFileSync as readFileSync9, statSync as statSync7 } from "fs";
|
|
2443
2850
|
import { homedir as homedir6 } from "os";
|
|
2444
|
-
import { dirname as
|
|
2851
|
+
import { dirname as dirname5, isAbsolute as isAbsolute2, join as join9, resolve as resolve6 } from "path";
|
|
2445
2852
|
import { parse as parseToml3 } from "smol-toml";
|
|
2446
2853
|
var MAX_SKILL_FILE_BYTES = 1024 * 1024;
|
|
2447
2854
|
var MAX_IMPORT_DEPTH = 5;
|
|
@@ -2465,12 +2872,12 @@ function redacted(data) {
|
|
|
2465
2872
|
}
|
|
2466
2873
|
function skillFiles2(root, prefix) {
|
|
2467
2874
|
const files2 = [];
|
|
2468
|
-
for (const entry2 of
|
|
2469
|
-
const path =
|
|
2875
|
+
for (const entry2 of readdirSync4(root, { withFileTypes: true })) {
|
|
2876
|
+
const path = join9(root, entry2.name);
|
|
2470
2877
|
if (entry2.isDirectory()) {
|
|
2471
2878
|
if (!SKIPPED_DIRS.has(entry2.name)) files2.push(...skillFiles2(path, `${prefix}/${entry2.name}`));
|
|
2472
|
-
} else if (entry2.isFile() && !sensitive(entry2.name) &&
|
|
2473
|
-
const bytes = redacted(
|
|
2879
|
+
} else if (entry2.isFile() && !sensitive(entry2.name) && statSync7(path).size <= MAX_SKILL_FILE_BYTES) {
|
|
2880
|
+
const bytes = redacted(readFileSync9(path));
|
|
2474
2881
|
if (bytes) files2.push({ path: `${prefix}/${entry2.name}`, bytes });
|
|
2475
2882
|
}
|
|
2476
2883
|
}
|
|
@@ -2481,18 +2888,18 @@ function readLocalSkills(agents) {
|
|
|
2481
2888
|
const ignored = [];
|
|
2482
2889
|
for (const agent2 of agents) {
|
|
2483
2890
|
if (!existsSync7(agent2.skillsDir)) continue;
|
|
2484
|
-
for (const entry2 of
|
|
2485
|
-
const dir =
|
|
2891
|
+
for (const entry2 of readdirSync4(agent2.skillsDir, { withFileTypes: true })) {
|
|
2892
|
+
const dir = join9(agent2.skillsDir, entry2.name);
|
|
2486
2893
|
let isDir2 = entry2.isDirectory();
|
|
2487
2894
|
if (entry2.isSymbolicLink()) {
|
|
2488
2895
|
try {
|
|
2489
|
-
isDir2 =
|
|
2896
|
+
isDir2 = statSync7(dir).isDirectory();
|
|
2490
2897
|
} catch {
|
|
2491
2898
|
isDir2 = false;
|
|
2492
2899
|
}
|
|
2493
2900
|
}
|
|
2494
|
-
if (!isDir2 || !existsSync7(
|
|
2495
|
-
const name = frontmatterName(
|
|
2901
|
+
if (!isDir2 || !existsSync7(join9(dir, "SKILL.md")) || isInstalledSkill(dir)) continue;
|
|
2902
|
+
const name = frontmatterName(readFileSync9(join9(dir, "SKILL.md"), "utf8"));
|
|
2496
2903
|
if (!name) {
|
|
2497
2904
|
ignored.push({ agent: agent2.id, dir, reason: "SKILL.md has no name" });
|
|
2498
2905
|
continue;
|
|
@@ -2520,11 +2927,11 @@ function inlineImports(content, from, home, depth, seen) {
|
|
|
2520
2927
|
}
|
|
2521
2928
|
const target = fence ? void 0 : line.match(/^@(\S+\.(?:md|markdown|txt))$/i)?.[1];
|
|
2522
2929
|
if (!target || depth >= MAX_IMPORT_DEPTH) return line;
|
|
2523
|
-
const path = target.startsWith("~/") ?
|
|
2930
|
+
const path = target.startsWith("~/") ? join9(home, target.slice(2)) : isAbsolute2(target) ? target : resolve6(dirname5(from), target);
|
|
2524
2931
|
if (seen.includes(path)) return "";
|
|
2525
2932
|
let imported;
|
|
2526
2933
|
try {
|
|
2527
|
-
imported =
|
|
2934
|
+
imported = readFileSync9(path, "utf8");
|
|
2528
2935
|
} catch {
|
|
2529
2936
|
return line;
|
|
2530
2937
|
}
|
|
@@ -2537,10 +2944,10 @@ function readGlobalInstructions(agents, home = homedir6()) {
|
|
|
2537
2944
|
for (const agent2 of agents) {
|
|
2538
2945
|
const file = INSTRUCTION_FILES2[agent2.id];
|
|
2539
2946
|
if (!file) continue;
|
|
2540
|
-
const path =
|
|
2947
|
+
const path = join9(agent2.root, file);
|
|
2541
2948
|
let raw;
|
|
2542
2949
|
try {
|
|
2543
|
-
raw =
|
|
2950
|
+
raw = readFileSync9(path, "utf8");
|
|
2544
2951
|
} catch {
|
|
2545
2952
|
continue;
|
|
2546
2953
|
}
|
|
@@ -2559,7 +2966,7 @@ ${item.content.trim()}`).join("\n\n");
|
|
|
2559
2966
|
}
|
|
2560
2967
|
function readJson3(path) {
|
|
2561
2968
|
try {
|
|
2562
|
-
return JSON.parse(
|
|
2969
|
+
return JSON.parse(readFileSync9(path, "utf8"));
|
|
2563
2970
|
} catch {
|
|
2564
2971
|
return void 0;
|
|
2565
2972
|
}
|
|
@@ -2573,10 +2980,10 @@ function bearerOf(def) {
|
|
|
2573
2980
|
return match?.[1] ?? null;
|
|
2574
2981
|
}
|
|
2575
2982
|
function serversOf(agent2, home) {
|
|
2576
|
-
if (agent2.id === "claude_code") return readJson3(
|
|
2577
|
-
if (agent2.id === "cursor") return readJson3(
|
|
2983
|
+
if (agent2.id === "claude_code") return readJson3(join9(home, ".claude.json"))?.mcpServers ?? {};
|
|
2984
|
+
if (agent2.id === "cursor") return readJson3(join9(agent2.root, "mcp.json"))?.mcpServers ?? {};
|
|
2578
2985
|
try {
|
|
2579
|
-
return parseToml3(
|
|
2986
|
+
return parseToml3(readFileSync9(join9(agent2.root, "config.toml"), "utf8")).mcp_servers ?? {};
|
|
2580
2987
|
} catch {
|
|
2581
2988
|
return {};
|
|
2582
2989
|
}
|
|
@@ -2597,9 +3004,9 @@ function findLocalSecrets(agents, home = homedir6()) {
|
|
|
2597
3004
|
|
|
2598
3005
|
// src/subscriptions.ts
|
|
2599
3006
|
import { spawn as spawn3 } from "child_process";
|
|
2600
|
-
import { existsSync as existsSync8, readFileSync as
|
|
3007
|
+
import { existsSync as existsSync8, readFileSync as readFileSync10 } from "fs";
|
|
2601
3008
|
import { homedir as homedir7 } from "os";
|
|
2602
|
-
import { join as
|
|
3009
|
+
import { join as join10 } from "path";
|
|
2603
3010
|
import { createInterface as createInterface2 } from "readline";
|
|
2604
3011
|
var ALIASES = {
|
|
2605
3012
|
claude: "anthropic",
|
|
@@ -2615,14 +3022,24 @@ function providerFor(name) {
|
|
|
2615
3022
|
}
|
|
2616
3023
|
return provider;
|
|
2617
3024
|
}
|
|
3025
|
+
var MANAGED_ALIASES = {
|
|
3026
|
+
...ALIASES,
|
|
3027
|
+
cursor: "cursor",
|
|
3028
|
+
grok: "xai",
|
|
3029
|
+
xai: "xai",
|
|
3030
|
+
muse: "meta",
|
|
3031
|
+
meta: "meta"
|
|
3032
|
+
};
|
|
2618
3033
|
function managedProviderFor(name) {
|
|
2619
|
-
|
|
2620
|
-
const provider = ALIASES[name.toLowerCase()];
|
|
3034
|
+
const provider = MANAGED_ALIASES[name.toLowerCase()];
|
|
2621
3035
|
if (!provider) {
|
|
2622
|
-
throw new ApiError(400, "unknown_provider", `unknown provider "${name}" \u2014 use claude, chatgpt or
|
|
3036
|
+
throw new ApiError(400, "unknown_provider", `unknown provider "${name}" \u2014 use claude, chatgpt, cursor, grok or muse`);
|
|
2623
3037
|
}
|
|
2624
3038
|
return provider;
|
|
2625
3039
|
}
|
|
3040
|
+
function accountQuery(account) {
|
|
3041
|
+
return account ? `?accountId=${encodeURIComponent(account)}` : "";
|
|
3042
|
+
}
|
|
2626
3043
|
function ask(question) {
|
|
2627
3044
|
return new Promise((done) => {
|
|
2628
3045
|
const rl = createInterface2({ input: process.stdin, output: process.stdout });
|
|
@@ -2685,11 +3102,11 @@ async function connectClaude() {
|
|
|
2685
3102
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
2686
3103
|
}
|
|
2687
3104
|
function codexSignIn(env = process.env, home = homedir7()) {
|
|
2688
|
-
const path =
|
|
3105
|
+
const path = join10(env.CODEX_HOME || join10(home, ".codex"), "auth.json");
|
|
2689
3106
|
if (!existsSync8(path)) {
|
|
2690
3107
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
2691
3108
|
}
|
|
2692
|
-
const raw =
|
|
3109
|
+
const raw = readFileSync10(path, "utf8");
|
|
2693
3110
|
try {
|
|
2694
3111
|
const parsed = JSON.parse(raw);
|
|
2695
3112
|
return { path, raw, account: parsed.tokens?.account_id };
|
|
@@ -2698,11 +3115,11 @@ function codexSignIn(env = process.env, home = homedir7()) {
|
|
|
2698
3115
|
}
|
|
2699
3116
|
}
|
|
2700
3117
|
function cursorSignIn(env = process.env, home = homedir7()) {
|
|
2701
|
-
const path =
|
|
3118
|
+
const path = join10(env.XDG_CONFIG_HOME || join10(home, ".config"), "cursor", "auth.json");
|
|
2702
3119
|
if (!existsSync8(path)) {
|
|
2703
3120
|
throw new ApiError(404, "no_cursor_auth", `no ${path} \u2014 run \`cursor-agent login\` first`);
|
|
2704
3121
|
}
|
|
2705
|
-
const raw =
|
|
3122
|
+
const raw = readFileSync10(path, "utf8");
|
|
2706
3123
|
try {
|
|
2707
3124
|
const parsed = JSON.parse(raw);
|
|
2708
3125
|
if (typeof parsed.accessToken !== "string" || typeof parsed.refreshToken !== "string") throw new Error("shape");
|
|
@@ -2765,7 +3182,87 @@ async function disconnect(name, opts) {
|
|
|
2765
3182
|
console.log(
|
|
2766
3183
|
`${affectedAgents.length} agent${affectedAgents.length === 1 ? "" : "s"} still point at it and will fail: ` + affectedAgents.map((a) => a.agentId ?? a.id).join(", ")
|
|
2767
3184
|
);
|
|
2768
|
-
console.log(
|
|
3185
|
+
console.log(`Move them to platform credits with \`sky auth switch-to-credits ${name}\`, or reconnect.`);
|
|
3186
|
+
}
|
|
3187
|
+
}
|
|
3188
|
+
async function browserSignIn(name, opts) {
|
|
3189
|
+
const provider = managedProviderFor(name);
|
|
3190
|
+
const base = `/model-subscriptions/${provider}/logins`;
|
|
3191
|
+
const readCode = opts.readCode ?? ask;
|
|
3192
|
+
let { login: login2 } = await api("POST", base, {
|
|
3193
|
+
...opts.account ? { accountId: opts.account } : {},
|
|
3194
|
+
...opts.label ? { label: opts.label } : {}
|
|
3195
|
+
});
|
|
3196
|
+
let shown = false;
|
|
3197
|
+
let codeSent = false;
|
|
3198
|
+
const cancel = () => {
|
|
3199
|
+
if ("loginId" in login2 && login2.loginId) {
|
|
3200
|
+
void api("DELETE", `${base}/${encodeURIComponent(login2.loginId)}`).catch(() => void 0);
|
|
3201
|
+
}
|
|
3202
|
+
};
|
|
3203
|
+
process.once("SIGINT", cancel);
|
|
3204
|
+
try {
|
|
3205
|
+
for (; ; ) {
|
|
3206
|
+
if (login2.state === "connected" || login2.state === "cleanup_pending") {
|
|
3207
|
+
if (login2.state === "cleanup_pending") cancel();
|
|
3208
|
+
return login2.subscription;
|
|
3209
|
+
}
|
|
3210
|
+
if (login2.state === "failed") {
|
|
3211
|
+
if (login2.loginId) cancel();
|
|
3212
|
+
throw new ApiError(422, "sign_in_failed", login2.reason);
|
|
3213
|
+
}
|
|
3214
|
+
if (login2.state === "waiting" && !shown) {
|
|
3215
|
+
shown = true;
|
|
3216
|
+
console.log(`Sign in to ${MODEL_SUBSCRIPTION_PROVIDER_NAME[provider]} at:
|
|
3217
|
+
${login2.url}`);
|
|
3218
|
+
if (login2.userCode) console.log(`Confirm this code on that page: ${login2.userCode}`);
|
|
3219
|
+
if (opts.browser !== false) openBrowser2(login2.url);
|
|
3220
|
+
}
|
|
3221
|
+
if (login2.state === "waiting" && login2.acceptsCode && !codeSent) {
|
|
3222
|
+
const code = await readCode("Paste the code the sign-in page shows: ");
|
|
3223
|
+
if (!code) throw new ApiError(400, "no_code", "no code provided");
|
|
3224
|
+
await api("POST", `${base}/${encodeURIComponent(login2.loginId)}/code`, { code });
|
|
3225
|
+
codeSent = true;
|
|
3226
|
+
} else {
|
|
3227
|
+
await new Promise((r) => setTimeout(r, opts.pollMs ?? 2e3));
|
|
3228
|
+
}
|
|
3229
|
+
({ login: login2 } = await api("GET", `${base}/${encodeURIComponent(login2.loginId)}`));
|
|
3230
|
+
}
|
|
3231
|
+
} finally {
|
|
3232
|
+
process.removeListener("SIGINT", cancel);
|
|
3233
|
+
}
|
|
3234
|
+
}
|
|
3235
|
+
async function subscriptionUsage(name, account) {
|
|
3236
|
+
const provider = managedProviderFor(name);
|
|
3237
|
+
const { usage } = await api("GET", `/model-subscriptions/${provider}/usage${accountQuery(account)}`);
|
|
3238
|
+
return usage;
|
|
3239
|
+
}
|
|
3240
|
+
function renderSubscriptionUsage(name, usage) {
|
|
3241
|
+
const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[managedProviderFor(name)];
|
|
3242
|
+
if (usage.status === "unavailable") return [`${label}: usage unavailable (${usage.reason ?? "unknown"})`];
|
|
3243
|
+
const head = [`${label}${usage.plan ? ` ${usage.plan}` : ""}`, usage.allowed === false ? "limit reached" : null].filter(Boolean).join(" \xB7 ");
|
|
3244
|
+
const rows = usage.windows.map(
|
|
3245
|
+
(w) => ` ${w.label}: ${Math.round(w.remainingPercent)}% left${w.resetsAt ? ` \xB7 resets ${w.resetsAt.slice(0, 16).replace("T", " ")}` : ""}`
|
|
3246
|
+
);
|
|
3247
|
+
return [head, ...rows.length ? rows : [" no usage windows reported"]];
|
|
3248
|
+
}
|
|
3249
|
+
async function switchToCredits(name, opts) {
|
|
3250
|
+
const provider = managedProviderFor(name);
|
|
3251
|
+
const label = MODEL_SUBSCRIPTION_PROVIDER_NAME[provider];
|
|
3252
|
+
if (!opts.yes) {
|
|
3253
|
+
const ok = await confirm2(`Move every agent on this ${label} account to platform credits? Their running sessions restart.`);
|
|
3254
|
+
if (!ok) {
|
|
3255
|
+
console.log("Aborted.");
|
|
3256
|
+
return;
|
|
3257
|
+
}
|
|
3258
|
+
}
|
|
3259
|
+
const result = await api(
|
|
3260
|
+
"POST",
|
|
3261
|
+
`/model-subscriptions/${provider}/switch-to-credits${accountQuery(opts.account)}`
|
|
3262
|
+
);
|
|
3263
|
+
console.log(`Moved ${result.switched} agent${result.switched === 1 ? "" : "s"} to platform credits.`);
|
|
3264
|
+
if (result.restartFailures.length) {
|
|
3265
|
+
console.log(`Couldn't restart: ${result.restartFailures.join(", ")} \u2014 they pick up credits on their next start.`);
|
|
2769
3266
|
}
|
|
2770
3267
|
}
|
|
2771
3268
|
|
|
@@ -2781,11 +3278,11 @@ function planProviders(names2) {
|
|
|
2781
3278
|
}));
|
|
2782
3279
|
}
|
|
2783
3280
|
function reportPath() {
|
|
2784
|
-
return
|
|
3281
|
+
return join11(dirname6(configPath()), "setup.json");
|
|
2785
3282
|
}
|
|
2786
3283
|
function loadReport() {
|
|
2787
3284
|
try {
|
|
2788
|
-
const saved = JSON.parse(
|
|
3285
|
+
const saved = JSON.parse(readFileSync11(reportPath(), "utf8"));
|
|
2789
3286
|
return { agents: saved.agents ?? [], plans: saved.plans ?? [], ...saved.secrets ? { secrets: saved.secrets } : {} };
|
|
2790
3287
|
} catch {
|
|
2791
3288
|
return { agents: [], plans: [] };
|
|
@@ -2798,8 +3295,8 @@ function mergeReport(base, next) {
|
|
|
2798
3295
|
return { agents, plans, ...secrets2 ? { secrets: secrets2 } : {} };
|
|
2799
3296
|
}
|
|
2800
3297
|
function saveReport(report) {
|
|
2801
|
-
|
|
2802
|
-
|
|
3298
|
+
mkdirSync3(dirname6(reportPath()), { recursive: true });
|
|
3299
|
+
writeFileSync5(reportPath(), `${JSON.stringify(report, null, 2)}
|
|
2803
3300
|
`, { mode: 384 });
|
|
2804
3301
|
}
|
|
2805
3302
|
function confirm3(question) {
|
|
@@ -2886,7 +3383,7 @@ async function connectedProviders() {
|
|
|
2886
3383
|
var WORKSPACE_VAULT_NAME = "default";
|
|
2887
3384
|
async function findWorkspaceVault() {
|
|
2888
3385
|
const { vaults } = await api("GET", "/vaults");
|
|
2889
|
-
return vaults.find((
|
|
3386
|
+
return vaults.find((vault2) => vault2.display_name === WORKSPACE_VAULT_NAME)?.id ?? null;
|
|
2890
3387
|
}
|
|
2891
3388
|
async function workspaceVaultId() {
|
|
2892
3389
|
const existing = await findWorkspaceVault();
|
|
@@ -3148,17 +3645,17 @@ function renderArtifact(artifact2) {
|
|
|
3148
3645
|
}
|
|
3149
3646
|
|
|
3150
3647
|
// src/skills-store.ts
|
|
3151
|
-
import { readdirSync as
|
|
3152
|
-
import { basename as
|
|
3648
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync12, statSync as statSync8 } from "fs";
|
|
3649
|
+
import { basename as basename7, join as join12, relative as relative4, resolve as resolve7 } from "path";
|
|
3153
3650
|
function skillBundle(dir) {
|
|
3154
|
-
const root =
|
|
3155
|
-
const top =
|
|
3651
|
+
const root = resolve7(dir);
|
|
3652
|
+
const top = basename7(root);
|
|
3156
3653
|
const files2 = [];
|
|
3157
3654
|
const walk = (path) => {
|
|
3158
|
-
for (const name of
|
|
3159
|
-
const full =
|
|
3160
|
-
if (
|
|
3161
|
-
else files2.push({ path: `${top}/${
|
|
3655
|
+
for (const name of readdirSync5(path).sort()) {
|
|
3656
|
+
const full = join12(path, name);
|
|
3657
|
+
if (statSync8(full).isDirectory()) walk(full);
|
|
3658
|
+
else files2.push({ path: `${top}/${relative4(root, full)}`, bytes: readFileSync12(full) });
|
|
3162
3659
|
}
|
|
3163
3660
|
};
|
|
3164
3661
|
walk(root);
|
|
@@ -3168,7 +3665,7 @@ function skillBundle(dir) {
|
|
|
3168
3665
|
async function uploadSkill(dir, displayName) {
|
|
3169
3666
|
const form = new FormData();
|
|
3170
3667
|
for (const file of skillBundle(dir)) form.append("files", new Blob([new Uint8Array(file.bytes)]), file.path);
|
|
3171
|
-
form.append("displayName", displayName ??
|
|
3668
|
+
form.append("displayName", displayName ?? basename7(resolve7(dir)));
|
|
3172
3669
|
return apiForm("POST", "/skills", form);
|
|
3173
3670
|
}
|
|
3174
3671
|
async function listSkills() {
|
|
@@ -3192,6 +3689,7 @@ function renderRequest(request) {
|
|
|
3192
3689
|
}
|
|
3193
3690
|
|
|
3194
3691
|
// src/workspace.ts
|
|
3692
|
+
import { createInterface as createInterface4 } from "readline";
|
|
3195
3693
|
var KINDS = ["TOOL_CALL", "SPEND_CAP", "CALL_COUNT", "CAPABILITY"];
|
|
3196
3694
|
async function workspaceSlug(explicit) {
|
|
3197
3695
|
if (explicit) return explicit;
|
|
@@ -3199,9 +3697,9 @@ async function workspaceSlug(explicit) {
|
|
|
3199
3697
|
if (current) return current;
|
|
3200
3698
|
return (await api("GET", "/whoami")).universe.slug;
|
|
3201
3699
|
}
|
|
3202
|
-
var
|
|
3700
|
+
var workspacePath2 = (slug) => `/universes/${encodeURIComponent(slug)}`;
|
|
3203
3701
|
async function listMembers(slug) {
|
|
3204
|
-
return api("GET", `${
|
|
3702
|
+
return api("GET", `${workspacePath2(slug)}/members`);
|
|
3205
3703
|
}
|
|
3206
3704
|
function findMember(members, ref) {
|
|
3207
3705
|
const needle = ref.toLowerCase();
|
|
@@ -3220,20 +3718,20 @@ function inviteBody(emails, options) {
|
|
|
3220
3718
|
};
|
|
3221
3719
|
}
|
|
3222
3720
|
async function invite(slug, emails, options) {
|
|
3223
|
-
return api("POST", `${
|
|
3721
|
+
return api("POST", `${workspacePath2(slug)}/invites`, inviteBody(emails, options));
|
|
3224
3722
|
}
|
|
3225
3723
|
async function withdrawInvite(slug, inviteId) {
|
|
3226
|
-
await api("DELETE", `${
|
|
3724
|
+
await api("DELETE", `${workspacePath2(slug)}/invites/${encodeURIComponent(inviteId)}`);
|
|
3227
3725
|
}
|
|
3228
3726
|
async function setRole(slug, memberRef, role) {
|
|
3229
3727
|
if (role !== "admin" && role !== "member") throw new ApiError(400, "invalid_request", "a role is admin or member");
|
|
3230
3728
|
const member = findMember((await listMembers(slug)).members, memberRef);
|
|
3231
|
-
await api("PATCH", `${
|
|
3729
|
+
await api("PATCH", `${workspacePath2(slug)}/members/${encodeURIComponent(member.user_id)}`, { role });
|
|
3232
3730
|
return member;
|
|
3233
3731
|
}
|
|
3234
3732
|
async function removeMember(slug, memberRef) {
|
|
3235
3733
|
const member = findMember((await listMembers(slug)).members, memberRef);
|
|
3236
|
-
await api("DELETE", `${
|
|
3734
|
+
await api("DELETE", `${workspacePath2(slug)}/members/${encodeURIComponent(member.user_id)}`);
|
|
3237
3735
|
return member;
|
|
3238
3736
|
}
|
|
3239
3737
|
async function joinWorkspace(token) {
|
|
@@ -3266,7 +3764,7 @@ function policyRuleBody(options) {
|
|
|
3266
3764
|
...options.disabled ? { enabled: false } : {}
|
|
3267
3765
|
};
|
|
3268
3766
|
}
|
|
3269
|
-
var policiesPath = (scope) => "session" in scope ? `/sessions/${encodeURIComponent(scope.session)}/policies` : `${
|
|
3767
|
+
var policiesPath = (scope) => "session" in scope ? `/sessions/${encodeURIComponent(scope.session)}/policies` : `${workspacePath2(scope.slug)}/policies`;
|
|
3270
3768
|
async function listPolicies(scope, effective = false) {
|
|
3271
3769
|
if (effective && "session" in scope) return api("GET", `/sessions/${encodeURIComponent(scope.session)}/effective-policy`);
|
|
3272
3770
|
return api("GET", policiesPath(scope));
|
|
@@ -3284,21 +3782,145 @@ function renderPolicy(rule) {
|
|
|
3284
3782
|
const state = rule.enabled ? "on " : "off";
|
|
3285
3783
|
return `${state} ${rule.layer.padEnd(9)} ${rule.kind.padEnd(10)} ${rule.name} ${JSON.stringify(rule.config)} ${rule.id}`;
|
|
3286
3784
|
}
|
|
3785
|
+
var REMOVED_KINDS = [
|
|
3786
|
+
["sessions", "sessions and their transcripts"],
|
|
3787
|
+
["agents", "agents"],
|
|
3788
|
+
["artifacts", "artifacts"],
|
|
3789
|
+
["files", "task files"],
|
|
3790
|
+
["memory_stores", "memory stores"],
|
|
3791
|
+
["routines", "routines"],
|
|
3792
|
+
["channels", "channel connections"],
|
|
3793
|
+
["webhooks", "webhooks"],
|
|
3794
|
+
["api_keys", "API keys"],
|
|
3795
|
+
["vaults", "vaults"],
|
|
3796
|
+
["skills", "skills"],
|
|
3797
|
+
["environments", "environments"],
|
|
3798
|
+
["connections", "connected apps"],
|
|
3799
|
+
["traces", "uploaded traces"],
|
|
3800
|
+
["members", "other members"],
|
|
3801
|
+
["invites", "pending invitations"]
|
|
3802
|
+
];
|
|
3803
|
+
async function deletionPreview(slug) {
|
|
3804
|
+
return api("GET", `/universes/${encodeURIComponent(slug)}/deletion`);
|
|
3805
|
+
}
|
|
3806
|
+
function renderDeletionPreview(preview) {
|
|
3807
|
+
if (!preview.can_delete) return [`${preview.workspace.slug} cannot be deleted:`, ...preview.blockers.map((b) => ` ${b.message}`)];
|
|
3808
|
+
const lines = [`Deleting ${preview.workspace.slug} (${preview.workspace.name}) permanently removes:`];
|
|
3809
|
+
const removed = REMOVED_KINDS.filter(([key]) => (preview.removes[key] ?? 0) > 0);
|
|
3810
|
+
for (const [key, label] of removed) lines.push(` ${preview.removes[key]} ${label}`);
|
|
3811
|
+
if (!removed.length) lines.push(" nothing: the workspace is empty");
|
|
3812
|
+
const running = preview.removes.running_sessions ?? 0;
|
|
3813
|
+
if (running) lines.push(`${running} running session${running === 1 ? " is" : "s are"} stopped first.`);
|
|
3814
|
+
if (preview.wallet) lines.push(`$${(preview.wallet.balance_micros / 1e6).toFixed(2)} in its wallet goes back to ${preview.wallet.returns_to ?? "you"}.`);
|
|
3815
|
+
lines.push("This cannot be undone.");
|
|
3816
|
+
return lines;
|
|
3817
|
+
}
|
|
3818
|
+
async function deleteWorkspace(slug) {
|
|
3819
|
+
return (await api("DELETE", `/universes/${encodeURIComponent(slug)}`)).deleted;
|
|
3820
|
+
}
|
|
3821
|
+
function typedConfirmation(slug) {
|
|
3822
|
+
return new Promise((done) => {
|
|
3823
|
+
const rl = createInterface4({ input: process.stdin, output: process.stdout });
|
|
3824
|
+
rl.question(`Type ${slug} to delete it: `, (answer) => {
|
|
3825
|
+
rl.close();
|
|
3826
|
+
done(answer.trim() === slug);
|
|
3827
|
+
});
|
|
3828
|
+
});
|
|
3829
|
+
}
|
|
3830
|
+
|
|
3831
|
+
// src/vaults.ts
|
|
3832
|
+
import { createInterface as createInterface5 } from "readline";
|
|
3833
|
+
var vaultPath = (id) => `/vaults/${encodeURIComponent(id)}`;
|
|
3834
|
+
function secretLine(s) {
|
|
3835
|
+
const target = s.secret_name ? `$${s.secret_name}` : s.mcp_server_url ?? "";
|
|
3836
|
+
return `${s.id} ${s.auth_type.padEnd(20)} ${s.display_name} ${target} ${s.status}${s.expires_at ? ` expires ${s.expires_at.slice(0, 10)}` : ""}`;
|
|
3837
|
+
}
|
|
3838
|
+
async function readSecretValue(prompt, input = process.stdin) {
|
|
3839
|
+
if (!input.isTTY) {
|
|
3840
|
+
const chunks = [];
|
|
3841
|
+
for await (const chunk of input) chunks.push(chunk);
|
|
3842
|
+
return Buffer.concat(chunks).toString("utf8").replace(/\r?\n$/, "");
|
|
3843
|
+
}
|
|
3844
|
+
const rl = createInterface5({ input, output: process.stdout, terminal: true });
|
|
3845
|
+
const write = rl._writeToOutput.bind(rl);
|
|
3846
|
+
let prompted = false;
|
|
3847
|
+
rl._writeToOutput = (s) => {
|
|
3848
|
+
if (!prompted) {
|
|
3849
|
+
prompted = true;
|
|
3850
|
+
write(s);
|
|
3851
|
+
}
|
|
3852
|
+
};
|
|
3853
|
+
const value = await new Promise((done) => rl.question(prompt, done));
|
|
3854
|
+
rl.close();
|
|
3855
|
+
process.stdout.write("\n");
|
|
3856
|
+
return value;
|
|
3857
|
+
}
|
|
3858
|
+
function secretBody(input) {
|
|
3859
|
+
if (input.kind === "mcp") {
|
|
3860
|
+
return { display_name: input.displayName ?? new URL(input.url).host, auth: { type: "static_bearer", mcp_server_url: input.url, token: input.token } };
|
|
3861
|
+
}
|
|
3862
|
+
if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(input.name)) {
|
|
3863
|
+
throw new ApiError(400, "invalid_name", `"${input.name}" is not an environment variable name`);
|
|
3864
|
+
}
|
|
3865
|
+
if (!input.hosts.length) throw new ApiError(400, "no_hosts", "name at least one --host the secret may be sent to");
|
|
3866
|
+
const injection = {
|
|
3867
|
+
header: true,
|
|
3868
|
+
body: false,
|
|
3869
|
+
...input.header ? { header_name: input.header } : {},
|
|
3870
|
+
...input.template ? { value_template: input.template } : {}
|
|
3871
|
+
};
|
|
3872
|
+
return {
|
|
3873
|
+
display_name: input.displayName ?? input.name,
|
|
3874
|
+
auth: { type: "environment_variable", secret_name: input.name, value: input.value, allowed_hosts: input.hosts, injection_location: injection }
|
|
3875
|
+
};
|
|
3876
|
+
}
|
|
3877
|
+
async function listVaults() {
|
|
3878
|
+
return (await api("GET", "/vaults")).vaults;
|
|
3879
|
+
}
|
|
3880
|
+
async function listSecrets(vaultId) {
|
|
3881
|
+
return (await api("GET", `${vaultPath(vaultId)}/secrets`)).secrets;
|
|
3882
|
+
}
|
|
3883
|
+
async function addSecret(vaultId, input) {
|
|
3884
|
+
return (await api("POST", `${vaultPath(vaultId)}/secrets`, secretBody(input))).secret;
|
|
3885
|
+
}
|
|
3886
|
+
async function updateSecret(vaultId, secretId, patch) {
|
|
3887
|
+
const body = {
|
|
3888
|
+
...patch.value !== void 0 ? { value: patch.value } : {},
|
|
3889
|
+
...patch.token !== void 0 ? { token: patch.token } : {},
|
|
3890
|
+
...patch.hosts?.length ? { allowed_hosts: patch.hosts } : {},
|
|
3891
|
+
...patch.displayName ? { display_name: patch.displayName } : {}
|
|
3892
|
+
};
|
|
3893
|
+
if (!Object.keys(body).length) throw new ApiError(400, "nothing_to_change", "pass a new value, --host or --name");
|
|
3894
|
+
return (await api("PATCH", `${vaultPath(vaultId)}/secrets/${encodeURIComponent(secretId)}`, body)).secret;
|
|
3895
|
+
}
|
|
3896
|
+
async function moveSecrets(intoVault, from, dryRun) {
|
|
3897
|
+
const result = await api("POST", `${vaultPath(intoVault)}/secrets/move`, { from, ...dryRun ? { dry_run: true } : {} });
|
|
3898
|
+
return result.secrets;
|
|
3899
|
+
}
|
|
3900
|
+
function confirmPrompt(question) {
|
|
3901
|
+
return new Promise((done) => {
|
|
3902
|
+
const rl = createInterface5({ input: process.stdin, output: process.stdout });
|
|
3903
|
+
rl.question(`${question} [y/N] `, (answer) => {
|
|
3904
|
+
rl.close();
|
|
3905
|
+
done(/^y(es)?$/i.test(answer.trim()));
|
|
3906
|
+
});
|
|
3907
|
+
});
|
|
3908
|
+
}
|
|
3287
3909
|
|
|
3288
3910
|
// src/harness.ts
|
|
3289
|
-
import { existsSync as existsSync9, mkdirSync as
|
|
3290
|
-
import { dirname as
|
|
3911
|
+
import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync6 } from "fs";
|
|
3912
|
+
import { dirname as dirname7, join as join13, resolve as resolve8 } from "path";
|
|
3291
3913
|
var HARNESS_FILE = "harness.json";
|
|
3292
3914
|
function harnessFilePath(dir) {
|
|
3293
|
-
return
|
|
3915
|
+
return join13(resolve8(dir), HARNESS_FILE);
|
|
3294
3916
|
}
|
|
3295
3917
|
function readHarnessFile(dir) {
|
|
3296
3918
|
const path = harnessFilePath(dir);
|
|
3297
3919
|
if (!existsSync9(path)) {
|
|
3298
|
-
throw new Error(`no ${HARNESS_FILE} found in ${
|
|
3920
|
+
throw new Error(`no ${HARNESS_FILE} found in ${resolve8(dir)} \u2014 run \`sky harness init\` first`);
|
|
3299
3921
|
}
|
|
3300
3922
|
try {
|
|
3301
|
-
return JSON.parse(
|
|
3923
|
+
return JSON.parse(readFileSync13(path, "utf8"));
|
|
3302
3924
|
} catch (err) {
|
|
3303
3925
|
throw new Error(`could not parse ${path}: ${err instanceof Error ? err.message : String(err)}`);
|
|
3304
3926
|
}
|
|
@@ -3328,8 +3950,8 @@ function harnessInit(dir) {
|
|
|
3328
3950
|
if (existsSync9(path)) {
|
|
3329
3951
|
throw new Error(`${path} already exists \u2014 delete it to re-init`);
|
|
3330
3952
|
}
|
|
3331
|
-
|
|
3332
|
-
|
|
3953
|
+
mkdirSync4(dirname7(path), { recursive: true });
|
|
3954
|
+
writeFileSync6(path, `${JSON.stringify(INIT_SKELETON, null, 2)}
|
|
3333
3955
|
`);
|
|
3334
3956
|
console.log(`Created ${path}`);
|
|
3335
3957
|
console.log(INIT_COMMENT);
|
|
@@ -3466,6 +4088,18 @@ program.hook("preAction", () => {
|
|
|
3466
4088
|
const workspace2 = program.opts().workspace;
|
|
3467
4089
|
if (workspace2) process.env.SKY_WORKSPACE = workspace2;
|
|
3468
4090
|
});
|
|
4091
|
+
var lastProgressDecile = -1;
|
|
4092
|
+
function uploadProgress(sent, total) {
|
|
4093
|
+
const percent = Math.floor(sent / total * 100);
|
|
4094
|
+
const line = `uploading ${(sent / 1024 / 1024).toFixed(0)} / ${(total / 1024 / 1024).toFixed(0)} MiB (${percent}%)`;
|
|
4095
|
+
if (process.stderr.isTTY) {
|
|
4096
|
+
process.stderr.write(`\r${line}${sent >= total ? "\n" : ""}`);
|
|
4097
|
+
} else if (Math.floor(percent / 10) !== lastProgressDecile) {
|
|
4098
|
+
lastProgressDecile = Math.floor(percent / 10);
|
|
4099
|
+
process.stderr.write(`${line}
|
|
4100
|
+
`);
|
|
4101
|
+
}
|
|
4102
|
+
}
|
|
3469
4103
|
function fail(error) {
|
|
3470
4104
|
if (error instanceof ApiError) console.error(`sky: ${error.message} (${error.code})`);
|
|
3471
4105
|
else console.error(`sky: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -3488,13 +4122,26 @@ auth.command("whoami").description("who the current token acts as").action(
|
|
|
3488
4122
|
);
|
|
3489
4123
|
})
|
|
3490
4124
|
);
|
|
3491
|
-
auth.command("connect <provider>").description("connect a model subscription: claude (setup-token) or chatgpt (Codex sign-in)").option("-y, --yes", "skip the consent prompt").action(
|
|
4125
|
+
auth.command("connect <provider>").description("connect a model subscription: claude (setup-token) or chatgpt (Codex sign-in); --browser signs any plan in on its own page").option("-y, --yes", "skip the consent prompt").option("--browser", "sign in on the provider's page instead of using this machine's sign-in (claude, chatgpt, cursor, grok, muse)").option("--account <id>", "with --browser: re-sign an additional account, or `new` to add one").option("--label <label>", "with --browser: name the account").option("--no-open", "with --browser: print the sign-in link without opening it").action(
|
|
3492
4126
|
run(async (name, opts) => {
|
|
4127
|
+
if (opts.browser) {
|
|
4128
|
+
const sub = await browserSignIn(name, { account: opts.account, label: opts.label, browser: opts.open });
|
|
4129
|
+
console.log(`Connected ${MODEL_SUBSCRIPTION_PROVIDER_NAME[sub.provider]} (${sub.label}). Powers new eligible agents.`);
|
|
4130
|
+
return;
|
|
4131
|
+
}
|
|
3493
4132
|
const provider = providerFor(name);
|
|
3494
4133
|
if (provider === "anthropic") await connectClaude();
|
|
3495
4134
|
else await connectChatgpt(opts);
|
|
3496
4135
|
})
|
|
3497
4136
|
);
|
|
4137
|
+
auth.command("usage <provider>").description("plan allowance left on a connected model subscription").option("--account <id>", "an additional account (default: the provider's first account)").option("--json", "print the raw usage").action(
|
|
4138
|
+
run(async (name, opts) => {
|
|
4139
|
+
const usage = await subscriptionUsage(name, opts.account);
|
|
4140
|
+
if (opts.json) console.log(JSON.stringify(usage, null, 2));
|
|
4141
|
+
else for (const line of renderSubscriptionUsage(name, usage)) console.log(line);
|
|
4142
|
+
})
|
|
4143
|
+
);
|
|
4144
|
+
auth.command("switch-to-credits <provider>").description("move the agents on a model subscription onto platform credits (do this before disconnecting)").option("--account <id>", "an additional account (default: the provider's first account)").option("-y, --yes", "skip the confirmation prompt").action(run(async (name, opts) => switchToCredits(name, opts)));
|
|
3498
4145
|
auth.command("accounts").description("list connected model subscriptions and the agents they power").action(run(async () => listSubscriptions()));
|
|
3499
4146
|
auth.command("disconnect <provider>").description("disconnect a model subscription (agents on it fail until switched)").option("-y, --yes", "skip the confirmation prompt").action(run(async (name, opts) => disconnect(name, opts)));
|
|
3500
4147
|
var universe = program.command("universe").description("grouping boundary for agents");
|
|
@@ -3565,6 +4212,19 @@ universe.command("remove <member>").description("remove a member (user id or ema
|
|
|
3565
4212
|
console.log(`Removed ${member.email ?? member.user_id}`);
|
|
3566
4213
|
})
|
|
3567
4214
|
);
|
|
4215
|
+
async function deleteWorkspaceCommand(slug, opts) {
|
|
4216
|
+
const preview = await deletionPreview(slug);
|
|
4217
|
+
for (const line of renderDeletionPreview(preview)) console.log(line);
|
|
4218
|
+
if (!preview.can_delete) process.exit(1);
|
|
4219
|
+
if (!opts.yes && !await typedConfirmation(slug)) {
|
|
4220
|
+
console.log("Aborted.");
|
|
4221
|
+
return;
|
|
4222
|
+
}
|
|
4223
|
+
const deleted = await deleteWorkspace(slug);
|
|
4224
|
+
if (currentUniverse() === slug) saveConfig({ ...loadConfig(), currentUniverse: void 0 });
|
|
4225
|
+
console.log(`Deleted ${deleted.slug}${deleted.sessions_stopped ? ` \xB7 stopped ${deleted.sessions_stopped} running session${deleted.sessions_stopped === 1 ? "" : "s"}` : ""}`);
|
|
4226
|
+
}
|
|
4227
|
+
universe.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
|
|
3568
4228
|
universe.command("join <token>").description("accept an invitation sent to your verified email").action(
|
|
3569
4229
|
run(async (token) => {
|
|
3570
4230
|
const workspace2 = await joinWorkspace(token);
|
|
@@ -3582,6 +4242,7 @@ workspace.command("list").action(
|
|
|
3582
4242
|
}
|
|
3583
4243
|
})
|
|
3584
4244
|
);
|
|
4245
|
+
workspace.command("delete <slug>").description("permanently delete a workspace you own and everything in it (asks you to type the slug)").option("-y, --yes", "skip typing the slug").action(run(deleteWorkspaceCommand));
|
|
3585
4246
|
var policy = program.command("policy").description("policy rules: tool calls, spend caps, call counts, capabilities");
|
|
3586
4247
|
async function policyScope(opts) {
|
|
3587
4248
|
return opts.session ? { session: await resolveSessionRef(opts.session) } : { slug: await workspaceSlug(opts.universe) };
|
|
@@ -3692,7 +4353,7 @@ agent.command("destroy <id>").description("delete the spec; requires all session
|
|
|
3692
4353
|
console.log(`Destroyed ${id}`);
|
|
3693
4354
|
})
|
|
3694
4355
|
);
|
|
3695
|
-
agent.command("message <id> [text]").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").action(
|
|
4356
|
+
agent.command("message <id> [text]").description("send one message to the default session and stream the turn until idle").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
|
|
3696
4357
|
run(
|
|
3697
4358
|
async (id, text, opts) => sendAndFollow(await resolveSessionRef(id), text ?? "", opts)
|
|
3698
4359
|
)
|
|
@@ -3802,7 +4463,7 @@ session.command("delete <id>").description("permanently delete the session, its
|
|
|
3802
4463
|
console.log(`Deleted ${id}`);
|
|
3803
4464
|
})
|
|
3804
4465
|
);
|
|
3805
|
-
session.command("message <ref> [text]").description("send one message (session id, or agent id = its default session)").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").action(
|
|
4466
|
+
session.command("message <ref> [text]").description("send one message (session id, or agent id = its default session)").option("--detach", "don't follow the stream; exit once the message is accepted").option("--file <path...>", "attach local files (uploaded first, sent as document blocks)").option("--image <path...>", "attach local images (uploaded first, sent as image blocks)").option("--system <text>", "per-turn system text sent alongside the message").option("--plan", "plan mode: the agent inspects and answers with a plan, then waits for you to choose to implement it").option("--build", "build mode for this turn, overriding a session left in plan mode").action(
|
|
3806
4467
|
run(
|
|
3807
4468
|
async (ref, text, opts) => sendAndFollow(await resolveSessionRef(ref), text ?? "", opts)
|
|
3808
4469
|
)
|
|
@@ -3944,6 +4605,78 @@ sessionPorts.command("revoke <ref> <port>").description("stop exposing a port; i
|
|
|
3944
4605
|
console.log(`Revoked port ${port}`);
|
|
3945
4606
|
})
|
|
3946
4607
|
);
|
|
4608
|
+
session.command("access <ref>").description("who may use the session; --private/--members and --add-viewer/--remove-viewer change it (owner)").option("--private", "only you can use it").option("--members", "every member of its workspace can read and prompt it").option("--add-viewer <email...>", "people who may read its messages from any workspace").option("--remove-viewer <email...>", "take viewers off").action(
|
|
4609
|
+
run(async (ref, opts) => {
|
|
4610
|
+
const id = await resolveSessionRef(ref);
|
|
4611
|
+
const patch = accessPatch(opts);
|
|
4612
|
+
const path = `/sessions/${encodeURIComponent(id)}/access`;
|
|
4613
|
+
const access = await api(Object.keys(patch).length ? "PATCH" : "GET", path, Object.keys(patch).length ? patch : void 0);
|
|
4614
|
+
for (const line of renderAccess(access)) console.log(line);
|
|
4615
|
+
})
|
|
4616
|
+
);
|
|
4617
|
+
session.command("shared").description("sessions other people shared with you to read, newest first").option("--limit <n>", "at most this many (1\u2013100, default 50)").action(
|
|
4618
|
+
run(async (opts) => {
|
|
4619
|
+
const { sessions } = await api("GET", `/shared-sessions${opts.limit ? `?limit=${encodeURIComponent(opts.limit)}` : ""}`);
|
|
4620
|
+
if (!sessions.length) console.log("nobody has shared a session with you");
|
|
4621
|
+
for (const s of sessions) console.log(`${s.id} ${s.agent_type.padEnd(12)} ${s.title ?? "(untitled)"} from ${s.owner.name ?? s.owner.email ?? "unknown"} ${s.shared_at.slice(0, 10)}`);
|
|
4622
|
+
})
|
|
4623
|
+
);
|
|
4624
|
+
session.command("handoff <ref> <targetAgent>").description("hand the session's context to another agent (agent id or catalog handle); prints the new session").option("--from-message <id>", "carry the conversation up to this message only").option("--no-wait", "return once the handoff is accepted").action(
|
|
4625
|
+
run(async (ref, targetAgent, opts) => {
|
|
4626
|
+
const id = await resolveSessionRef(ref);
|
|
4627
|
+
const done = await handoff(id, targetAgent, {
|
|
4628
|
+
fromMessage: opts.fromMessage,
|
|
4629
|
+
wait: opts.wait !== false,
|
|
4630
|
+
onStatus: (h) => console.error(`${h.id}: ${h.status}`)
|
|
4631
|
+
});
|
|
4632
|
+
if (done.status === "failed") throw new ApiError(422, done.error?.code ?? "handoff_failed", done.error?.message ?? "the handoff failed");
|
|
4633
|
+
if (done.target_session) console.log(`${done.target_session} (${done.resolved_mode === "native_fork" ? "forked" : "from a written summary"})`);
|
|
4634
|
+
})
|
|
4635
|
+
);
|
|
4636
|
+
var sessionWorkspace = session.command("workspace").description("the session's workspace: list and read files, see changes, take a patch, copy a folder in");
|
|
4637
|
+
sessionWorkspace.command("ls <ref> [path]").description("list a directory (default: the workspace root)").option("--hidden", "include dotfiles").action(
|
|
4638
|
+
run(async (ref, path, opts) => {
|
|
4639
|
+
const listing = await listFiles(await resolveSessionRef(ref), path, Boolean(opts.hidden));
|
|
4640
|
+
for (const e of listing.entries) console.log(entryLine(e));
|
|
4641
|
+
if (listing.truncated) console.error(`(only the first ${listing.entries.length} entries)`);
|
|
4642
|
+
})
|
|
4643
|
+
);
|
|
4644
|
+
sessionWorkspace.command("cat <ref> <path>").description("print a file (up to 1 MB), or save it with --out").option("--out <file>", "write it to this local file").action(
|
|
4645
|
+
run(async (ref, path, opts) => {
|
|
4646
|
+
const bytes = await readFile(await resolveSessionRef(ref), path);
|
|
4647
|
+
if (opts.out) {
|
|
4648
|
+
writeFileSync7(opts.out, bytes);
|
|
4649
|
+
console.error(`saved ${bytes.length} bytes to ${opts.out}`);
|
|
4650
|
+
} else process.stdout.write(bytes);
|
|
4651
|
+
})
|
|
4652
|
+
);
|
|
4653
|
+
sessionWorkspace.command("changes <ref>").description("git status and diff summary of each repository in the workspace").action(
|
|
4654
|
+
run(async (ref) => {
|
|
4655
|
+
const changes = await api("GET", `/sessions/${encodeURIComponent(await resolveSessionRef(ref))}/workspace/changes`);
|
|
4656
|
+
for (const line of renderChanges(changes)) console.log(line);
|
|
4657
|
+
})
|
|
4658
|
+
);
|
|
4659
|
+
sessionWorkspace.command("patch <ref> [repo]").description("the work in a repository since its base branch, as a git patch (default repo: the root)").option("--out <file>", "write the patch to this file instead of stdout").action(
|
|
4660
|
+
run(async (ref, repo, opts) => {
|
|
4661
|
+
const { meta, patch } = await workspacePatch(await resolveSessionRef(ref), repo ?? ".");
|
|
4662
|
+
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)})`);
|
|
4663
|
+
if (!patch.length) console.error("nothing changed");
|
|
4664
|
+
else if (opts.out) {
|
|
4665
|
+
writeFileSync7(opts.out, patch);
|
|
4666
|
+
console.error(`wrote ${opts.out} \u2014 apply with: git apply ${opts.out}`);
|
|
4667
|
+
} else process.stdout.write(patch);
|
|
4668
|
+
})
|
|
4669
|
+
);
|
|
4670
|
+
sessionWorkspace.command("import <ref> <dir>").description("copy a local folder into the workspace as a git repository (files git ignores are left out)").option("--name <name>", "its folder name in the workspace (default: the local folder's name)").action(
|
|
4671
|
+
run(async (ref, dir, opts) => {
|
|
4672
|
+
const imported = await importFolder(await resolveSessionRef(ref), dir, { name: opts.name });
|
|
4673
|
+
console.log(`${imported.path} (${imported.files} files)`);
|
|
4674
|
+
})
|
|
4675
|
+
);
|
|
4676
|
+
function interactionMode(opts) {
|
|
4677
|
+
if (opts.plan && opts.build) throw new ApiError(400, "invalid_request", "pass --plan or --build, not both");
|
|
4678
|
+
return opts.plan ? "plan" : opts.build ? "build" : void 0;
|
|
4679
|
+
}
|
|
3947
4680
|
async function uploadAttachments(opts) {
|
|
3948
4681
|
const files2 = [];
|
|
3949
4682
|
for (const path of [...opts.image ?? [], ...opts.file ?? []]) {
|
|
@@ -3960,7 +4693,8 @@ function turnOver(frame) {
|
|
|
3960
4693
|
return stopReason?.type !== "requires_action";
|
|
3961
4694
|
}
|
|
3962
4695
|
async function sendAndFollow(sessionId, text, opts) {
|
|
3963
|
-
const
|
|
4696
|
+
const mode = interactionMode(opts);
|
|
4697
|
+
const events = eventsFor(text, await uploadAttachments(opts), opts.system, mode);
|
|
3964
4698
|
const controller = new AbortController();
|
|
3965
4699
|
let opened = null;
|
|
3966
4700
|
if (!opts.detach) {
|
|
@@ -3985,7 +4719,7 @@ async function sendAndFollow(sessionId, text, opts) {
|
|
|
3985
4719
|
async function chatLoop(sessionId, opts = {}) {
|
|
3986
4720
|
console.log(`Chatting with ${sessionId} \u2014 Ctrl-C to leave.`);
|
|
3987
4721
|
const controller = new AbortController();
|
|
3988
|
-
const rl =
|
|
4722
|
+
const rl = createInterface6({ input: process.stdin, output: process.stdout, prompt: "> " });
|
|
3989
4723
|
let system = opts.system;
|
|
3990
4724
|
void (async () => {
|
|
3991
4725
|
try {
|
|
@@ -4099,9 +4833,9 @@ secrets.command("unset <id> <key>").action(
|
|
|
4099
4833
|
);
|
|
4100
4834
|
program.command("launch").description("scaffold ./agent.toml if absent, create the agent, print its address").option("-f, --file <path>", "agent file", "agent.toml").option("--type <type>", "override the file's harness: hermes | claude_code | codex | openclaw | pi | dsh | kimi_code | opencode").option("--llm <llm>", "override the file's model (must suit the harness)").action(
|
|
4101
4835
|
run(async (opts) => {
|
|
4102
|
-
const path =
|
|
4836
|
+
const path = resolve9(opts.file);
|
|
4103
4837
|
if (!existsSync10(path)) {
|
|
4104
|
-
const name =
|
|
4838
|
+
const name = basename8(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
4105
4839
|
scaffoldAgentFile(path, name);
|
|
4106
4840
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
4107
4841
|
}
|
|
@@ -4132,7 +4866,7 @@ program.command("launch").description("scaffold ./agent.toml if absent, create t
|
|
|
4132
4866
|
);
|
|
4133
4867
|
program.command("deploy").description("re-apply ./agent.toml to the existing agent (idempotent)").option("-f, --file <path>", "agent file", "agent.toml").action(
|
|
4134
4868
|
run(async (opts) => {
|
|
4135
|
-
const path =
|
|
4869
|
+
const path = resolve9(opts.file);
|
|
4136
4870
|
const { file, spec } = readAgentFile(path);
|
|
4137
4871
|
if (!file.id) {
|
|
4138
4872
|
console.log("No agent id in the file yet \u2014 running launch instead.");
|
|
@@ -4233,13 +4967,13 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
4233
4967
|
console.log(` ${ws}`);
|
|
4234
4968
|
});
|
|
4235
4969
|
console.log();
|
|
4236
|
-
const rl =
|
|
4970
|
+
const rl = createInterface6({ input: process.stdin, output: process.stdout });
|
|
4237
4971
|
const answer = await new Promise(
|
|
4238
|
-
(
|
|
4972
|
+
(resolve10) => rl.question(
|
|
4239
4973
|
`Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
|
|
4240
4974
|
(a) => {
|
|
4241
4975
|
rl.close();
|
|
4242
|
-
|
|
4976
|
+
resolve10(a.trim());
|
|
4243
4977
|
}
|
|
4244
4978
|
)
|
|
4245
4979
|
);
|
|
@@ -4261,10 +4995,10 @@ program.command("import").description("import a local Claude Code or Codex sessi
|
|
|
4261
4995
|
}
|
|
4262
4996
|
if (!autoConfirm) {
|
|
4263
4997
|
if (nonInteractive) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
4264
|
-
const prompt =
|
|
4265
|
-
const answer = await new Promise((
|
|
4998
|
+
const prompt = createInterface6({ input: process.stdin, output: process.stdout });
|
|
4999
|
+
const answer = await new Promise((resolve10) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
4266
5000
|
prompt.close();
|
|
4267
|
-
|
|
5001
|
+
resolve10(value);
|
|
4268
5002
|
}));
|
|
4269
5003
|
if (!/^y(?:es)?$/i.test(answer.trim())) {
|
|
4270
5004
|
console.log("Cancelled. Nothing uploaded.");
|
|
@@ -4318,7 +5052,7 @@ Importing ${label} \u2026
|
|
|
4318
5052
|
var files = program.command("files").description("files: upload once, reference by id from any message");
|
|
4319
5053
|
files.command("upload <path>").description("upload a local file; prints the file id to reference from a message").option("--expires <seconds>", "expire the upload after this many seconds (3600\u20137776000)").action(
|
|
4320
5054
|
run(async (path, opts) => {
|
|
4321
|
-
const file = await uploadLocalFile(path, opts.expires ? Number(opts.expires) : void 0);
|
|
5055
|
+
const file = await uploadLocalFile(path, opts.expires ? Number(opts.expires) : void 0, uploadProgress);
|
|
4322
5056
|
console.log(`${file.id} ${file.filename} ${file.mime_type} ${file.size_bytes} bytes`);
|
|
4323
5057
|
})
|
|
4324
5058
|
);
|
|
@@ -4349,9 +5083,8 @@ files.command("rm <id>").description("delete a file (a session that already rece
|
|
|
4349
5083
|
);
|
|
4350
5084
|
files.command("download <id>").description("download a session file's bytes").requiredOption("-o, --out <path>", "where to write the bytes").action(
|
|
4351
5085
|
run(async (id, opts) => {
|
|
4352
|
-
const
|
|
4353
|
-
|
|
4354
|
-
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
5086
|
+
const written = await downloadFileTo(id, opts.out);
|
|
5087
|
+
console.log(`Wrote ${written} bytes to ${opts.out}`);
|
|
4355
5088
|
})
|
|
4356
5089
|
);
|
|
4357
5090
|
var task = program.command("task").description("a task's shared files and open requests (the task is its first session's id)");
|
|
@@ -4366,14 +5099,14 @@ task.command("get <task> <path>").description("read one task file; prints it, or
|
|
|
4366
5099
|
run(async (taskId, path, opts) => {
|
|
4367
5100
|
const bytes = await readTaskFile(taskId, path);
|
|
4368
5101
|
if (opts.out) {
|
|
4369
|
-
|
|
5102
|
+
writeFileSync7(opts.out, bytes);
|
|
4370
5103
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
4371
5104
|
} else process.stdout.write(bytes);
|
|
4372
5105
|
})
|
|
4373
5106
|
);
|
|
4374
5107
|
task.command("put <task> <path> <local>").description("write a local file to <path> in the task's files (every session of the task sees it)").action(
|
|
4375
5108
|
run(async (taskId, path, local) => {
|
|
4376
|
-
const { file, conflict } = await writeTaskFile(taskId, path,
|
|
5109
|
+
const { file, conflict } = await writeTaskFile(taskId, path, readFileSync14(local), mediaTypeFor(local));
|
|
4377
5110
|
console.log(`${renderTaskFile(file)}${conflict ? " (replaced a version you had not seen)" : ""}`);
|
|
4378
5111
|
})
|
|
4379
5112
|
);
|
|
@@ -4439,7 +5172,7 @@ artifact.command("get <id> <path>").description("read one file of an artifact; p
|
|
|
4439
5172
|
run(async (id, path, opts) => {
|
|
4440
5173
|
const bytes = await readArtifactFile(id, path);
|
|
4441
5174
|
if (opts.out) {
|
|
4442
|
-
|
|
5175
|
+
writeFileSync7(opts.out, bytes);
|
|
4443
5176
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
4444
5177
|
} else process.stdout.write(bytes);
|
|
4445
5178
|
})
|
|
@@ -4551,6 +5284,50 @@ routine.command("runs [id]").description("every fire attempt, newest first \u201
|
|
|
4551
5284
|
}
|
|
4552
5285
|
})
|
|
4553
5286
|
);
|
|
5287
|
+
routine.command("inbound-url <id>").description("a URL that starts a run when posted to, for services that can't hold an API token").option("--create", "make one (replaces any earlier URL); the URL is printed once").option("--rm", "remove it; posting to it stops working").action(
|
|
5288
|
+
run(async (id, opts) => {
|
|
5289
|
+
const res = await triggerRequest(id, "inbound-url", { create: opts.create, remove: opts.rm });
|
|
5290
|
+
console.log(opts.rm ? res.removed ? "Removed the inbound URL" : "There was no inbound URL" : inboundLine(res.inbound_url ?? null));
|
|
5291
|
+
})
|
|
5292
|
+
);
|
|
5293
|
+
routine.command("precheck <id> [url]").description("run scheduled fires only when the content at an https URL changed; no url shows the precheck").option("--rm", "remove it: every scheduled fire runs again").action(
|
|
5294
|
+
run(async (id, url, opts) => {
|
|
5295
|
+
const res = await triggerRequest(id, "precheck", { body: url ? { url } : void 0, remove: opts.rm });
|
|
5296
|
+
console.log(opts.rm ? res.removed ? "Removed the precheck" : "There was no precheck" : precheckLine(res.precheck ?? null));
|
|
5297
|
+
})
|
|
5298
|
+
);
|
|
5299
|
+
routine.command("result-email <id> [state]").description("email you each run's result: on or off; no state shows the setting (new-session routines only)").action(
|
|
5300
|
+
run(async (id, state) => {
|
|
5301
|
+
if (state && state !== "on" && state !== "off") throw new ApiError(400, "invalid_request", "state is on or off");
|
|
5302
|
+
const res = await api(
|
|
5303
|
+
state === "on" ? "PUT" : state === "off" ? "DELETE" : "GET",
|
|
5304
|
+
triggerPath(id, "result-email")
|
|
5305
|
+
);
|
|
5306
|
+
console.log(`result email ${res.result_email.enabled ? "on" : "off"}`);
|
|
5307
|
+
})
|
|
5308
|
+
);
|
|
5309
|
+
routine.command("chat-trigger <id>").description("start a run from your own message in a connected chat; no flags shows the trigger").option("--connection <id>", "the chat connection (see `sky channel list`)").option("--thread <id>", "only this conversation (default: every conversation on the connection)").option("--prefix <text>", "only messages starting with this; the rest of the message is the run's input").option("--rm", "remove the trigger").action(
|
|
5310
|
+
run(async (id, opts) => {
|
|
5311
|
+
const setting = Boolean(opts.connection || opts.thread || opts.prefix);
|
|
5312
|
+
const res = await triggerRequest(
|
|
5313
|
+
id,
|
|
5314
|
+
"channel-trigger",
|
|
5315
|
+
{ body: setting ? triggerBody(opts) : void 0, remove: opts.rm }
|
|
5316
|
+
);
|
|
5317
|
+
console.log(opts.rm ? res.removed ? "Removed the chat trigger" : "There was no chat trigger" : triggerLine(res.channel_trigger ?? null, res.available ?? true));
|
|
5318
|
+
})
|
|
5319
|
+
);
|
|
5320
|
+
routine.command("deliver-to <id>").description("post each run's answer to a conversation in a connected chat; no flags shows where").option("--connection <id>", "the chat connection (see `sky channel list`)").option("--thread <id>", "the conversation (default: the chat itself)").option("--rm", "stop posting answers").action(
|
|
5321
|
+
run(async (id, opts) => {
|
|
5322
|
+
const setting = Boolean(opts.connection || opts.thread);
|
|
5323
|
+
const res = await triggerRequest(
|
|
5324
|
+
id,
|
|
5325
|
+
"deliver-to",
|
|
5326
|
+
{ body: setting ? deliverBody(opts) : void 0, remove: opts.rm }
|
|
5327
|
+
);
|
|
5328
|
+
console.log(opts.rm ? res.removed ? "Stopped posting answers" : "Answers were posted nowhere" : deliverLine(res.deliver_to ?? null));
|
|
5329
|
+
})
|
|
5330
|
+
);
|
|
4554
5331
|
var collect = (value, previous = []) => [...previous, value];
|
|
4555
5332
|
function goalPath(sessionId, suffix = "") {
|
|
4556
5333
|
return `/sessions/${encodeURIComponent(sessionId)}/goal${suffix}`;
|
|
@@ -4693,7 +5470,7 @@ usageOptions(
|
|
|
4693
5470
|
run(async (opts) => {
|
|
4694
5471
|
const { bytes } = await apiBytes("GET", `/billing/usage/export${usageQuery(opts)}`);
|
|
4695
5472
|
if (opts.out) {
|
|
4696
|
-
|
|
5473
|
+
writeFileSync7(opts.out, bytes);
|
|
4697
5474
|
console.log(`Wrote ${bytes.length} bytes to ${opts.out}`);
|
|
4698
5475
|
} else {
|
|
4699
5476
|
process.stdout.write(bytes);
|
|
@@ -4786,6 +5563,189 @@ webhook.command("rm <id>").description("delete an endpoint").action(
|
|
|
4786
5563
|
console.log(`Deleted ${id}`);
|
|
4787
5564
|
})
|
|
4788
5565
|
);
|
|
5566
|
+
var channel = program.command("channel").description("chat surfaces (Slack, Telegram, Discord, WhatsApp, iMessage, \u2026) routed to a session");
|
|
5567
|
+
channel.command("list").description("your connections and the session each routes to").action(
|
|
5568
|
+
run(async () => {
|
|
5569
|
+
const connections = await listConnections();
|
|
5570
|
+
if (!connections.length) console.log("no channels \u2014 connect one with `sky channel connect <platform> --session sess_\u2026`");
|
|
5571
|
+
for (const c of connections) console.log(connectionLine(c));
|
|
5572
|
+
})
|
|
5573
|
+
);
|
|
5574
|
+
channel.command("connect <platform>").description("connect a surface; prints the link or code that finishes it").option("--session <id>", "route it to this session (sess_\u2026) once claimed").option("--webhook <id>", "route it to this channel webhook endpoint instead").option("--app <id>", "run the connection on your own registered bot or number (see `sky channel apps`)").option("--label <label>", "a name for the connection").option("--wait", "wait until the connection is claimed").action(
|
|
5575
|
+
run(async (platform, opts) => {
|
|
5576
|
+
const created = await connect(platform, opts);
|
|
5577
|
+
console.log(connectionLine(created));
|
|
5578
|
+
for (const step of connectSteps(created)) console.log(` ${step}`);
|
|
5579
|
+
if (opts.wait && created.status === "PENDING") {
|
|
5580
|
+
const done = await waitConnected(created.id);
|
|
5581
|
+
console.log(connectionLine(done));
|
|
5582
|
+
}
|
|
5583
|
+
})
|
|
5584
|
+
);
|
|
5585
|
+
channel.command("bind <connectionId>").description("route a connection to a session or a channel webhook endpoint").option("--session <id>", "the session (sess_\u2026)").option("--webhook <id>", "the channel webhook endpoint").action(
|
|
5586
|
+
run(async (connectionId, opts) => {
|
|
5587
|
+
await bind(connectionId, opts);
|
|
5588
|
+
console.log(`${connectionId} \u2192 ${opts.session ?? opts.webhook}`);
|
|
5589
|
+
})
|
|
5590
|
+
);
|
|
5591
|
+
channel.command("send <threadId> <text...>").description("post a markdown message into a connected thread").action(
|
|
5592
|
+
run(async (threadId, text) => {
|
|
5593
|
+
await send(threadId, text.join(" "));
|
|
5594
|
+
console.log(`Sent to ${threadId}`);
|
|
5595
|
+
})
|
|
5596
|
+
);
|
|
5597
|
+
channel.command("disconnect <connectionId>").description("disconnect a surface").action(
|
|
5598
|
+
run(async (connectionId) => {
|
|
5599
|
+
await api("DELETE", `/channels/connections/${encodeURIComponent(connectionId)}`);
|
|
5600
|
+
console.log(`Disconnected ${connectionId}`);
|
|
5601
|
+
})
|
|
5602
|
+
);
|
|
5603
|
+
channel.command("apps").description("your own bots and numbers registered as channel apps").action(
|
|
5604
|
+
run(async () => {
|
|
5605
|
+
const { apps } = await api("GET", "/channels/apps");
|
|
5606
|
+
if (!apps.length) console.log("no channel apps \u2014 connections use the shared AgentSky bots");
|
|
5607
|
+
for (const a of apps) console.log(`${a.id} ${a.platform.padEnd(9)} ${a.label}${a.status ? ` ${a.status}` : ""}`);
|
|
5608
|
+
})
|
|
5609
|
+
);
|
|
5610
|
+
channel.command("identities").description("chat identities whose messages run as you").action(
|
|
5611
|
+
run(async () => {
|
|
5612
|
+
const { identities } = await api("GET", "/channels/identities");
|
|
5613
|
+
if (!identities.length) console.log("no linked chat identities");
|
|
5614
|
+
for (const i of identities) console.log(`${i.id} ${i.platform.padEnd(9)} ${i.display_name ?? i.external_user_id}${i.linked_at ? ` since ${i.linked_at.slice(0, 10)}` : ""}`);
|
|
5615
|
+
})
|
|
5616
|
+
);
|
|
5617
|
+
channel.command("unlink <identityId>").description("unlink a chat identity; that person's messages stop running as you").action(
|
|
5618
|
+
run(async (identityId) => {
|
|
5619
|
+
await api("DELETE", `/channels/identities/${encodeURIComponent(identityId)}`);
|
|
5620
|
+
console.log(`Unlinked ${identityId}`);
|
|
5621
|
+
})
|
|
5622
|
+
);
|
|
5623
|
+
var connector = program.command("connector").description("apps your agents can use: connect, list, share with a workspace");
|
|
5624
|
+
connector.command("apps [query]").description("apps you can connect, searched by name").option("--limit <n>", "at most this many", (v) => Number.parseInt(v, 10), 50).action(
|
|
5625
|
+
run(async (query, opts) => {
|
|
5626
|
+
for (const a of await listApps(query, opts.limit)) console.log(`${a.slug.padEnd(24)} ${a.name} \u2014 ${a.description}`);
|
|
5627
|
+
})
|
|
5628
|
+
);
|
|
5629
|
+
connector.command("list").description("the accounts you connected").action(
|
|
5630
|
+
run(async () => {
|
|
5631
|
+
const { connections, credit } = await api("GET", "/connectors/connections");
|
|
5632
|
+
if (!connections.length) console.log("no connections \u2014 connect one with `sky connector connect <app>`");
|
|
5633
|
+
for (const c of connections) console.log(connectionLine2(c));
|
|
5634
|
+
if (credit.cap_usd > 0) console.log(`credit earned by connecting: $${credit.earned_usd} of $${credit.cap_usd}`);
|
|
5635
|
+
})
|
|
5636
|
+
);
|
|
5637
|
+
connector.command("connect <app>").description("connect an app in the browser (the slug from `sky connector apps`)").option("--universe <slug>", "connect it for this workspace itself (owner or admin)").option("--no-open", "print the authorization link without opening it").action(
|
|
5638
|
+
run(async (app, opts) => {
|
|
5639
|
+
const connection = await connectApp(app, { workspace: opts.universe, open: opts.open });
|
|
5640
|
+
console.log(`Connected ${connection.app_name} (${connection.label})${opts.universe ? ` for ${opts.universe}` : ""}`);
|
|
5641
|
+
})
|
|
5642
|
+
);
|
|
5643
|
+
connector.command("disconnect <connectionId>").description("disconnect one of your connections; agents lose it at once").action(
|
|
5644
|
+
run(async (connectionId) => {
|
|
5645
|
+
await api("DELETE", `/connectors/connections/${encodeURIComponent(connectionId)}`);
|
|
5646
|
+
console.log(`Disconnected ${connectionId}`);
|
|
5647
|
+
})
|
|
5648
|
+
);
|
|
5649
|
+
connector.command("repos").description("repositories your GitHub connection can reach").action(
|
|
5650
|
+
run(async () => {
|
|
5651
|
+
const { repos } = await api("GET", "/connectors/github/repos");
|
|
5652
|
+
for (const r of repos) console.log(`${r.full_name} ${r.default_branch}${r.private ? " private" : ""}`);
|
|
5653
|
+
})
|
|
5654
|
+
);
|
|
5655
|
+
connector.command("workspace [slug]").description("connections shared with a workspace (default: the current one)").action(
|
|
5656
|
+
run(async (slug) => {
|
|
5657
|
+
const ws = await api("GET", `/universes/${encodeURIComponent(await workspaceSlug(slug))}/connections`);
|
|
5658
|
+
if (!ws.sharing) console.log("this workspace does not take shared connections");
|
|
5659
|
+
for (const c of ws.connections) console.log(workspaceConnectionLine(c));
|
|
5660
|
+
})
|
|
5661
|
+
);
|
|
5662
|
+
connector.command("share <connectionId>").description("share one of your connections with a workspace, so its sessions can call through it").option("--universe <slug>", "the workspace (default: the current one)").action(
|
|
5663
|
+
run(async (connectionId, opts) => {
|
|
5664
|
+
const slug = await workspaceSlug(opts.universe);
|
|
5665
|
+
await api("POST", `/universes/${encodeURIComponent(slug)}/connections`, { connection_id: connectionId });
|
|
5666
|
+
console.log(`Shared ${connectionId} with ${slug}`);
|
|
5667
|
+
})
|
|
5668
|
+
);
|
|
5669
|
+
connector.command("unshare <connectionId>").description("stop sharing a connection with a workspace").option("--universe <slug>", "the workspace (default: the current one)").action(
|
|
5670
|
+
run(async (connectionId, opts) => {
|
|
5671
|
+
const slug = await workspaceSlug(opts.universe);
|
|
5672
|
+
await api("DELETE", `/universes/${encodeURIComponent(slug)}/connections/${encodeURIComponent(connectionId)}`);
|
|
5673
|
+
console.log(`Stopped sharing ${connectionId} with ${slug}`);
|
|
5674
|
+
})
|
|
5675
|
+
);
|
|
5676
|
+
connector.command("shared").description("everything you share with workspaces, and the calls made through each").action(
|
|
5677
|
+
run(async () => {
|
|
5678
|
+
const { shared } = await api("GET", "/shared-connections");
|
|
5679
|
+
if (!shared.length) console.log("you share no connections");
|
|
5680
|
+
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"}`);
|
|
5681
|
+
})
|
|
5682
|
+
);
|
|
5683
|
+
var vault = program.command("vault").description("vaults: credentials a session is granted when it starts (values write-only)");
|
|
5684
|
+
vault.command("list").description("the workspace's vaults").action(
|
|
5685
|
+
run(async () => {
|
|
5686
|
+
const vaults = await listVaults();
|
|
5687
|
+
if (!vaults.length) console.log("no vaults");
|
|
5688
|
+
for (const v of vaults) console.log(`${v.id} ${v.display_name}`);
|
|
5689
|
+
})
|
|
5690
|
+
);
|
|
5691
|
+
vault.command("create <name>").description("create a vault; pass its id as vault_ids when starting a session").action(
|
|
5692
|
+
run(async (name) => {
|
|
5693
|
+
const { vault: created } = await api("POST", "/vaults", { display_name: name });
|
|
5694
|
+
console.log(`${created.id} ${created.display_name}`);
|
|
5695
|
+
})
|
|
5696
|
+
);
|
|
5697
|
+
vault.command("show <vaultId>").description("a vault's secrets (metadata only)").action(
|
|
5698
|
+
run(async (vaultId) => {
|
|
5699
|
+
const secrets2 = await listSecrets(vaultId);
|
|
5700
|
+
if (!secrets2.length) console.log("no secrets");
|
|
5701
|
+
for (const secret of secrets2) console.log(secretLine(secret));
|
|
5702
|
+
})
|
|
5703
|
+
);
|
|
5704
|
+
vault.command("rm <vaultId>").description("delete a vault and every secret in it").option("-y, --yes", "skip the confirmation prompt").action(
|
|
5705
|
+
run(async (vaultId, opts) => {
|
|
5706
|
+
if (!opts.yes && !await confirmPrompt(`Delete ${vaultId} and every secret in it?`)) return void console.log("Aborted.");
|
|
5707
|
+
await api("DELETE", `/vaults/${encodeURIComponent(vaultId)}`);
|
|
5708
|
+
console.log(`Deleted ${vaultId}`);
|
|
5709
|
+
})
|
|
5710
|
+
);
|
|
5711
|
+
vault.command("move <intoVaultId>").description("move the secrets of other vaults into this one").requiredOption("--from <vaultId...>", "the vaults to move out of").option("--dry-run", "only report what would move").action(
|
|
5712
|
+
run(async (intoVaultId, opts) => {
|
|
5713
|
+
for (const m of await moveSecrets(intoVaultId, opts.from, Boolean(opts.dryRun))) {
|
|
5714
|
+
console.log(`${m.outcome.padEnd(8)} ${m.name} from ${m.from}${m.reason ? ` (${m.reason})` : ""}`);
|
|
5715
|
+
}
|
|
5716
|
+
})
|
|
5717
|
+
);
|
|
5718
|
+
var vaultSecret = vault.command("secret").description("the secrets in a vault");
|
|
5719
|
+
vaultSecret.command("add <vaultId> <name>").description("add an environment variable (value from stdin or a hidden prompt), or an MCP server's bearer token with --mcp").option("--host <host...>", "hosts the environment variable may be sent to").option("--header <name>", "the header it is sent in (default: Authorization)").option("--template <template>", "how the header value is built, e.g. 'Bearer {secret}'").option("--mcp <url>", "store a bearer token for this MCP server instead; <name> is its display name").action(
|
|
5720
|
+
run(async (vaultId, name, opts) => {
|
|
5721
|
+
const value = await readSecretValue(opts.mcp ? "Token: " : `Value for ${name}: `);
|
|
5722
|
+
if (!value) throw new ApiError(400, "no_value", "no value provided");
|
|
5723
|
+
const input = opts.mcp ? { kind: "mcp", url: opts.mcp, token: value, displayName: name } : { kind: "env", name, value, hosts: opts.host ?? [], header: opts.header, template: opts.template };
|
|
5724
|
+
console.log(secretLine(await addSecret(vaultId, input)));
|
|
5725
|
+
})
|
|
5726
|
+
);
|
|
5727
|
+
vaultSecret.command("rotate <vaultId> <secretId>").description("replace a secret's value in place (stdin or a hidden prompt); running sessions keep working").option("--host <host...>", "also replace the hosts it may be sent to").option("--keep-value", "only change --host, keep the value").action(
|
|
5728
|
+
run(async (vaultId, secretId, opts) => {
|
|
5729
|
+
const current = (await listSecrets(vaultId)).find((s) => s.id === secretId);
|
|
5730
|
+
if (!current) throw new ApiError(404, "not_found", `no secret ${secretId} in ${vaultId}`);
|
|
5731
|
+
const value = opts.keepValue ? void 0 : await readSecretValue("New value: ");
|
|
5732
|
+
if (value === "") throw new ApiError(400, "no_value", "no value provided");
|
|
5733
|
+
const field = current.auth_type === "environment_variable" ? "value" : "token";
|
|
5734
|
+
console.log(secretLine(await updateSecret(vaultId, secretId, { [field]: value, hosts: opts.host })));
|
|
5735
|
+
})
|
|
5736
|
+
);
|
|
5737
|
+
vaultSecret.command("rm <vaultId> <secretId>").description("delete a secret").action(
|
|
5738
|
+
run(async (vaultId, secretId) => {
|
|
5739
|
+
await api("DELETE", `/vaults/${encodeURIComponent(vaultId)}/secrets/${encodeURIComponent(secretId)}`);
|
|
5740
|
+
console.log(`Deleted ${secretId}`);
|
|
5741
|
+
})
|
|
5742
|
+
);
|
|
5743
|
+
vaultSecret.command("validate <vaultId> <secretId>").description("check that an MCP OAuth grant still works").action(
|
|
5744
|
+
run(async (vaultId, secretId) => {
|
|
5745
|
+
const { result } = await api("POST", `/vaults/${encodeURIComponent(vaultId)}/secrets/${encodeURIComponent(secretId)}/validate`);
|
|
5746
|
+
console.log(`${secretId}: ${result}`);
|
|
5747
|
+
})
|
|
5748
|
+
);
|
|
4789
5749
|
var harness = program.command("harness").description("workspace harnesses: init, verify, push");
|
|
4790
5750
|
harness.command("init [dir]").description("write a harness.json profile skeleton in the given directory (default: .)").action(run(async (dir) => {
|
|
4791
5751
|
harnessInit(dir ?? ".");
|
|
@@ -4809,7 +5769,7 @@ harness.command("connect").description("run an ACP agent (the command after --)
|
|
|
4809
5769
|
}));
|
|
4810
5770
|
var traceCmd = program.command("trace").description("correlate this machine's agent work with the cloud sessions it starts (metadata only unless you choose content)");
|
|
4811
5771
|
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) => {
|
|
4812
|
-
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-
|
|
5772
|
+
const { readState, writeState, record, installClaudeHooks, parseContentScopes, chosenPaths, fileEvent } = await import("./trace-IOARVWJ6.js");
|
|
4813
5773
|
if (readState()) throw new Error("a trace is already running here \u2014 `sky trace stop` it first");
|
|
4814
5774
|
const scopes = parseContentScopes(opts.capture);
|
|
4815
5775
|
const chosen = chosenPaths(opts.file ?? [], opts.dir ?? []);
|
|
@@ -4838,13 +5798,13 @@ traceCmd.command("start").description("start a trace: a grant for this machine,
|
|
|
4838
5798
|
]);
|
|
4839
5799
|
console.log(`Tracing ${trace.id} as ${opts.source} until ${grant.expires_at}`);
|
|
4840
5800
|
console.log(scopes.length ? ` content: ${scopes.join(", ")} (redacted here before it leaves)` : " metadata only");
|
|
4841
|
-
if (opts.hooks) console.log(` hooks: ${installClaudeHooks(
|
|
5801
|
+
if (opts.hooks) console.log(` hooks: ${installClaudeHooks(resolve9(typeof opts.hooks === "string" ? opts.hooks : ".claude/settings.local.json"))}`);
|
|
4842
5802
|
console.log(` a session started elsewhere joins it with metadata {"trace_id": "${trace.id}"}`);
|
|
4843
5803
|
console.log(` sky trace show ${trace.id}`);
|
|
4844
5804
|
}));
|
|
4845
5805
|
traceCmd.command("hook").description("record one Claude Code hook event read from stdin (run by the installed hooks; never fails, prints nothing)").action(async () => {
|
|
4846
5806
|
try {
|
|
4847
|
-
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-
|
|
5807
|
+
const { readState, record, flush, grantSender, eventsForClaudeHook, contentForClaudeHook, clearState } = await import("./trace-IOARVWJ6.js");
|
|
4848
5808
|
const chunks = [];
|
|
4849
5809
|
for await (const chunk of process.stdin) chunks.push(chunk);
|
|
4850
5810
|
const input = JSON.parse(Buffer.concat(chunks).toString("utf8") || "{}");
|
|
@@ -4857,14 +5817,14 @@ traceCmd.command("hook").description("record one Claude Code hook event read fro
|
|
|
4857
5817
|
}
|
|
4858
5818
|
});
|
|
4859
5819
|
traceCmd.command("flush").description("send what the spool holds, retrying with backoff").action(run(async () => {
|
|
4860
|
-
const { readState, flush, grantSender } = await import("./trace-
|
|
5820
|
+
const { readState, flush, grantSender } = await import("./trace-IOARVWJ6.js");
|
|
4861
5821
|
const state = readState();
|
|
4862
5822
|
if (!state) throw new Error("no trace is running here");
|
|
4863
5823
|
const outcome = await flush(grantSender(state), { attempts: 4 });
|
|
4864
5824
|
console.log(`sent ${outcome.sent}, refused ${outcome.refused}, given up ${outcome.expired}, still pending ${outcome.pending}${outcome.stopped ? ` (stopped: ${outcome.stopped})` : ""}`);
|
|
4865
5825
|
}));
|
|
4866
5826
|
traceCmd.command("status").description("the running trace and its spool").action(run(async () => {
|
|
4867
|
-
const { readState, spoolStatus } = await import("./trace-
|
|
5827
|
+
const { readState, spoolStatus } = await import("./trace-IOARVWJ6.js");
|
|
4868
5828
|
const state = readState();
|
|
4869
5829
|
if (!state) {
|
|
4870
5830
|
console.log("No trace is running here.");
|
|
@@ -4874,7 +5834,7 @@ traceCmd.command("status").description("the running trace and its spool").action
|
|
|
4874
5834
|
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`);
|
|
4875
5835
|
}));
|
|
4876
5836
|
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) => {
|
|
4877
|
-
const { readState } = await import("./trace-
|
|
5837
|
+
const { readState } = await import("./trace-IOARVWJ6.js");
|
|
4878
5838
|
const ref = id ?? readState()?.trace_id;
|
|
4879
5839
|
if (!ref) throw new Error("name a trace, or start one here");
|
|
4880
5840
|
const bundle = await api("GET", `/traces/${ref}`);
|
|
@@ -4901,7 +5861,7 @@ traceCmd.command("show [id]").description("the trace bundle: spans, the sessions
|
|
|
4901
5861
|
for (const gap of bundle.gaps) console.log(` gap: ${gap.span_id} ${gap.reason}`);
|
|
4902
5862
|
}));
|
|
4903
5863
|
traceCmd.command("stop").description("end the root span, send what is left, and revoke this machine's grant").action(run(async () => {
|
|
4904
|
-
const { readState, record, flush, grantSender, clearState } = await import("./trace-
|
|
5864
|
+
const { readState, record, flush, grantSender, clearState } = await import("./trace-IOARVWJ6.js");
|
|
4905
5865
|
const state = readState();
|
|
4906
5866
|
if (!state) throw new Error("no trace is running here");
|
|
4907
5867
|
if (state.root_span) record(() => [{ span_id: state.root_span, kind: "end", outcome: "completed" }]);
|
|
@@ -4911,7 +5871,7 @@ traceCmd.command("stop").description("end the root span, send what is left, and
|
|
|
4911
5871
|
console.log(`Stopped ${state.trace_id}${outcome.pending ? ` (${outcome.pending} events could not be sent)` : ""}`);
|
|
4912
5872
|
}));
|
|
4913
5873
|
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) => {
|
|
4914
|
-
const lib = await import("./trace-library-
|
|
5874
|
+
const lib = await import("./trace-library-K3NE26GH.js");
|
|
4915
5875
|
if (opts.hook) {
|
|
4916
5876
|
try {
|
|
4917
5877
|
let raw = payload ?? "";
|
|
@@ -4959,10 +5919,10 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4959
5919
|
console.log(`A repository's .agentsky.json "workspace" wins over these rules.`);
|
|
4960
5920
|
}
|
|
4961
5921
|
if (opts.installSchedule) {
|
|
4962
|
-
const { join:
|
|
5922
|
+
const { join: join14 } = await import("path");
|
|
4963
5923
|
const { homedir: homedir8 } = await import("os");
|
|
4964
5924
|
const { execFileSync } = await import("child_process");
|
|
4965
|
-
const log =
|
|
5925
|
+
const log = join14(homedir8(), ".config", "sky", "trace-sync.log");
|
|
4966
5926
|
const plist = lib.installLaunchd([process.execPath, process.argv[1], "trace", "sync", "--quiet"], log);
|
|
4967
5927
|
try {
|
|
4968
5928
|
execFileSync("launchctl", ["unload", plist], { stdio: "ignore" });
|
|
@@ -4972,11 +5932,11 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
4972
5932
|
console.log(`Sweeping every 15 minutes (${plist}); a full re-read runs weekly. Log: ${log}`);
|
|
4973
5933
|
}
|
|
4974
5934
|
if (opts.status) {
|
|
4975
|
-
const { readFileSync:
|
|
4976
|
-
const { join:
|
|
5935
|
+
const { readFileSync: readFileSync15 } = await import("fs");
|
|
5936
|
+
const { join: join14 } = await import("path");
|
|
4977
5937
|
let state = { sessions: {} };
|
|
4978
5938
|
try {
|
|
4979
|
-
state = JSON.parse(
|
|
5939
|
+
state = JSON.parse(readFileSync15(join14(lib.syncConfigPath(), "..", "trace-sync-state.json"), "utf8"));
|
|
4980
5940
|
} catch {
|
|
4981
5941
|
}
|
|
4982
5942
|
const counts = /* @__PURE__ */ new Map();
|
|
@@ -5009,7 +5969,7 @@ traceCmd.command("sync [payload]").description("upload this machine's Claude Cod
|
|
|
5009
5969
|
})();
|
|
5010
5970
|
});
|
|
5011
5971
|
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) => {
|
|
5012
|
-
const { libraryRequest } = await import("./trace-library-
|
|
5972
|
+
const { libraryRequest } = await import("./trace-library-K3NE26GH.js");
|
|
5013
5973
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
5014
5974
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
5015
5975
|
const params = new URLSearchParams({ q: words.join(" "), scope: "traces", limit: String(opts.limit), ...opts.cursor ? { cursor: opts.cursor } : {} });
|
|
@@ -5026,7 +5986,7 @@ traceCmd.command("search <query...>").description("search every session in a wor
|
|
|
5026
5986
|
if (page.nextCursor) console.log(`More: sky trace search ${JSON.stringify(words.join(" "))} --workspace ${workspace2} --cursor ${page.nextCursor}`);
|
|
5027
5987
|
}));
|
|
5028
5988
|
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) => {
|
|
5029
|
-
const { libraryRequest } = await import("./trace-library-
|
|
5989
|
+
const { libraryRequest } = await import("./trace-library-K3NE26GH.js");
|
|
5030
5990
|
const workspace2 = opts.workspace ?? currentUniverse();
|
|
5031
5991
|
if (!workspace2) throw new Error("name a workspace with --workspace");
|
|
5032
5992
|
const page = await libraryRequest("GET", `/trace-library?limit=${opts.limit}`, workspace2);
|