primcli 1.29.0 → 1.31.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/README.md +130 -0
- package/dist/{cli-config-BKso8oJu.js → cli-config-BvflSzQe.js} +456 -12
- package/dist/oclif/index.js +29943 -13596
- package/dist/oclif/root-signup-hint.js +1 -1
- package/man/primitive.1 +74 -0
- package/package.json +13 -5
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { Errors } from "@oclif/core";
|
|
2
|
-
import { chmodSync, mkdirSync, readFileSync, renameSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
3
|
-
import { randomUUID } from "node:crypto";
|
|
4
|
-
import { join } from "node:path";
|
|
2
|
+
import { chmodSync, closeSync, fsyncSync, lstatSync, mkdirSync, mkdtempSync, openSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, statSync, unlinkSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { dirname, join, win32 } from "node:path";
|
|
5
|
+
import { setTimeout as setTimeout$1 } from "node:timers/promises";
|
|
6
|
+
import { execFileSync } from "node:child_process";
|
|
5
7
|
//#region ../packages/api-core/src/api/core/bodySerializer.gen.ts
|
|
6
8
|
const jsonBodySerializer = { bodySerializer: (body) => JSON.stringify(body, (_key, value) => typeof value === "bigint" ? value.toString() : value) };
|
|
7
9
|
//#endregion
|
|
@@ -636,6 +638,430 @@ var PrimitiveApiClient = class {
|
|
|
636
638
|
}
|
|
637
639
|
};
|
|
638
640
|
//#endregion
|
|
641
|
+
//#region src/oclif/listen-state.ts
|
|
642
|
+
var ListenStateError = class extends Error {};
|
|
643
|
+
function normalizeListenOrigin(value) {
|
|
644
|
+
const url = new URL(value);
|
|
645
|
+
if (!["http:", "https:"].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new ListenStateError("The API base URL must be an HTTP(S) URL without credentials, query, or fragment.");
|
|
646
|
+
return `${url.origin}${url.pathname.replace(/\/+$/, "")}`;
|
|
647
|
+
}
|
|
648
|
+
function listenIdentity(origin, accountId) {
|
|
649
|
+
return createHash("sha256").update(JSON.stringify([normalizeListenOrigin(origin), accountId])).digest("hex");
|
|
650
|
+
}
|
|
651
|
+
function privateDirectory(path) {
|
|
652
|
+
mkdirSync(path, {
|
|
653
|
+
recursive: true,
|
|
654
|
+
mode: 448
|
|
655
|
+
});
|
|
656
|
+
if (!lstatSync(path).isDirectory() || lstatSync(path).isSymbolicLink()) throw new ListenStateError("Listener state must be a private directory.");
|
|
657
|
+
chmodSync(path, 448);
|
|
658
|
+
}
|
|
659
|
+
function processAlive(pid) {
|
|
660
|
+
try {
|
|
661
|
+
process.kill(pid, 0);
|
|
662
|
+
return true;
|
|
663
|
+
} catch (error) {
|
|
664
|
+
return error.code !== "ESRCH";
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
function listenProcessIdentity(pid) {
|
|
668
|
+
if (!Number.isSafeInteger(pid) || pid < 1) return null;
|
|
669
|
+
try {
|
|
670
|
+
if (process.platform === "linux") {
|
|
671
|
+
const boot = readFileSync("/proc/sys/kernel/random/boot_id", "utf8").trim();
|
|
672
|
+
const stat = readFileSync(`/proc/${pid}/stat`, "utf8");
|
|
673
|
+
const end = stat.lastIndexOf(")");
|
|
674
|
+
const started = stat.slice(end + 1).trim().split(/\s+/)[19];
|
|
675
|
+
if (!/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/.test(boot) || !stat.startsWith(`${pid} (`) || end < 0 || !started || !/^\d+$/.test(started)) return null;
|
|
676
|
+
return `linux:${boot}:${started}`;
|
|
677
|
+
}
|
|
678
|
+
if (process.platform === "darwin") {
|
|
679
|
+
const options = {
|
|
680
|
+
encoding: "utf8",
|
|
681
|
+
timeout: 1e3,
|
|
682
|
+
maxBuffer: 4096,
|
|
683
|
+
stdio: [
|
|
684
|
+
"ignore",
|
|
685
|
+
"pipe",
|
|
686
|
+
"ignore"
|
|
687
|
+
],
|
|
688
|
+
env: {
|
|
689
|
+
LC_ALL: "C",
|
|
690
|
+
TZ: "UTC",
|
|
691
|
+
PATH: "/usr/bin:/bin:/usr/sbin:/sbin"
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
const boot = execFileSync("/usr/sbin/sysctl", ["-n", "kern.boottime"], options);
|
|
695
|
+
const bootMatch = /\{\s*sec\s*=\s*(\d+),\s*usec\s*=\s*(\d+)\s*\}/.exec(boot);
|
|
696
|
+
const started = execFileSync("/bin/ps", [
|
|
697
|
+
"-p",
|
|
698
|
+
String(pid),
|
|
699
|
+
"-o",
|
|
700
|
+
"lstart="
|
|
701
|
+
], options).trim().replace(/\s+/g, " ");
|
|
702
|
+
if (!bootMatch || !/^[A-Z][a-z]{2} [A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2} \d{4}$/.test(started)) return null;
|
|
703
|
+
return `darwin:${bootMatch[1]}:${bootMatch[2]}:${started}`;
|
|
704
|
+
}
|
|
705
|
+
if (process.platform === "win32") {
|
|
706
|
+
const root = process.env.SystemRoot;
|
|
707
|
+
if (!root || !win32.isAbsolute(root)) return null;
|
|
708
|
+
const started = execFileSync(win32.join(root, "System32", "WindowsPowerShell", "v1.0", "powershell.exe"), [
|
|
709
|
+
"-NoLogo",
|
|
710
|
+
"-NoProfile",
|
|
711
|
+
"-NonInteractive",
|
|
712
|
+
"-Command",
|
|
713
|
+
`$ErrorActionPreference='Stop'; (Get-Process -Id ${pid} -ErrorAction Stop).StartTime.ToUniversalTime().Ticks.ToString([System.Globalization.CultureInfo]::InvariantCulture)`
|
|
714
|
+
], {
|
|
715
|
+
encoding: "utf8",
|
|
716
|
+
timeout: 2e3,
|
|
717
|
+
maxBuffer: 4096,
|
|
718
|
+
windowsHide: true,
|
|
719
|
+
stdio: [
|
|
720
|
+
"ignore",
|
|
721
|
+
"pipe",
|
|
722
|
+
"ignore"
|
|
723
|
+
]
|
|
724
|
+
}).trim();
|
|
725
|
+
return /^\d{1,20}$/.test(started) ? `win32:${started}` : null;
|
|
726
|
+
}
|
|
727
|
+
} catch {}
|
|
728
|
+
return null;
|
|
729
|
+
}
|
|
730
|
+
function ownerIdentity(path) {
|
|
731
|
+
try {
|
|
732
|
+
const stat = lstatSync(path);
|
|
733
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.size > 1024) return null;
|
|
734
|
+
const saved = JSON.parse(readFileSync(path, "utf8"));
|
|
735
|
+
if (!saved || typeof saved !== "object" || !("version" in saved) || saved.version !== 1 || !("identity" in saved) || typeof saved.identity !== "string") return null;
|
|
736
|
+
return /^(?:linux:[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}:\d+|darwin:\d+:\d+:[A-Z][a-z]{2} [A-Z][a-z]{2} \d{1,2} \d{2}:\d{2}:\d{2} \d{4}|win32:\d{1,20})$/.test(saved.identity) ? saved.identity : null;
|
|
737
|
+
} catch {
|
|
738
|
+
return null;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
function removeOwner(path, owner) {
|
|
742
|
+
try {
|
|
743
|
+
unlinkSync(join(path, owner));
|
|
744
|
+
} catch (error) {
|
|
745
|
+
if (error.code !== "ENOENT") throw error;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
rmdirSync(path);
|
|
749
|
+
} catch (error) {
|
|
750
|
+
if (![
|
|
751
|
+
"ENOENT",
|
|
752
|
+
"ENOTEMPTY",
|
|
753
|
+
"EEXIST"
|
|
754
|
+
].includes(error.code ?? "")) throw error;
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
function acquireListenLock(directory, scope) {
|
|
758
|
+
privateDirectory(directory);
|
|
759
|
+
const path = join(directory, `${createHash("sha256").update(scope).digest("hex")}.lock`);
|
|
760
|
+
const owner = `${process.pid}-${randomUUID()}`;
|
|
761
|
+
const candidate = mkdtempSync(join(directory, ".claim-"));
|
|
762
|
+
chmodSync(candidate, 448);
|
|
763
|
+
writeFileSync(join(candidate, owner), JSON.stringify({
|
|
764
|
+
version: 1,
|
|
765
|
+
identity: listenProcessIdentity(process.pid)
|
|
766
|
+
}), {
|
|
767
|
+
mode: 384,
|
|
768
|
+
flag: "wx"
|
|
769
|
+
});
|
|
770
|
+
let installed = false;
|
|
771
|
+
try {
|
|
772
|
+
for (let attempt = 0; attempt < 8; attempt++) {
|
|
773
|
+
try {
|
|
774
|
+
renameSync(candidate, path);
|
|
775
|
+
installed = true;
|
|
776
|
+
break;
|
|
777
|
+
} catch (error) {
|
|
778
|
+
if (!["EEXIST", "ENOTEMPTY"].includes(error.code ?? "")) throw error;
|
|
779
|
+
}
|
|
780
|
+
let entries;
|
|
781
|
+
try {
|
|
782
|
+
entries = readdirSync(path);
|
|
783
|
+
} catch (error) {
|
|
784
|
+
if (error.code === "ENOENT") continue;
|
|
785
|
+
throw error;
|
|
786
|
+
}
|
|
787
|
+
if (entries.length === 0) continue;
|
|
788
|
+
const match = entries.length === 1 ? /^(\d+)-[a-f0-9-]{36}$/.exec(entries[0] ?? "") : null;
|
|
789
|
+
const pid = match ? Number(match[1]) : null;
|
|
790
|
+
let stale = pid !== null && !processAlive(pid);
|
|
791
|
+
if (!stale && pid !== null) {
|
|
792
|
+
const saved = ownerIdentity(join(path, entries[0] ?? ""));
|
|
793
|
+
const current = saved === null ? null : listenProcessIdentity(pid);
|
|
794
|
+
stale = saved !== null && current !== null && saved !== current;
|
|
795
|
+
}
|
|
796
|
+
if (!stale) throw new ListenStateError("Another listener is using this subscription, or its owner cannot be verified. Stop the original listener before reconnecting. To use a separate subscription instead, run: primitive listen --subscription local-" + randomUUID());
|
|
797
|
+
removeOwner(path, entries[0] ?? "");
|
|
798
|
+
}
|
|
799
|
+
if (!installed) throw new ListenStateError("Subscription lock changed concurrently. Retry the command.");
|
|
800
|
+
} finally {
|
|
801
|
+
if (!installed) removeOwner(candidate, owner);
|
|
802
|
+
}
|
|
803
|
+
let released = false;
|
|
804
|
+
return () => {
|
|
805
|
+
if (!released) {
|
|
806
|
+
released = true;
|
|
807
|
+
removeOwner(path, owner);
|
|
808
|
+
}
|
|
809
|
+
};
|
|
810
|
+
}
|
|
811
|
+
function resolveListenSubscription(configDir, identity, explicit) {
|
|
812
|
+
const directory = join(configDir, "listen", identity);
|
|
813
|
+
privateDirectory(directory);
|
|
814
|
+
let name = explicit;
|
|
815
|
+
if (name === void 0) {
|
|
816
|
+
const unlock = acquireListenLock(directory, "default-name");
|
|
817
|
+
try {
|
|
818
|
+
const file = join(directory, "default.json");
|
|
819
|
+
try {
|
|
820
|
+
if (!lstatSync(file).isFile() || lstatSync(file).isSymbolicLink()) throw new ListenStateError("Invalid listener identity file.");
|
|
821
|
+
const saved = JSON.parse(readFileSync(file, "utf8"));
|
|
822
|
+
if (typeof saved.name !== "string" || !/^local-[a-f0-9-]{36}$/.test(saved.name)) throw new ListenStateError("Invalid listener identity file.");
|
|
823
|
+
name = saved.name;
|
|
824
|
+
} catch (error) {
|
|
825
|
+
if (error.code !== "ENOENT") throw error;
|
|
826
|
+
name = `local-${randomUUID()}`;
|
|
827
|
+
const temporary = join(directory, `.state-${randomUUID()}`);
|
|
828
|
+
const fd = openSync(temporary, "wx", 384);
|
|
829
|
+
try {
|
|
830
|
+
writeFileSync(fd, `${JSON.stringify({ name })}\n`);
|
|
831
|
+
fsyncSync(fd);
|
|
832
|
+
} finally {
|
|
833
|
+
closeSync(fd);
|
|
834
|
+
}
|
|
835
|
+
renameSync(temporary, file);
|
|
836
|
+
}
|
|
837
|
+
if (process.platform !== "win32") for (const path of [
|
|
838
|
+
directory,
|
|
839
|
+
join(configDir, "listen"),
|
|
840
|
+
configDir
|
|
841
|
+
]) {
|
|
842
|
+
const directoryFd = openSync(path, "r");
|
|
843
|
+
try {
|
|
844
|
+
fsyncSync(directoryFd);
|
|
845
|
+
} finally {
|
|
846
|
+
closeSync(directoryFd);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
} finally {
|
|
850
|
+
unlock();
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
if (!name || name.length > 64 || !/^[a-zA-Z0-9][a-zA-Z0-9_-]*$/.test(name)) throw new ListenStateError("Use a subscription name of 1–64 letters, digits, underscores, or hyphens.");
|
|
854
|
+
return {
|
|
855
|
+
name,
|
|
856
|
+
release: acquireListenLock(directory, `subscription:${name}`)
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region src/oclif/shared-mail-files.ts
|
|
861
|
+
var SharedMailStateError = class extends ListenStateError {};
|
|
862
|
+
const invalidSharedMail = () => new SharedMailStateError("Shared mail state is invalid or inconsistent. Preserve it before retrying to avoid duplicate delivery.");
|
|
863
|
+
function privateMailPermissions(info, platform = process.platform) {
|
|
864
|
+
return platform === "win32" || info.uid === process.getuid?.() && (info.mode & 63) === 0;
|
|
865
|
+
}
|
|
866
|
+
function privateMailDirectory(path, create = false) {
|
|
867
|
+
if (create) {
|
|
868
|
+
const created = mkdirSync(path, {
|
|
869
|
+
recursive: true,
|
|
870
|
+
mode: 448
|
|
871
|
+
});
|
|
872
|
+
if (created !== void 0) {
|
|
873
|
+
let current = path;
|
|
874
|
+
for (;;) {
|
|
875
|
+
syncMailDirectory(current);
|
|
876
|
+
syncMailDirectory(dirname(current));
|
|
877
|
+
if (current === created) break;
|
|
878
|
+
current = dirname(current);
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
const stat = lstatSync(path);
|
|
883
|
+
if (!stat.isDirectory() || !privateMailPermissions(stat)) throw invalidSharedMail();
|
|
884
|
+
}
|
|
885
|
+
function syncMailDirectory(path, platform = process.platform) {
|
|
886
|
+
if (platform === "win32") return;
|
|
887
|
+
const fd = openSync(path, "r");
|
|
888
|
+
try {
|
|
889
|
+
fsyncSync(fd);
|
|
890
|
+
} finally {
|
|
891
|
+
closeSync(fd);
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
function readMailJson(path, maxBytes = 16384) {
|
|
895
|
+
try {
|
|
896
|
+
const info = lstatSync(path);
|
|
897
|
+
if (!info.isFile() || !privateMailPermissions(info) || info.size > maxBytes) throw invalidSharedMail();
|
|
898
|
+
const value = JSON.parse(readFileSync(path, "utf8"));
|
|
899
|
+
if (value === null) throw invalidSharedMail();
|
|
900
|
+
return value;
|
|
901
|
+
} catch (error) {
|
|
902
|
+
if (error.code === "ENOENT") return null;
|
|
903
|
+
throw invalidSharedMail();
|
|
904
|
+
}
|
|
905
|
+
}
|
|
906
|
+
function writeMailJson(path, value) {
|
|
907
|
+
privateMailDirectory(dirname(path), true);
|
|
908
|
+
const temporary = join(dirname(path), `.write-${randomUUID()}`);
|
|
909
|
+
try {
|
|
910
|
+
const fd = openSync(temporary, "wx", 384);
|
|
911
|
+
try {
|
|
912
|
+
writeFileSync(fd, `${JSON.stringify(value)}\n`);
|
|
913
|
+
fsyncSync(fd);
|
|
914
|
+
} finally {
|
|
915
|
+
closeSync(fd);
|
|
916
|
+
}
|
|
917
|
+
renameSync(temporary, path);
|
|
918
|
+
syncMailDirectory(dirname(path));
|
|
919
|
+
} finally {
|
|
920
|
+
try {
|
|
921
|
+
unlinkSync(temporary);
|
|
922
|
+
} catch {}
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
function removeMailFile(path) {
|
|
926
|
+
try {
|
|
927
|
+
unlinkSync(path);
|
|
928
|
+
} catch (error) {
|
|
929
|
+
if (error.code !== "ENOENT") throw error;
|
|
930
|
+
}
|
|
931
|
+
syncMailDirectory(dirname(path));
|
|
932
|
+
}
|
|
933
|
+
function mailLockBusy(error) {
|
|
934
|
+
return error instanceof ListenStateError && /^(Another listener|Subscription lock changed)/.test(error.message);
|
|
935
|
+
}
|
|
936
|
+
async function withMailLock(directory, action, signal) {
|
|
937
|
+
const deadline = performance.now() + 5e3;
|
|
938
|
+
for (;;) {
|
|
939
|
+
signal?.throwIfAborted();
|
|
940
|
+
let release;
|
|
941
|
+
try {
|
|
942
|
+
release = acquireListenLock(directory, "shared-mail-state");
|
|
943
|
+
} catch (error) {
|
|
944
|
+
if (!mailLockBusy(error) || performance.now() >= deadline) throw error;
|
|
945
|
+
await setTimeout$1(25, void 0, { signal });
|
|
946
|
+
continue;
|
|
947
|
+
}
|
|
948
|
+
try {
|
|
949
|
+
signal?.throwIfAborted();
|
|
950
|
+
return action();
|
|
951
|
+
} finally {
|
|
952
|
+
release();
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
function mailObject(value, keys) {
|
|
957
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw invalidSharedMail();
|
|
958
|
+
const result = value;
|
|
959
|
+
if (Object.keys(result).length !== keys.length || keys.some((key) => !Object.hasOwn(result, key))) throw invalidSharedMail();
|
|
960
|
+
return result;
|
|
961
|
+
}
|
|
962
|
+
function mailId(value) {
|
|
963
|
+
if (typeof value !== "string" || !/^[a-f0-9]{8}(?:-[a-f0-9]{4}){3}-[a-f0-9]{12}$/i.test(value)) throw invalidSharedMail();
|
|
964
|
+
return value.toLowerCase();
|
|
965
|
+
}
|
|
966
|
+
function mailString(value, max = 256) {
|
|
967
|
+
if (typeof value !== "string" || !value || value.length > max || Array.from(value).some((character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127)) throw invalidSharedMail();
|
|
968
|
+
return value;
|
|
969
|
+
}
|
|
970
|
+
function mailAddress(value) {
|
|
971
|
+
const address = mailString(value, 254).toLowerCase();
|
|
972
|
+
if (!/^[^\s<>@]+@[^\s<>@]+\.[^\s<>@]+$/.test(address)) throw invalidSharedMail();
|
|
973
|
+
return address;
|
|
974
|
+
}
|
|
975
|
+
function mailTime(value) {
|
|
976
|
+
const time = mailString(value, 40);
|
|
977
|
+
if (!Number.isFinite(Date.parse(time))) throw invalidSharedMail();
|
|
978
|
+
return new Date(time).toISOString();
|
|
979
|
+
}
|
|
980
|
+
//#endregion
|
|
981
|
+
//#region src/oclif/connected-agent-profile.ts
|
|
982
|
+
const AGENT_PROFILE_ENV = "PRIMITIVE_AGENT_PROFILE";
|
|
983
|
+
var AgentConnectionSetupError = class extends Error {};
|
|
984
|
+
function requireDefaultLoginProfile() {
|
|
985
|
+
if (process.env["PRIMITIVE_AGENT_PROFILE"]?.trim()) throw new AgentConnectionSetupError("This command manages the default OAuth login. Unset PRIMITIVE_AGENT_PROFILE before using it; the connected-agent profile is unchanged.");
|
|
986
|
+
}
|
|
987
|
+
function agentProfileName(value) {
|
|
988
|
+
if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,62}$/.test(value)) throw new AgentConnectionSetupError("Agent profile must start with a letter or number and contain only letters, numbers, dots, underscores or hyphens (at most 63 characters).");
|
|
989
|
+
return value;
|
|
990
|
+
}
|
|
991
|
+
function connectedApiBaseUrl(value) {
|
|
992
|
+
if (value !== "https://api.primitive.dev/v1" && value !== "https://api.primitive-staging-1.com/v1") throw new AgentConnectionSetupError("Agent setup requires an official Primitive API origin.");
|
|
993
|
+
return value;
|
|
994
|
+
}
|
|
995
|
+
function agentProfilesDirectory(configDir) {
|
|
996
|
+
return join(configDir, "agent-connections");
|
|
997
|
+
}
|
|
998
|
+
function agentProfileDirectory(configDir, profileName) {
|
|
999
|
+
return join(agentProfilesDirectory(configDir), "profiles", agentProfileName(profileName));
|
|
1000
|
+
}
|
|
1001
|
+
function profilePath(configDir, profileName) {
|
|
1002
|
+
return join(agentProfileDirectory(configDir, profileName), "connection.json");
|
|
1003
|
+
}
|
|
1004
|
+
function parseConnectedAgentProfile(value) {
|
|
1005
|
+
try {
|
|
1006
|
+
const row = mailObject(value, [
|
|
1007
|
+
"version",
|
|
1008
|
+
"auth_method",
|
|
1009
|
+
"api_key",
|
|
1010
|
+
"api_base_url",
|
|
1011
|
+
"org_id",
|
|
1012
|
+
"agent_address",
|
|
1013
|
+
"owner_address",
|
|
1014
|
+
"invitation_hash",
|
|
1015
|
+
"created_at"
|
|
1016
|
+
]);
|
|
1017
|
+
const apiKey = mailString(row.api_key, 4096);
|
|
1018
|
+
const invitationHash = mailString(row.invitation_hash, 64);
|
|
1019
|
+
if (row.version !== 1 || row.auth_method !== "agent_connection" || !apiKey.startsWith("pconn_") || /\s/.test(apiKey) || !/^[a-f0-9]{64}$/.test(invitationHash)) throw new Error();
|
|
1020
|
+
return {
|
|
1021
|
+
version: 1,
|
|
1022
|
+
auth_method: "agent_connection",
|
|
1023
|
+
api_key: apiKey,
|
|
1024
|
+
api_base_url: connectedApiBaseUrl(row.api_base_url),
|
|
1025
|
+
org_id: mailId(row.org_id),
|
|
1026
|
+
agent_address: mailAddress(row.agent_address),
|
|
1027
|
+
owner_address: mailAddress(row.owner_address),
|
|
1028
|
+
invitation_hash: invitationHash,
|
|
1029
|
+
created_at: mailTime(row.created_at)
|
|
1030
|
+
};
|
|
1031
|
+
} catch {
|
|
1032
|
+
throw new AgentConnectionSetupError("Saved agent profile is invalid. Preserve its private files and request a fresh setup invitation.");
|
|
1033
|
+
}
|
|
1034
|
+
}
|
|
1035
|
+
function loadConnectedAgentProfile(configDir, profileName) {
|
|
1036
|
+
agentProfileName(profileName);
|
|
1037
|
+
try {
|
|
1038
|
+
privateMailDirectory(agentProfilesDirectory(configDir));
|
|
1039
|
+
privateMailDirectory(join(agentProfilesDirectory(configDir), "profiles"));
|
|
1040
|
+
privateMailDirectory(agentProfileDirectory(configDir, profileName));
|
|
1041
|
+
const value = readMailJson(profilePath(configDir, profileName));
|
|
1042
|
+
return value === null ? null : parseConnectedAgentProfile(value);
|
|
1043
|
+
} catch (error) {
|
|
1044
|
+
if (error.code === "ENOENT") return null;
|
|
1045
|
+
throw new AgentConnectionSetupError("Cannot read the private agent profile. Check its ownership and permissions without exposing its contents.");
|
|
1046
|
+
}
|
|
1047
|
+
}
|
|
1048
|
+
function saveConnectedAgentProfile(configDir, profileName, profile) {
|
|
1049
|
+
const validated = parseConnectedAgentProfile(profile);
|
|
1050
|
+
privateMailDirectory(agentProfilesDirectory(configDir), true);
|
|
1051
|
+
privateMailDirectory(join(agentProfilesDirectory(configDir), "profiles"), true);
|
|
1052
|
+
privateMailDirectory(agentProfileDirectory(configDir, profileName), true);
|
|
1053
|
+
writeMailJson(profilePath(configDir, profileName), validated);
|
|
1054
|
+
}
|
|
1055
|
+
function connectedAgentIdentity(profileName, profile) {
|
|
1056
|
+
return {
|
|
1057
|
+
profileName,
|
|
1058
|
+
orgId: profile.org_id,
|
|
1059
|
+
agentAddress: profile.agent_address,
|
|
1060
|
+
ownerAddress: profile.owner_address,
|
|
1061
|
+
apiBaseUrl: profile.api_base_url
|
|
1062
|
+
};
|
|
1063
|
+
}
|
|
1064
|
+
//#endregion
|
|
639
1065
|
//#region src/oclif/chat-state.ts
|
|
640
1066
|
const CHAT_STATE_FILE = "chat-state.json";
|
|
641
1067
|
const CHAT_STATE_VERSION = 2;
|
|
@@ -655,11 +1081,11 @@ function nonNegativeInteger(value) {
|
|
|
655
1081
|
function parseLocalId(value) {
|
|
656
1082
|
return typeof value === "number" && Number.isInteger(value) && value >= 0 && value <= Number.MAX_SAFE_INTEGER ? value : null;
|
|
657
1083
|
}
|
|
658
|
-
function chatStatePath(configDir) {
|
|
659
|
-
return join(configDir, CHAT_STATE_FILE);
|
|
1084
|
+
function chatStatePath(configDir, profileName = process.env["PRIMITIVE_AGENT_PROFILE"]?.trim() || null) {
|
|
1085
|
+
return join(profileName ? agentProfileDirectory(configDir, profileName) : configDir, CHAT_STATE_FILE);
|
|
660
1086
|
}
|
|
661
1087
|
function deleteChatState(configDir) {
|
|
662
|
-
rmSync(chatStatePath(configDir), { force: true });
|
|
1088
|
+
rmSync(chatStatePath(configDir, null), { force: true });
|
|
663
1089
|
}
|
|
664
1090
|
function chatConversationState(params) {
|
|
665
1091
|
return {
|
|
@@ -755,12 +1181,13 @@ function loadChatConversationByLocalId(configDir, localId) {
|
|
|
755
1181
|
return loadChatState(configDir)?.conversations.find((conversation) => conversation.local_id === localId) ?? null;
|
|
756
1182
|
}
|
|
757
1183
|
function saveChatState(configDir, state) {
|
|
758
|
-
|
|
1184
|
+
const path = chatStatePath(configDir);
|
|
1185
|
+
const directory = dirname(path);
|
|
1186
|
+
mkdirSync(directory, {
|
|
759
1187
|
mode: 448,
|
|
760
1188
|
recursive: true
|
|
761
1189
|
});
|
|
762
|
-
const
|
|
763
|
-
const tempPath = join(configDir, `${CHAT_STATE_FILE}.${process.pid}.${randomUUID()}.tmp`);
|
|
1190
|
+
const tempPath = join(directory, `${CHAT_STATE_FILE}.${process.pid}.${randomUUID()}.tmp`);
|
|
764
1191
|
try {
|
|
765
1192
|
writeFileSync(tempPath, `${JSON.stringify(state, null, 2)}\n`, { mode: 384 });
|
|
766
1193
|
renameSync(tempPath, path);
|
|
@@ -863,7 +1290,7 @@ function credentialsPath(configDir) {
|
|
|
863
1290
|
function credentialsLockPath(configDir) {
|
|
864
1291
|
return join(configDir, CREDENTIALS_LOCK_DIR);
|
|
865
1292
|
}
|
|
866
|
-
function normalize(url, fallback) {
|
|
1293
|
+
function normalize$1(url, fallback) {
|
|
867
1294
|
const trimmed = url?.trim();
|
|
868
1295
|
if (!trimmed) return fallback;
|
|
869
1296
|
return trimmed.replace(/\/+$/, "");
|
|
@@ -889,7 +1316,7 @@ function canonicalizeKnownApiBaseUrl(url) {
|
|
|
889
1316
|
return url;
|
|
890
1317
|
}
|
|
891
1318
|
function normalizeApiBaseUrl(url) {
|
|
892
|
-
return canonicalizeKnownApiBaseUrl(normalize(url, DEFAULT_API_BASE_URL));
|
|
1319
|
+
return canonicalizeKnownApiBaseUrl(normalize$1(url, DEFAULT_API_BASE_URL));
|
|
893
1320
|
}
|
|
894
1321
|
function cliAccessTokenExpiresAt(expiresInSeconds, now = Date.now) {
|
|
895
1322
|
return new Date(now() + expiresInSeconds * 1e3).toISOString();
|
|
@@ -920,6 +1347,7 @@ function loadCliCredentials(configDir) {
|
|
|
920
1347
|
}
|
|
921
1348
|
}
|
|
922
1349
|
function saveCliCredentials(configDir, credentials) {
|
|
1350
|
+
requireDefaultLoginProfile();
|
|
923
1351
|
mkdirSync(configDir, {
|
|
924
1352
|
mode: 448,
|
|
925
1353
|
recursive: true
|
|
@@ -937,10 +1365,12 @@ function saveCliCredentials(configDir, credentials) {
|
|
|
937
1365
|
}
|
|
938
1366
|
}
|
|
939
1367
|
function deleteCliCredentials(configDir) {
|
|
1368
|
+
requireDefaultLoginProfile();
|
|
940
1369
|
rmSync(credentialsPath(configDir), { force: true });
|
|
941
1370
|
deleteChatState(configDir);
|
|
942
1371
|
}
|
|
943
1372
|
function saveSignupCredentials(params) {
|
|
1373
|
+
requireDefaultLoginProfile();
|
|
944
1374
|
deleteChatState(params.configDir);
|
|
945
1375
|
saveCliCredentials(params.configDir, {
|
|
946
1376
|
access_token: params.signup.access_token,
|
|
@@ -1051,6 +1481,7 @@ function credentialsLockInProgressMessage(lockPath) {
|
|
|
1051
1481
|
return `Another Primitive CLI credential operation is already in progress. Wait for it to finish, then retry. If no Primitive auth command is still running, run \`primitive logout --force\` to clear local CLI auth state and remove ${lockPath}.`;
|
|
1052
1482
|
}
|
|
1053
1483
|
function acquireCliCredentialsLock(configDir, options = {}) {
|
|
1484
|
+
requireDefaultLoginProfile();
|
|
1054
1485
|
mkdirSync(configDir, {
|
|
1055
1486
|
mode: 448,
|
|
1056
1487
|
recursive: true
|
|
@@ -1133,6 +1564,19 @@ function hasStoredCliLogin(configDir) {
|
|
|
1133
1564
|
function resolveCliAuth(params) {
|
|
1134
1565
|
const apiKey = params.apiKey?.trim();
|
|
1135
1566
|
const apiBaseUrl = normalizeApiBaseUrl(params.apiBaseUrl);
|
|
1567
|
+
const profileName = (params.env ?? process.env)["PRIMITIVE_AGENT_PROFILE"]?.trim() || void 0;
|
|
1568
|
+
if (profileName !== void 0) {
|
|
1569
|
+
const profile = loadConnectedAgentProfile(params.configDir, profileName);
|
|
1570
|
+
if (!profile) throw new AgentConnectionSetupError("Selected agent profile is not configured. No fallback login will be used.");
|
|
1571
|
+
if (apiKey && apiKey !== profile.api_key || params.apiBaseUrl !== void 0 && apiBaseUrl !== profile.api_base_url) throw new AgentConnectionSetupError("Explicit credentials or API origin conflict with the selected agent profile.");
|
|
1572
|
+
return {
|
|
1573
|
+
apiKey: profile.api_key,
|
|
1574
|
+
apiBaseUrl: profile.api_base_url,
|
|
1575
|
+
credentials: null,
|
|
1576
|
+
connectedAgent: connectedAgentIdentity(profileName, profile),
|
|
1577
|
+
source: "connected-profile"
|
|
1578
|
+
};
|
|
1579
|
+
}
|
|
1136
1580
|
if (apiKey) return {
|
|
1137
1581
|
apiKey,
|
|
1138
1582
|
apiBaseUrl,
|
|
@@ -1333,4 +1777,4 @@ function redactCliEnvironment(environment) {
|
|
|
1333
1777
|
};
|
|
1334
1778
|
}
|
|
1335
1779
|
//#endregion
|
|
1336
|
-
export { saveActiveChatState as A, resolveCliAuth as C, deleteChatState as D, chatStatePath as E,
|
|
1780
|
+
export { syncMailDirectory as $, saveActiveChatState as A, requireDefaultLoginProfile as B, resolveCliAuth as C, deleteChatState as D, chatStatePath as E, agentProfilesDirectory as F, mailId as G, SharedMailStateError as H, connectedAgentIdentity as I, mailString as J, mailLockBusy as K, connectedApiBaseUrl as L, AgentConnectionSetupError as M, agentProfileDirectory as N, loadActiveChatState as O, agentProfileName as P, removeMailFile as Q, loadConnectedAgentProfile as R, normalizeApiBaseUrl as S, saveSignupCredentials as T, invalidSharedMail as U, saveConnectedAgentProfile as V, mailAddress as W, privateMailDirectory as X, mailTime as Y, readMailJson as Z, deleteCliCredentials as _, normalizeCliEnvironmentName as a, listenProcessIdentity as at, hasStoredCliLogin as b, resolveConfigEnvironment as c, PrimitiveApiClient as ct, validateCliHeaderName as d, withMailLock as et, validateCliHeaderValue as f, credentialsPath as g, credentialsLockPath as h, loadCliConfig as i, listenIdentity as it, AGENT_PROFILE_ENV as j, loadChatConversationByLocalId as k, saveCliConfig as l, createClient as lt, cliAccessTokenExpiresAt as m, deleteCliConfig as n, ListenStateError as nt, redactCliEnvironment as o, normalizeListenOrigin as ot, acquireCliCredentialsLock as p, mailObject as q, emptyCliConfig as r, acquireListenLock as rt, removeCliEnvironment as s, resolveListenSubscription as st, DEFAULT_ENVIRONMENT as t, writeMailJson as tt, upsertCliEnvironment as u, createConfig as ut, deleteCliCredentialsLock as v, saveCliCredentials as w, loadCliCredentials as x, detectPrimitiveKeyEnvMisname as y, parseConnectedAgentProfile as z };
|