@agentsky/sky 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -10,13 +10,14 @@ import {
|
|
|
10
10
|
mcpServersFromKept,
|
|
11
11
|
modelSubscriptionProviderFor,
|
|
12
12
|
reasoningEffortDocLine,
|
|
13
|
+
redactLine,
|
|
13
14
|
skillRefSchema,
|
|
14
15
|
splitMcpServers,
|
|
15
16
|
supersededBy
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-FSAF6HIL.js";
|
|
17
18
|
|
|
18
19
|
// src/index.ts
|
|
19
|
-
import { existsSync as
|
|
20
|
+
import { existsSync as existsSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
20
21
|
import { basename as basename3, resolve as resolve3 } from "path";
|
|
21
22
|
import { createInterface as createInterface3 } from "readline";
|
|
22
23
|
import { Command } from "commander";
|
|
@@ -61,7 +62,7 @@ function currentUniverse() {
|
|
|
61
62
|
}
|
|
62
63
|
|
|
63
64
|
// src/version.ts
|
|
64
|
-
var CLI_VERSION = "2.
|
|
65
|
+
var CLI_VERSION = "2.4.0";
|
|
65
66
|
var USER_AGENT = `sky-cli/${CLI_VERSION} (${process.platform}; node/${process.versions.node})`;
|
|
66
67
|
|
|
67
68
|
// src/api.ts
|
|
@@ -128,7 +129,7 @@ async function postEvents(sessionId, events, idempotencyKey) {
|
|
|
128
129
|
}
|
|
129
130
|
async function uploadFile(bytes, filename, mediaType, expiresInSeconds) {
|
|
130
131
|
const form = new FormData();
|
|
131
|
-
form.append("file", new Blob([bytes], { type: mediaType }), filename);
|
|
132
|
+
form.append("file", new Blob([new Uint8Array(bytes)], { type: mediaType }), filename);
|
|
132
133
|
if (expiresInSeconds) form.append("expires_in_seconds", String(expiresInSeconds));
|
|
133
134
|
const res = await fetch(`${apiBase()}/files`, {
|
|
134
135
|
method: "POST",
|
|
@@ -164,7 +165,7 @@ async function resolveSessionRef(ref) {
|
|
|
164
165
|
return agent2.defaultSessionId;
|
|
165
166
|
}
|
|
166
167
|
async function* streamEvents(sessionId, signal) {
|
|
167
|
-
const { parseSseStream } = await import("./src-
|
|
168
|
+
const { parseSseStream } = await import("./src-67OBSB2Q.js");
|
|
168
169
|
const res = await fetch(
|
|
169
170
|
`${apiBase()}/sessions/${encodeURIComponent(sessionId)}/stream`,
|
|
170
171
|
{
|
|
@@ -678,8 +679,303 @@ async function clone(opts) {
|
|
|
678
679
|
else await createClone(local, opts);
|
|
679
680
|
}
|
|
680
681
|
|
|
682
|
+
// src/import-local.ts
|
|
683
|
+
import { closeSync, existsSync as existsSync3, openSync, readdirSync, readFileSync as readFileSync5, readSync, statSync } from "fs";
|
|
684
|
+
import { homedir as homedir3 } from "os";
|
|
685
|
+
import { join as join3 } from "path";
|
|
686
|
+
import { createHash } from "crypto";
|
|
687
|
+
function redactLine2(raw) {
|
|
688
|
+
return redactLine(raw).line;
|
|
689
|
+
}
|
|
690
|
+
function decodeWorkspace(encoded) {
|
|
691
|
+
try {
|
|
692
|
+
return decodeURIComponent(encoded.replace(/-/g, "%").replace(/_/g, "%5F"));
|
|
693
|
+
} catch {
|
|
694
|
+
return encoded;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
function readPrefix(filePath) {
|
|
698
|
+
const fd = openSync(filePath, "r");
|
|
699
|
+
try {
|
|
700
|
+
const bytes = Buffer.alloc(256 * 1024);
|
|
701
|
+
return bytes.subarray(0, readSync(fd, bytes, 0, bytes.length, 0)).toString("utf8");
|
|
702
|
+
} finally {
|
|
703
|
+
closeSync(fd);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
function sniffTitle(filePath) {
|
|
707
|
+
try {
|
|
708
|
+
const data = readPrefix(filePath);
|
|
709
|
+
for (const raw of data.split("\n")) {
|
|
710
|
+
if (!raw.trim()) continue;
|
|
711
|
+
let line;
|
|
712
|
+
try {
|
|
713
|
+
line = JSON.parse(redactLine2(raw));
|
|
714
|
+
} catch {
|
|
715
|
+
continue;
|
|
716
|
+
}
|
|
717
|
+
if (!line || typeof line !== "object") continue;
|
|
718
|
+
const rec = line;
|
|
719
|
+
if (rec.type === "user" && rec.message && typeof rec.message === "object") {
|
|
720
|
+
const msg = rec.message;
|
|
721
|
+
if (Array.isArray(msg.content)) {
|
|
722
|
+
for (const block of msg.content) {
|
|
723
|
+
if (typeof block === "object" && block !== null) {
|
|
724
|
+
const b = block;
|
|
725
|
+
if (b.type === "text" && typeof b.text === "string" && b.text.trim()) {
|
|
726
|
+
return b.text.trim().slice(0, 120);
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
if (typeof msg.content === "string" && msg.content.trim()) {
|
|
732
|
+
return msg.content.trim().slice(0, 120);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
if (rec.type === "event_msg" && rec.payload && typeof rec.payload === "object") {
|
|
736
|
+
const payload = rec.payload;
|
|
737
|
+
if (payload.type === "user_message" && typeof payload.message === "string") return redactLine2(payload.message).slice(0, 120);
|
|
738
|
+
}
|
|
739
|
+
if (rec.type === "response_item" && rec.payload && typeof rec.payload === "object") {
|
|
740
|
+
const payload = rec.payload;
|
|
741
|
+
if (payload.type === "message" && payload.role === "user" && Array.isArray(payload.content)) {
|
|
742
|
+
const text = payload.content.find((part) => typeof part?.text === "string")?.text;
|
|
743
|
+
if (typeof text === "string" && text.trim()) return redactLine2(text.trim()).slice(0, 120);
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
if (rec.type === "session_meta") continue;
|
|
747
|
+
if (rec.type === "user_turn" || rec.type === "message" && rec.role === "user") {
|
|
748
|
+
const content = rec.content;
|
|
749
|
+
if (typeof content === "string" && content.trim()) return content.trim().slice(0, 120);
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
return null;
|
|
753
|
+
} catch {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function listClaudeCodeSessions(limit) {
|
|
758
|
+
const root = process.env.CLAUDE_CONFIG_DIR ?? join3(homedir3(), ".claude");
|
|
759
|
+
const projectsDir = join3(root, "projects");
|
|
760
|
+
if (!existsSync3(projectsDir)) return [];
|
|
761
|
+
const sessions = [];
|
|
762
|
+
try {
|
|
763
|
+
for (const encoded of readdirSync(projectsDir)) {
|
|
764
|
+
const projectDir = join3(projectsDir, encoded);
|
|
765
|
+
let entries;
|
|
766
|
+
try {
|
|
767
|
+
entries = readdirSync(projectDir);
|
|
768
|
+
} catch {
|
|
769
|
+
continue;
|
|
770
|
+
}
|
|
771
|
+
const workspace = decodeWorkspace(encoded);
|
|
772
|
+
for (const entry of entries) {
|
|
773
|
+
if (!entry.endsWith(".jsonl")) continue;
|
|
774
|
+
const sessionId = entry.slice(0, -6);
|
|
775
|
+
const mainFile = join3(projectDir, entry);
|
|
776
|
+
let stat;
|
|
777
|
+
try {
|
|
778
|
+
stat = statSync(mainFile);
|
|
779
|
+
} catch {
|
|
780
|
+
continue;
|
|
781
|
+
}
|
|
782
|
+
if (!stat.isFile()) continue;
|
|
783
|
+
let discoveryError;
|
|
784
|
+
const subagentFiles = [];
|
|
785
|
+
const subagentDir = join3(projectDir, sessionId, "subagents");
|
|
786
|
+
if (existsSync3(subagentDir)) {
|
|
787
|
+
try {
|
|
788
|
+
const subFiles = readdirSync(subagentDir).filter((f) => f.endsWith(".jsonl")).sort();
|
|
789
|
+
for (const f of subFiles) {
|
|
790
|
+
subagentFiles.push(join3(subagentDir, f));
|
|
791
|
+
}
|
|
792
|
+
} catch {
|
|
793
|
+
discoveryError = "Cannot read all recorded subagent conversations. Fix local file access before importing this session.";
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
sessions.push({
|
|
797
|
+
harness: "claude_code",
|
|
798
|
+
sessionId,
|
|
799
|
+
workspace,
|
|
800
|
+
title: sniffTitle(mainFile),
|
|
801
|
+
mtimeMs: stat.mtimeMs,
|
|
802
|
+
files: [mainFile, ...subagentFiles],
|
|
803
|
+
...discoveryError ? { discoveryError } : {}
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
}
|
|
807
|
+
} catch {
|
|
808
|
+
}
|
|
809
|
+
return sessions.sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
810
|
+
}
|
|
811
|
+
function listCodexSessions(limit) {
|
|
812
|
+
const sessionsDir = join3(process.env.CODEX_HOME ?? process.env.CODEX_CONFIG_DIR ?? join3(homedir3(), ".codex"), "sessions");
|
|
813
|
+
const records = [];
|
|
814
|
+
function walk(dir) {
|
|
815
|
+
let entries;
|
|
816
|
+
try {
|
|
817
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
818
|
+
} catch {
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
for (const entry of entries) {
|
|
822
|
+
const full = join3(dir, entry.name);
|
|
823
|
+
if (entry.isDirectory()) {
|
|
824
|
+
walk(full);
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
if (!entry.isFile() || !entry.name.startsWith("rollout-") || !entry.name.endsWith(".jsonl")) continue;
|
|
828
|
+
try {
|
|
829
|
+
const first = readPrefix(full).split("\n", 1)[0];
|
|
830
|
+
const meta = JSON.parse(first);
|
|
831
|
+
if (meta.type !== "session_meta" || typeof meta.payload?.id !== "string") continue;
|
|
832
|
+
const data = meta.payload;
|
|
833
|
+
const parent = data.forked_from_id ?? data.source?.spawn?.parent_thread_id ?? data.source?.subagent?.spawn?.parent_thread_id;
|
|
834
|
+
records.push({
|
|
835
|
+
session: {
|
|
836
|
+
harness: "codex",
|
|
837
|
+
sessionId: data.id,
|
|
838
|
+
workspace: typeof data.cwd === "string" ? data.cwd : dir,
|
|
839
|
+
title: sniffTitle(full),
|
|
840
|
+
mtimeMs: statSync(full).mtimeMs,
|
|
841
|
+
files: [full]
|
|
842
|
+
},
|
|
843
|
+
...typeof parent === "string" ? { parent } : {}
|
|
844
|
+
});
|
|
845
|
+
} catch {
|
|
846
|
+
continue;
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
walk(sessionsDir);
|
|
851
|
+
function descendants(id, visited) {
|
|
852
|
+
if (visited.has(id)) return [];
|
|
853
|
+
visited.add(id);
|
|
854
|
+
return records.filter((item) => item.parent === id).flatMap((item) => [
|
|
855
|
+
...item.session.files,
|
|
856
|
+
...descendants(item.session.sessionId, visited)
|
|
857
|
+
]);
|
|
858
|
+
}
|
|
859
|
+
return records.filter((item) => !item.parent).map(({ session: session2 }) => ({
|
|
860
|
+
...session2,
|
|
861
|
+
files: [.../* @__PURE__ */ new Set([...session2.files, ...descendants(session2.sessionId, /* @__PURE__ */ new Set())])]
|
|
862
|
+
})).sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
863
|
+
}
|
|
864
|
+
function listRecentSessions(harness, limit) {
|
|
865
|
+
const claudeSessions = harness === "codex" ? [] : listClaudeCodeSessions(limit);
|
|
866
|
+
const codexSessions = harness === "claude_code" ? [] : listCodexSessions(limit);
|
|
867
|
+
return [...claudeSessions, ...codexSessions].sort((a, b) => b.mtimeMs - a.mtimeMs).slice(0, limit);
|
|
868
|
+
}
|
|
869
|
+
function agentUseBase() {
|
|
870
|
+
const env = process.env.AGENT_USE_URL?.trim().replace(/\/$/, "");
|
|
871
|
+
if (env) return env;
|
|
872
|
+
const api2 = apiBase().replace(/\/$/, "");
|
|
873
|
+
const derived = api2.replace(/\/api\/v1$/, "").replace(/\/v1$/, "").replace("api.agentsky.dev", "agentsky.dev");
|
|
874
|
+
if (derived.includes("localhost:3000")) return derived.replace("localhost:3000", "localhost:3002");
|
|
875
|
+
return derived;
|
|
876
|
+
}
|
|
877
|
+
async function mintTrace(base, harness) {
|
|
878
|
+
const token = apiToken();
|
|
879
|
+
if (!token) throw new ApiError(401, "not_logged_in", "not logged in \u2014 run `sky auth login`");
|
|
880
|
+
const res = await fetch(`${base}/api/traces`, {
|
|
881
|
+
method: "POST",
|
|
882
|
+
redirect: "error",
|
|
883
|
+
signal: AbortSignal.timeout(15e3),
|
|
884
|
+
headers: {
|
|
885
|
+
"content-type": "application/json",
|
|
886
|
+
authorization: `Bearer ${token}`,
|
|
887
|
+
"user-agent": USER_AGENT
|
|
888
|
+
},
|
|
889
|
+
body: JSON.stringify({ harness })
|
|
890
|
+
});
|
|
891
|
+
if (!res.ok) {
|
|
892
|
+
const body = await res.json().catch(() => null);
|
|
893
|
+
throw new Error(
|
|
894
|
+
typeof body?.error === "string" ? body.error : `Failed to create import (${res.status}).`
|
|
895
|
+
);
|
|
896
|
+
}
|
|
897
|
+
return await res.json();
|
|
898
|
+
}
|
|
899
|
+
function prepareImport(session2) {
|
|
900
|
+
if (session2.discoveryError) throw new Error(session2.discoveryError);
|
|
901
|
+
const maximum = 16 * 1024 * 1024;
|
|
902
|
+
let inputBytes = 0;
|
|
903
|
+
let outputBytes = 0;
|
|
904
|
+
const lines = [];
|
|
905
|
+
const digest = createHash("sha256");
|
|
906
|
+
let nativeId;
|
|
907
|
+
for (const path of session2.files) {
|
|
908
|
+
const stat = statSync(path);
|
|
909
|
+
if (!stat.isFile() || (inputBytes += stat.size) > maximum) throw new Error("Selected transcript exceeds the 16 MiB limit or is not a regular file.");
|
|
910
|
+
const text = readFileSync5(path, "utf8");
|
|
911
|
+
for (const raw of text.split("\n")) {
|
|
912
|
+
if (!raw.trim()) continue;
|
|
913
|
+
const record = JSON.parse(raw);
|
|
914
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) throw new Error("Transcript records must be JSON objects.");
|
|
915
|
+
if (String(record.type).startsWith("import.")) throw new Error("Native transcripts cannot supply import protocol records.");
|
|
916
|
+
const id = record.type === "session_meta" ? record.payload?.id : record.sessionId;
|
|
917
|
+
if (!nativeId && typeof id === "string") nativeId = id;
|
|
918
|
+
const clean = redactLine(raw.replace(/\r$/, ""), { requireStructured: true }).line;
|
|
919
|
+
const line = clean + "\n";
|
|
920
|
+
outputBytes += Buffer.byteLength(line);
|
|
921
|
+
if (outputBytes > maximum) throw new Error("Redacted transcript exceeds the 16 MiB limit.");
|
|
922
|
+
digest.update(line);
|
|
923
|
+
lines.push(line);
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
if (!lines.length) throw new Error("The selected transcript is empty.");
|
|
927
|
+
const sourceId = nativeId ?? session2.sessionId;
|
|
928
|
+
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.");
|
|
929
|
+
const summary = {
|
|
930
|
+
format: "asteroids.session-summary",
|
|
931
|
+
format_version: 1,
|
|
932
|
+
source_harness: session2.harness,
|
|
933
|
+
source_harness_version: null,
|
|
934
|
+
source_session_id: sourceId,
|
|
935
|
+
created_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
936
|
+
summary: "The complete selected local conversation is attached as a transcript. Review it before continuing; this automatic handoff does not infer the user's unfinished objective or claim any work is complete.",
|
|
937
|
+
context: {
|
|
938
|
+
objective: "Review the imported conversation and continue its latest unfinished authorized objective.",
|
|
939
|
+
constraints: [],
|
|
940
|
+
decisions: [],
|
|
941
|
+
completed: [],
|
|
942
|
+
pending: ["Read the complete imported transcript, reconcile the latest human instructions and completed work, then continue."],
|
|
943
|
+
verification: [],
|
|
944
|
+
blockers: ["This handoff contains the recorded conversation only. Referenced local files, runtime state, and unreadable reasoning are not transferred."],
|
|
945
|
+
artifacts: [],
|
|
946
|
+
provenance: { native_checkpoint_sha256: digest.digest("hex"), selected_native_session_id: sourceId, context_bytes: outputBytes }
|
|
947
|
+
}
|
|
948
|
+
};
|
|
949
|
+
return lines.join("") + JSON.stringify({ type: "import.summary", package: summary }) + "\n" + JSON.stringify({ type: "import.end", records: lines.length + 1 }) + "\n";
|
|
950
|
+
}
|
|
951
|
+
async function importSession(opts) {
|
|
952
|
+
const base = (opts.agentUseUrl ?? agentUseBase()).replace(/\/$/, "");
|
|
953
|
+
const destination = new URL(base);
|
|
954
|
+
if (destination.username || destination.password || destination.protocol !== "https:" && !(destination.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(destination.hostname))) {
|
|
955
|
+
throw new Error("Import requires HTTPS, or a loopback development server.");
|
|
956
|
+
}
|
|
957
|
+
opts.onProgress?.("Reading transcript and redacting secrets locally\u2026");
|
|
958
|
+
const body = prepareImport(opts.session);
|
|
959
|
+
opts.onProgress?.("Minting import key\u2026");
|
|
960
|
+
const { traceId, key } = await mintTrace(base, opts.session.harness);
|
|
961
|
+
opts.onProgress?.("Uploading the complete transcript\u2026");
|
|
962
|
+
const response = await fetch(`${base}/api/traces/ingest`, {
|
|
963
|
+
method: "POST",
|
|
964
|
+
redirect: "error",
|
|
965
|
+
signal: AbortSignal.timeout(12e4),
|
|
966
|
+
headers: { authorization: `Bearer ${key}`, "content-type": "application/x-ndjson", "x-source-harness": opts.session.harness, "user-agent": USER_AGENT },
|
|
967
|
+
body
|
|
968
|
+
});
|
|
969
|
+
if (!response.ok) {
|
|
970
|
+
const result2 = await response.json().catch(() => null);
|
|
971
|
+
throw new Error(typeof result2?.error === "string" ? result2.error : result2?.error?.message ?? `Upload failed (${response.status}).`);
|
|
972
|
+
}
|
|
973
|
+
const result = await response.json();
|
|
974
|
+
return { traceId: result.traceId ?? traceId, url: `${base}/playground/new?import=${encodeURIComponent(result.traceId ?? traceId)}` };
|
|
975
|
+
}
|
|
976
|
+
|
|
681
977
|
// src/login.ts
|
|
682
|
-
import { createHash, randomBytes } from "crypto";
|
|
978
|
+
import { createHash as createHash2, randomBytes } from "crypto";
|
|
683
979
|
import { createServer } from "http";
|
|
684
980
|
import { hostname } from "os";
|
|
685
981
|
import { spawn as spawn2 } from "child_process";
|
|
@@ -695,7 +991,7 @@ function openBrowser2(url) {
|
|
|
695
991
|
}
|
|
696
992
|
async function login(opts) {
|
|
697
993
|
const verifier = b64url(randomBytes(32));
|
|
698
|
-
const challenge = b64url(
|
|
994
|
+
const challenge = b64url(createHash2("sha256").update(verifier).digest());
|
|
699
995
|
const result = opts.noBrowser ? await headlessLogin(verifier, challenge) : await browserLogin(verifier, challenge);
|
|
700
996
|
saveConfig({
|
|
701
997
|
...loadConfig(),
|
|
@@ -822,9 +1118,9 @@ function renderEvent(event, data) {
|
|
|
822
1118
|
|
|
823
1119
|
// src/subscriptions.ts
|
|
824
1120
|
import { spawn as spawn3 } from "child_process";
|
|
825
|
-
import { existsSync as
|
|
826
|
-
import { homedir as
|
|
827
|
-
import { join as
|
|
1121
|
+
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
1122
|
+
import { homedir as homedir4 } from "os";
|
|
1123
|
+
import { join as join4 } from "path";
|
|
828
1124
|
import { createInterface as createInterface2 } from "readline";
|
|
829
1125
|
var ALIASES = {
|
|
830
1126
|
claude: "anthropic",
|
|
@@ -902,12 +1198,12 @@ async function connectClaude() {
|
|
|
902
1198
|
console.log(`Connected Claude subscription${expiry}. Powers new eligible agents.`);
|
|
903
1199
|
}
|
|
904
1200
|
async function connectChatgpt(opts) {
|
|
905
|
-
const codexHome = process.env.CODEX_HOME ||
|
|
906
|
-
const path =
|
|
907
|
-
if (!
|
|
1201
|
+
const codexHome = process.env.CODEX_HOME || join4(homedir4(), ".codex");
|
|
1202
|
+
const path = join4(codexHome, "auth.json");
|
|
1203
|
+
if (!existsSync4(path)) {
|
|
908
1204
|
throw new ApiError(404, "no_codex_auth", `no ${path} \u2014 run \`codex login\` first`);
|
|
909
1205
|
}
|
|
910
|
-
const raw =
|
|
1206
|
+
const raw = readFileSync6(path, "utf8");
|
|
911
1207
|
let accountId;
|
|
912
1208
|
try {
|
|
913
1209
|
const parsed = JSON.parse(raw);
|
|
@@ -1427,7 +1723,7 @@ secrets.command("unset <id> <key>").action(
|
|
|
1427
1723
|
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(
|
|
1428
1724
|
run(async (opts) => {
|
|
1429
1725
|
const path = resolve3(opts.file);
|
|
1430
|
-
if (!
|
|
1726
|
+
if (!existsSync5(path)) {
|
|
1431
1727
|
const name = basename3(process.cwd()).replace(/[^a-zA-Z0-9 _-]/g, "") || "my-agent";
|
|
1432
1728
|
scaffoldAgentFile(path, name);
|
|
1433
1729
|
console.log(`Scaffolded ${opts.file} \u2014 edit it anytime; re-apply with \`sky deploy\`.`);
|
|
@@ -1493,6 +1789,103 @@ program.command("clone").description("clone your local agent (Claude Code, \u202
|
|
|
1493
1789
|
async (opts) => clone(opts)
|
|
1494
1790
|
)
|
|
1495
1791
|
);
|
|
1792
|
+
function formatRelative(msAgo) {
|
|
1793
|
+
const seconds = Math.round(msAgo / 1e3);
|
|
1794
|
+
if (seconds < 90) return "just now";
|
|
1795
|
+
const minutes = Math.round(seconds / 60);
|
|
1796
|
+
if (minutes < 90) return `${minutes}m ago`;
|
|
1797
|
+
const hours = Math.round(minutes / 60);
|
|
1798
|
+
if (hours < 48) return `${hours}h ago`;
|
|
1799
|
+
return `${Math.round(hours / 24)}d ago`;
|
|
1800
|
+
}
|
|
1801
|
+
program.command("import").description("import a local Claude Code or Codex session to AgentSky and continue it in the cloud").option("--all", "import all found sessions up to --limit").option("-y, --yes", "confirm transfer of the selected conversations without prompting").option("--limit <n>", "maximum sessions to list or import", "10").option("--session <id>", "import one specific session by its ID").option("--harness <type>", "claude_code | codex | all (default: all)").option("--url <base>", "agent-use app URL (default: derived from SKY_API_URL or AGENT_USE_URL)").action(
|
|
1802
|
+
run(
|
|
1803
|
+
async (opts) => {
|
|
1804
|
+
const limit = Math.max(1, Math.min(50, parseInt(opts.limit ?? "10", 10) || 10));
|
|
1805
|
+
if (opts.harness && !["claude_code", "codex", "all"].includes(opts.harness)) throw new Error("Use --harness claude_code, codex, or all.");
|
|
1806
|
+
const harness = opts.harness ?? "all";
|
|
1807
|
+
const base = opts.url ?? agentUseBase();
|
|
1808
|
+
const candidates = opts.session ? listRecentSessions(harness === "all" ? void 0 : harness, Number.POSITIVE_INFINITY).filter(
|
|
1809
|
+
(s) => s.sessionId === opts.session
|
|
1810
|
+
) : listRecentSessions(harness === "all" ? void 0 : harness, limit);
|
|
1811
|
+
if (candidates.length === 0) {
|
|
1812
|
+
if (opts.session) {
|
|
1813
|
+
throw new Error(`Session "${opts.session}" not found in ~/.claude or ~/.codex.`);
|
|
1814
|
+
}
|
|
1815
|
+
console.log(
|
|
1816
|
+
"No recent sessions found. Make sure you have run Claude Code (sessions in ~/.claude) or Codex (sessions in ~/.codex)."
|
|
1817
|
+
);
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
let toImport;
|
|
1821
|
+
if (opts.all || opts.session || candidates.length === 1) {
|
|
1822
|
+
toImport = candidates;
|
|
1823
|
+
} else {
|
|
1824
|
+
if (!process.stdin.isTTY) throw new Error("Use --session or --all to select conversations in a non-interactive terminal.");
|
|
1825
|
+
console.log("\nRecent local sessions:\n");
|
|
1826
|
+
candidates.forEach((s, i) => {
|
|
1827
|
+
const label = s.title ? `"${s.title.slice(0, 70)}"` : "(no title)";
|
|
1828
|
+
const ago = formatRelative(Date.now() - s.mtimeMs);
|
|
1829
|
+
const harnessBadge = s.harness === "codex" ? "[Codex]" : "[Claude Code]";
|
|
1830
|
+
const ws = s.workspace.replace(process.env.HOME ?? "", "~").slice(0, 60);
|
|
1831
|
+
console.log(` ${i + 1}. ${harnessBadge} ${ago} ${label}`);
|
|
1832
|
+
console.log(` ${ws}`);
|
|
1833
|
+
});
|
|
1834
|
+
console.log();
|
|
1835
|
+
const rl = createInterface3({ input: process.stdin, output: process.stdout });
|
|
1836
|
+
const answer = await new Promise(
|
|
1837
|
+
(resolve4) => rl.question(
|
|
1838
|
+
`Import which session? [1-${candidates.length}, or comma-separated, or "all"]: `,
|
|
1839
|
+
(a) => {
|
|
1840
|
+
rl.close();
|
|
1841
|
+
resolve4(a.trim());
|
|
1842
|
+
}
|
|
1843
|
+
)
|
|
1844
|
+
);
|
|
1845
|
+
if (answer.toLowerCase() === "all") {
|
|
1846
|
+
toImport = candidates;
|
|
1847
|
+
} else {
|
|
1848
|
+
const picks = answer.split(",").map((s) => parseInt(s.trim(), 10) - 1).filter((n) => !isNaN(n) && n >= 0 && n < candidates.length);
|
|
1849
|
+
if (picks.length === 0) {
|
|
1850
|
+
console.log("Nothing selected.");
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
toImport = picks.map((i) => candidates[i]);
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
toImport = [...new Map(toImport.map((session2) => [`${session2.harness}:${session2.sessionId}`, session2])).values()];
|
|
1857
|
+
console.log(`Selected ${toImport.length} conversation(s), ${toImport.reduce((count, session2) => count + session2.files.length, 0)} transcript file(s). Destination: ${base}`);
|
|
1858
|
+
console.log("The recorded conversation is redacted locally. Local project files and login credentials are not transferred.");
|
|
1859
|
+
if (!opts.yes) {
|
|
1860
|
+
if (!process.stdin.isTTY) throw new Error("Use --yes to confirm this transfer in a non-interactive terminal.");
|
|
1861
|
+
const prompt = createInterface3({ input: process.stdin, output: process.stdout });
|
|
1862
|
+
const answer = await new Promise((resolve4) => prompt.question("Upload these selected conversations? [y/N] ", (value) => {
|
|
1863
|
+
prompt.close();
|
|
1864
|
+
resolve4(value);
|
|
1865
|
+
}));
|
|
1866
|
+
if (!/^y(?:es)?$/i.test(answer.trim())) {
|
|
1867
|
+
console.log("Cancelled. Nothing uploaded.");
|
|
1868
|
+
return;
|
|
1869
|
+
}
|
|
1870
|
+
}
|
|
1871
|
+
for (const session2 of toImport) {
|
|
1872
|
+
const label = session2.title ? `"${session2.title.slice(0, 60)}"` : session2.sessionId;
|
|
1873
|
+
process.stdout.write(`
|
|
1874
|
+
Importing ${label} \u2026
|
|
1875
|
+
`);
|
|
1876
|
+
const result = await importSession({
|
|
1877
|
+
session: session2,
|
|
1878
|
+
agentUseUrl: base,
|
|
1879
|
+
onProgress: (msg) => process.stdout.write(` ${msg}
|
|
1880
|
+
`)
|
|
1881
|
+
});
|
|
1882
|
+
console.log(` \u2713 Imported. Continue here:
|
|
1883
|
+
${result.url}
|
|
1884
|
+
`);
|
|
1885
|
+
}
|
|
1886
|
+
}
|
|
1887
|
+
)
|
|
1888
|
+
);
|
|
1496
1889
|
var files = program.command("files").description("files: upload once, reference by id from any message");
|
|
1497
1890
|
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(
|
|
1498
1891
|
run(async (path, opts) => {
|
package/dist/products.d.ts
CHANGED
|
@@ -19,6 +19,15 @@ declare const agentAddonsSchema: z.ZodObject<{
|
|
|
19
19
|
sha: z.ZodString;
|
|
20
20
|
}, z.core.$strip>], "type">>>;
|
|
21
21
|
mount_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
22
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
23
|
+
type: z.ZodLiteral<"memory_store">;
|
|
24
|
+
memory_store_id: z.ZodString;
|
|
25
|
+
access: z.ZodEnum<{
|
|
26
|
+
read_only: "read_only";
|
|
27
|
+
read_write: "read_write";
|
|
28
|
+
}>;
|
|
29
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
30
|
+
mount_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
22
31
|
}, z.core.$strip>], "type">>>;
|
|
23
32
|
instructions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
24
33
|
name: z.ZodString;
|
|
@@ -52,6 +61,7 @@ declare const agentAddonsSchema: z.ZodObject<{
|
|
|
52
61
|
"exa.contents": "exa.contents";
|
|
53
62
|
"tinyfish.fetch": "tinyfish.fetch";
|
|
54
63
|
"tinyfish.browser": "tinyfish.browser";
|
|
64
|
+
"tinyfish.agent": "tinyfish.agent";
|
|
55
65
|
"dataforseo.serp": "dataforseo.serp";
|
|
56
66
|
"dataforseo.ai-keywords": "dataforseo.ai-keywords";
|
|
57
67
|
"dataforseo.llm-responses": "dataforseo.llm-responses";
|
|
@@ -141,6 +151,15 @@ declare const createProductAgentSchema: z.ZodObject<{
|
|
|
141
151
|
sha: z.ZodString;
|
|
142
152
|
}, z.core.$strip>], "type">>>;
|
|
143
153
|
mount_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
154
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
155
|
+
type: z.ZodLiteral<"memory_store">;
|
|
156
|
+
memory_store_id: z.ZodString;
|
|
157
|
+
access: z.ZodEnum<{
|
|
158
|
+
read_only: "read_only";
|
|
159
|
+
read_write: "read_write";
|
|
160
|
+
}>;
|
|
161
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
162
|
+
mount_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
144
163
|
}, z.core.$strip>], "type">>>;
|
|
145
164
|
instructions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
146
165
|
name: z.ZodString;
|
|
@@ -174,6 +193,7 @@ declare const createProductAgentSchema: z.ZodObject<{
|
|
|
174
193
|
"exa.contents": "exa.contents";
|
|
175
194
|
"tinyfish.fetch": "tinyfish.fetch";
|
|
176
195
|
"tinyfish.browser": "tinyfish.browser";
|
|
196
|
+
"tinyfish.agent": "tinyfish.agent";
|
|
177
197
|
"dataforseo.serp": "dataforseo.serp";
|
|
178
198
|
"dataforseo.ai-keywords": "dataforseo.ai-keywords";
|
|
179
199
|
"dataforseo.llm-responses": "dataforseo.llm-responses";
|
|
@@ -263,6 +283,15 @@ declare const prepareProductSessionSchema: z.ZodObject<{
|
|
|
263
283
|
sha: z.ZodString;
|
|
264
284
|
}, z.core.$strip>], "type">>>;
|
|
265
285
|
mount_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
286
|
+
}, z.core.$strip>, z.ZodObject<{
|
|
287
|
+
type: z.ZodLiteral<"memory_store">;
|
|
288
|
+
memory_store_id: z.ZodString;
|
|
289
|
+
access: z.ZodEnum<{
|
|
290
|
+
read_only: "read_only";
|
|
291
|
+
read_write: "read_write";
|
|
292
|
+
}>;
|
|
293
|
+
instructions: z.ZodOptional<z.ZodString>;
|
|
294
|
+
mount_path: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
266
295
|
}, z.core.$strip>], "type">>>;
|
|
267
296
|
instructions: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
268
297
|
name: z.ZodString;
|
|
@@ -296,6 +325,7 @@ declare const prepareProductSessionSchema: z.ZodObject<{
|
|
|
296
325
|
"exa.contents": "exa.contents";
|
|
297
326
|
"tinyfish.fetch": "tinyfish.fetch";
|
|
298
327
|
"tinyfish.browser": "tinyfish.browser";
|
|
328
|
+
"tinyfish.agent": "tinyfish.agent";
|
|
299
329
|
"dataforseo.serp": "dataforseo.serp";
|
|
300
330
|
"dataforseo.ai-keywords": "dataforseo.ai-keywords";
|
|
301
331
|
"dataforseo.llm-responses": "dataforseo.llm-responses";
|
package/dist/products.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
external_exports,
|
|
3
3
|
messagePartSchema
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FSAF6HIL.js";
|
|
5
5
|
|
|
6
6
|
// ../../packages/chat/src/stream.ts
|
|
7
7
|
async function* parseSseStream(body) {
|
|
@@ -104,7 +104,7 @@ function podMessageBody(messageId, turn, resourcesRevision = 0) {
|
|
|
104
104
|
};
|
|
105
105
|
return { id: messageId, ...envelope, parts: legacyPartsOf(envelope) };
|
|
106
106
|
}
|
|
107
|
-
return { id: messageId, parts: turn.parts };
|
|
107
|
+
return { id: messageId, parts: turn.parts, ...resourcesRevision > 0 ? { resources_revision: resourcesRevision } : {} };
|
|
108
108
|
}
|
|
109
109
|
var UNREACHABLE_CODES = /* @__PURE__ */ new Set([
|
|
110
110
|
"ECONNREFUSED",
|
|
@@ -223,7 +223,7 @@ async function postSessionEvents(args) {
|
|
|
223
223
|
`/api/v1/sessions/${encodeURIComponent(args.sessionId)}/events`,
|
|
224
224
|
normalizeBaseUrl(args.baseUrl)
|
|
225
225
|
);
|
|
226
|
-
const body = JSON.stringify({ events: args.events });
|
|
226
|
+
const body = JSON.stringify({ events: args.events, ...args.requireIdle ? { requireIdle: true } : {} });
|
|
227
227
|
const send = async (token) => fetch(url, {
|
|
228
228
|
method: "POST",
|
|
229
229
|
headers: {
|