@finchtech/cli 0.3.3 → 0.3.5
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 +15 -3
- package/dist/finch.js +649 -519
- package/package.json +2 -2
package/dist/finch.js
CHANGED
|
@@ -6606,11 +6606,478 @@ var require_ajv = __commonJS({
|
|
|
6606
6606
|
}
|
|
6607
6607
|
});
|
|
6608
6608
|
|
|
6609
|
+
// src/state.ts
|
|
6610
|
+
import { execFile } from "node:child_process";
|
|
6611
|
+
import { constants } from "node:fs";
|
|
6612
|
+
import {
|
|
6613
|
+
chmod,
|
|
6614
|
+
lstat,
|
|
6615
|
+
mkdir,
|
|
6616
|
+
open,
|
|
6617
|
+
readFile,
|
|
6618
|
+
realpath,
|
|
6619
|
+
rename,
|
|
6620
|
+
rm
|
|
6621
|
+
} from "node:fs/promises";
|
|
6622
|
+
import {
|
|
6623
|
+
dirname,
|
|
6624
|
+
isAbsolute,
|
|
6625
|
+
join,
|
|
6626
|
+
normalize,
|
|
6627
|
+
parse,
|
|
6628
|
+
relative,
|
|
6629
|
+
resolve,
|
|
6630
|
+
sep
|
|
6631
|
+
} from "node:path";
|
|
6632
|
+
import { homedir } from "node:os";
|
|
6633
|
+
import { promisify } from "node:util";
|
|
6634
|
+
function resolveFinchtechHome(environment3, userHome = homedir()) {
|
|
6635
|
+
const explicit = environment3.FINCHTECH_HOME;
|
|
6636
|
+
if (typeof explicit === "string" && explicit.length > 0) return explicit;
|
|
6637
|
+
return resolve(userHome, ".finchtech");
|
|
6638
|
+
}
|
|
6639
|
+
function windowsSystemExecutable(name) {
|
|
6640
|
+
const systemRoot = process.env.SystemRoot;
|
|
6641
|
+
if (typeof systemRoot !== "string" || systemRoot.length === 0 || !isAbsolute(systemRoot)) {
|
|
6642
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6643
|
+
}
|
|
6644
|
+
return join(systemRoot, "System32", name);
|
|
6645
|
+
}
|
|
6646
|
+
async function queryCurrentWindowsSid() {
|
|
6647
|
+
try {
|
|
6648
|
+
const { stdout } = await execFileAsync(
|
|
6649
|
+
windowsSystemExecutable("whoami.exe"),
|
|
6650
|
+
["/user", "/fo", "csv", "/nh"],
|
|
6651
|
+
{ windowsHide: true }
|
|
6652
|
+
);
|
|
6653
|
+
const match = /,"(S-[0-9-]+)"\s*$/u.exec(stdout.trim());
|
|
6654
|
+
if (!match?.[1]) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6655
|
+
return match[1];
|
|
6656
|
+
} catch (cause) {
|
|
6657
|
+
if (cause instanceof CliStateError) throw cause;
|
|
6658
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
6659
|
+
}
|
|
6660
|
+
}
|
|
6661
|
+
function currentWindowsSid() {
|
|
6662
|
+
windowsSidPromise ??= queryCurrentWindowsSid();
|
|
6663
|
+
return windowsSidPromise;
|
|
6664
|
+
}
|
|
6665
|
+
async function hardenWindows(path2, directory) {
|
|
6666
|
+
const sid = await currentWindowsSid();
|
|
6667
|
+
const permission = directory ? `*${sid}:(OI)(CI)F` : `*${sid}:F`;
|
|
6668
|
+
try {
|
|
6669
|
+
await execFileAsync(
|
|
6670
|
+
windowsSystemExecutable("icacls.exe"),
|
|
6671
|
+
[path2, "/setowner", `*${sid}`],
|
|
6672
|
+
{ windowsHide: true }
|
|
6673
|
+
);
|
|
6674
|
+
await execFileAsync(
|
|
6675
|
+
windowsSystemExecutable("icacls.exe"),
|
|
6676
|
+
[path2, "/inheritance:r", "/grant:r", permission],
|
|
6677
|
+
{ windowsHide: true }
|
|
6678
|
+
);
|
|
6679
|
+
} catch (cause) {
|
|
6680
|
+
if (cause instanceof CliStateError) throw cause;
|
|
6681
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
6682
|
+
}
|
|
6683
|
+
}
|
|
6684
|
+
async function verifyWindowsAcl(path2) {
|
|
6685
|
+
const expectedSid = await currentWindowsSid();
|
|
6686
|
+
try {
|
|
6687
|
+
const arguments_ = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", inspectWindowsAclScript];
|
|
6688
|
+
const options = {
|
|
6689
|
+
env: { ...process.env, FINCHTECH_ACL_PATH: path2 },
|
|
6690
|
+
windowsHide: true
|
|
6691
|
+
};
|
|
6692
|
+
let stdout;
|
|
6693
|
+
try {
|
|
6694
|
+
({ stdout } = await execFileAsync("pwsh.exe", arguments_, options));
|
|
6695
|
+
} catch {
|
|
6696
|
+
({ stdout } = await execFileAsync("powershell.exe", arguments_, options));
|
|
6697
|
+
}
|
|
6698
|
+
const inspection = JSON.parse(stdout);
|
|
6699
|
+
const unrelatedRule = inspection.rules.some((rule) => rule.sid !== expectedSid && !WINDOWS_PLATFORM_ADMINISTRATOR_SIDS.has(rule.sid) || rule.type !== "Allow");
|
|
6700
|
+
if (inspection.ownerSid !== expectedSid || !inspection.protected || inspection.rules.length < 1 || inspection.rules.some((rule) => rule.inherited) || unrelatedRule) {
|
|
6701
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6702
|
+
}
|
|
6703
|
+
} catch (cause) {
|
|
6704
|
+
if (cause instanceof CliStateError) throw cause;
|
|
6705
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
6706
|
+
}
|
|
6707
|
+
}
|
|
6708
|
+
async function harden(path2, directory) {
|
|
6709
|
+
if (process.platform === "win32") {
|
|
6710
|
+
await hardenWindows(path2, directory);
|
|
6711
|
+
} else {
|
|
6712
|
+
await chmod(path2, directory ? 448 : 384);
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
async function writeExclusiveProtectedFile(path2, value) {
|
|
6716
|
+
const target = resolve(path2);
|
|
6717
|
+
const parent = dirname(target);
|
|
6718
|
+
if (!isAbsolute(path2) || parse(target).root === target) {
|
|
6719
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6720
|
+
}
|
|
6721
|
+
let handle;
|
|
6722
|
+
let created = false;
|
|
6723
|
+
try {
|
|
6724
|
+
await assertSafePathComponents(parent);
|
|
6725
|
+
await defaultPermissionPolicy.verify(parent, true);
|
|
6726
|
+
handle = await open(target, "wx", 384);
|
|
6727
|
+
created = true;
|
|
6728
|
+
const opened = await handle.stat();
|
|
6729
|
+
const pathAfterOpen = await lstat(target);
|
|
6730
|
+
if (!opened.isFile() || opened.nlink !== 1 || !sameIdentity(opened, pathAfterOpen)) {
|
|
6731
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6732
|
+
}
|
|
6733
|
+
await defaultPermissionPolicy.hardenNew(target, false);
|
|
6734
|
+
await defaultPermissionPolicy.verify(target, false);
|
|
6735
|
+
const openedAfterAcl = await handle.stat();
|
|
6736
|
+
const pathAfterAcl = await lstat(target);
|
|
6737
|
+
if (!sameIdentity(opened, openedAfterAcl) || !sameIdentity(opened, pathAfterAcl) || openedAfterAcl.nlink !== 1) {
|
|
6738
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6739
|
+
}
|
|
6740
|
+
await handle.writeFile(value);
|
|
6741
|
+
await handle.sync();
|
|
6742
|
+
await handle.close();
|
|
6743
|
+
handle = void 0;
|
|
6744
|
+
await defaultSyncDirectory(parent);
|
|
6745
|
+
} catch (cause) {
|
|
6746
|
+
await handle?.close().catch(() => void 0);
|
|
6747
|
+
if (cause.code === "EEXIST") {
|
|
6748
|
+
throw new CliStateError("CLI_STATE_CONFLICT", { cause });
|
|
6749
|
+
}
|
|
6750
|
+
if (created) {
|
|
6751
|
+
await rm(target, { force: true }).catch(() => void 0);
|
|
6752
|
+
await defaultSyncDirectory(parent).catch(() => void 0);
|
|
6753
|
+
}
|
|
6754
|
+
throw permissionFailure(cause);
|
|
6755
|
+
}
|
|
6756
|
+
}
|
|
6757
|
+
async function assertSafeNode(path2, directory) {
|
|
6758
|
+
const metadata = await lstat(path2);
|
|
6759
|
+
if (metadata.isSymbolicLink() || (directory ? !metadata.isDirectory() : !metadata.isFile())) {
|
|
6760
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6761
|
+
}
|
|
6762
|
+
if (!directory && metadata.nlink !== 1) {
|
|
6763
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6764
|
+
}
|
|
6765
|
+
if (process.platform !== "win32") {
|
|
6766
|
+
const expected = directory ? 448 : 384;
|
|
6767
|
+
if ((metadata.mode & 511) !== expected || typeof process.getuid === "function" && metadata.uid !== process.getuid()) {
|
|
6768
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6769
|
+
}
|
|
6770
|
+
} else {
|
|
6771
|
+
await verifyWindowsAcl(path2);
|
|
6772
|
+
}
|
|
6773
|
+
}
|
|
6774
|
+
async function defaultSyncDirectory(path2) {
|
|
6775
|
+
const handle = await open(path2, process.platform === "win32" ? "r+" : "r");
|
|
6776
|
+
try {
|
|
6777
|
+
await handle.sync();
|
|
6778
|
+
} finally {
|
|
6779
|
+
await handle.close();
|
|
6780
|
+
}
|
|
6781
|
+
}
|
|
6782
|
+
function samePath(left, right) {
|
|
6783
|
+
const normalizedLeft = normalize(left);
|
|
6784
|
+
const normalizedRight = normalize(right);
|
|
6785
|
+
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
6786
|
+
}
|
|
6787
|
+
async function assertSafePathComponents(path2) {
|
|
6788
|
+
const target = resolve(path2);
|
|
6789
|
+
const root = parse(target).root;
|
|
6790
|
+
let cursor = root;
|
|
6791
|
+
for (const component of relative(root, target).split(sep).filter(Boolean)) {
|
|
6792
|
+
cursor = resolve(cursor, component);
|
|
6793
|
+
const metadata = await lstat(cursor);
|
|
6794
|
+
if (metadata.isSymbolicLink()) {
|
|
6795
|
+
const darwinSystemTarget = process.platform === "darwin" ? DARWIN_SYSTEM_LINK_TARGETS.get(cursor) : void 0;
|
|
6796
|
+
if (darwinSystemTarget === void 0 || !samePath(await realpath(cursor), darwinSystemTarget)) {
|
|
6797
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6798
|
+
}
|
|
6799
|
+
}
|
|
6800
|
+
}
|
|
6801
|
+
}
|
|
6802
|
+
function permissionFailure(cause) {
|
|
6803
|
+
return cause instanceof CliStateError ? cause : new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
6804
|
+
}
|
|
6805
|
+
function sameIdentity(left, right) {
|
|
6806
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
6807
|
+
}
|
|
6808
|
+
async function readProtectedFile(path2, maximumBytes) {
|
|
6809
|
+
const target = resolve(path2);
|
|
6810
|
+
const parent = dirname(target);
|
|
6811
|
+
if (!isAbsolute(path2) || !Number.isInteger(maximumBytes) || maximumBytes < 1) {
|
|
6812
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6813
|
+
}
|
|
6814
|
+
let handle;
|
|
6815
|
+
try {
|
|
6816
|
+
await assertSafePathComponents(parent);
|
|
6817
|
+
await defaultPermissionPolicy.verify(parent, true);
|
|
6818
|
+
const before = await lstat(target);
|
|
6819
|
+
const noFollow = constants.O_NOFOLLOW ?? 0;
|
|
6820
|
+
handle = await open(target, constants.O_RDONLY | noFollow);
|
|
6821
|
+
const opened = await handle.stat();
|
|
6822
|
+
const afterOpen = await lstat(target);
|
|
6823
|
+
if (!sameIdentity(before, opened) || !sameIdentity(opened, afterOpen) || !opened.isFile() || opened.nlink !== 1 || opened.size < 1 || opened.size > maximumBytes) {
|
|
6824
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6825
|
+
}
|
|
6826
|
+
if (process.platform === "win32") {
|
|
6827
|
+
await verifyWindowsAcl(target);
|
|
6828
|
+
} else if ((opened.mode & 511) !== 384 || typeof process.getuid === "function" && opened.uid !== process.getuid()) {
|
|
6829
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6830
|
+
}
|
|
6831
|
+
const afterAcl = await lstat(target);
|
|
6832
|
+
if (!sameIdentity(opened, afterAcl)) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6833
|
+
const body = Buffer.alloc(opened.size);
|
|
6834
|
+
let offset = 0;
|
|
6835
|
+
while (offset < body.length) {
|
|
6836
|
+
const chunk = await handle.read(body, offset, body.length - offset, offset);
|
|
6837
|
+
if (chunk.bytesRead === 0) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6838
|
+
offset += chunk.bytesRead;
|
|
6839
|
+
}
|
|
6840
|
+
const stable = await handle.stat();
|
|
6841
|
+
if (!sameIdentity(opened, stable) || stable.size !== opened.size) {
|
|
6842
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6843
|
+
}
|
|
6844
|
+
return body;
|
|
6845
|
+
} catch (cause) {
|
|
6846
|
+
throw permissionFailure(cause);
|
|
6847
|
+
} finally {
|
|
6848
|
+
await handle?.close().catch(() => void 0);
|
|
6849
|
+
}
|
|
6850
|
+
}
|
|
6851
|
+
var execFileAsync, DARWIN_SYSTEM_LINK_TARGETS, CliStateError, windowsSidPromise, WINDOWS_PLATFORM_ADMINISTRATOR_SIDS, inspectWindowsAclScript, defaultPermissionPolicy, HardenedFileStore;
|
|
6852
|
+
var init_state = __esm({
|
|
6853
|
+
"src/state.ts"() {
|
|
6854
|
+
"use strict";
|
|
6855
|
+
execFileAsync = promisify(execFile);
|
|
6856
|
+
DARWIN_SYSTEM_LINK_TARGETS = /* @__PURE__ */ new Map([
|
|
6857
|
+
["/etc", "/private/etc"],
|
|
6858
|
+
["/tmp", "/private/tmp"],
|
|
6859
|
+
["/var", "/private/var"]
|
|
6860
|
+
]);
|
|
6861
|
+
CliStateError = class extends Error {
|
|
6862
|
+
code;
|
|
6863
|
+
diagnostic;
|
|
6864
|
+
constructor(code, options) {
|
|
6865
|
+
super(code, options);
|
|
6866
|
+
this.name = "CliStateError";
|
|
6867
|
+
this.code = code;
|
|
6868
|
+
this.diagnostic = options?.diagnostic ?? null;
|
|
6869
|
+
}
|
|
6870
|
+
};
|
|
6871
|
+
WINDOWS_PLATFORM_ADMINISTRATOR_SIDS = /* @__PURE__ */ new Set([
|
|
6872
|
+
"S-1-5-18",
|
|
6873
|
+
// LocalSystem
|
|
6874
|
+
"S-1-5-32-544"
|
|
6875
|
+
// BUILTIN\Administrators
|
|
6876
|
+
]);
|
|
6877
|
+
inspectWindowsAclScript = String.raw`
|
|
6878
|
+
$ErrorActionPreference = 'Stop'
|
|
6879
|
+
$acl = Get-Acl -LiteralPath $env:FINCHTECH_ACL_PATH
|
|
6880
|
+
$ownerSid = ([System.Security.Principal.NTAccount]$acl.Owner).Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
6881
|
+
$rules = @($acl.Access | ForEach-Object {
|
|
6882
|
+
$sid = $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
6883
|
+
[pscustomobject]@{ sid = $sid; inherited = $_.IsInherited; type = $_.AccessControlType.ToString() }
|
|
6884
|
+
})
|
|
6885
|
+
[pscustomobject]@{ ownerSid = $ownerSid; protected = $acl.AreAccessRulesProtected; rules = $rules } |
|
|
6886
|
+
ConvertTo-Json -Compress -Depth 4
|
|
6887
|
+
`;
|
|
6888
|
+
defaultPermissionPolicy = Object.freeze({
|
|
6889
|
+
hardenNew: harden,
|
|
6890
|
+
verify: assertSafeNode
|
|
6891
|
+
});
|
|
6892
|
+
HardenedFileStore = class {
|
|
6893
|
+
root;
|
|
6894
|
+
permissions;
|
|
6895
|
+
syncDirectory;
|
|
6896
|
+
constructor(root, options = {}) {
|
|
6897
|
+
const resolved = resolve(root);
|
|
6898
|
+
if (!isAbsolute(root) || samePath(resolved, parse(resolved).root)) {
|
|
6899
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6900
|
+
}
|
|
6901
|
+
this.root = resolved;
|
|
6902
|
+
this.permissions = options.permissions ?? defaultPermissionPolicy;
|
|
6903
|
+
this.syncDirectory = options.syncDirectory ?? defaultSyncDirectory;
|
|
6904
|
+
}
|
|
6905
|
+
resolveRelative(path2) {
|
|
6906
|
+
if (path2.length === 0 || isAbsolute(path2) || path2.includes("\0")) {
|
|
6907
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6908
|
+
}
|
|
6909
|
+
const target = resolve(this.root, path2);
|
|
6910
|
+
const fromRoot = relative(this.root, target);
|
|
6911
|
+
if (fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
|
|
6912
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6913
|
+
}
|
|
6914
|
+
return target;
|
|
6915
|
+
}
|
|
6916
|
+
async ensureDirectory(path2) {
|
|
6917
|
+
try {
|
|
6918
|
+
const metadata = await lstat(path2);
|
|
6919
|
+
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
6920
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6921
|
+
}
|
|
6922
|
+
await assertSafePathComponents(path2);
|
|
6923
|
+
await this.permissions.verify(path2, true);
|
|
6924
|
+
return;
|
|
6925
|
+
} catch (cause) {
|
|
6926
|
+
if (cause.code !== "ENOENT") throw permissionFailure(cause);
|
|
6927
|
+
}
|
|
6928
|
+
const parent = dirname(path2);
|
|
6929
|
+
await assertSafePathComponents(parent);
|
|
6930
|
+
let created = false;
|
|
6931
|
+
try {
|
|
6932
|
+
await mkdir(path2, { recursive: false, mode: 448 });
|
|
6933
|
+
created = true;
|
|
6934
|
+
await this.permissions.hardenNew(path2, true);
|
|
6935
|
+
await this.permissions.verify(path2, true);
|
|
6936
|
+
await this.syncDirectory(parent);
|
|
6937
|
+
} catch (cause) {
|
|
6938
|
+
if (created) {
|
|
6939
|
+
await rm(path2, { recursive: true, force: true }).catch(() => void 0);
|
|
6940
|
+
await this.syncDirectory(parent).catch(() => void 0);
|
|
6941
|
+
}
|
|
6942
|
+
throw permissionFailure(cause);
|
|
6943
|
+
}
|
|
6944
|
+
}
|
|
6945
|
+
async ensureParent(target) {
|
|
6946
|
+
await this.ensureDirectory(this.root);
|
|
6947
|
+
const parent = resolve(target, "..");
|
|
6948
|
+
const fromRoot = relative(this.root, parent);
|
|
6949
|
+
let cursor = this.root;
|
|
6950
|
+
for (const component of fromRoot.split(sep).filter(Boolean)) {
|
|
6951
|
+
cursor = resolve(cursor, component);
|
|
6952
|
+
await this.ensureDirectory(cursor);
|
|
6953
|
+
}
|
|
6954
|
+
}
|
|
6955
|
+
async writeSecret(path2, value, options) {
|
|
6956
|
+
if (!options.exclusive) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
6957
|
+
const target = this.resolveRelative(path2);
|
|
6958
|
+
await this.ensureParent(target);
|
|
6959
|
+
let handle;
|
|
6960
|
+
let created = false;
|
|
6961
|
+
try {
|
|
6962
|
+
handle = await open(target, "wx", 384);
|
|
6963
|
+
created = true;
|
|
6964
|
+
await this.permissions.hardenNew(target, false);
|
|
6965
|
+
await this.permissions.verify(target, false);
|
|
6966
|
+
await handle.writeFile(value);
|
|
6967
|
+
await handle.sync();
|
|
6968
|
+
await handle.close();
|
|
6969
|
+
handle = void 0;
|
|
6970
|
+
await this.syncDirectory(dirname(target));
|
|
6971
|
+
} catch (cause) {
|
|
6972
|
+
await handle?.close().catch(() => void 0);
|
|
6973
|
+
if (cause.code === "EEXIST") {
|
|
6974
|
+
throw new CliStateError("CLI_STATE_CONFLICT", { cause });
|
|
6975
|
+
}
|
|
6976
|
+
if (created) {
|
|
6977
|
+
await rm(target, { force: true }).catch(() => void 0);
|
|
6978
|
+
await this.syncDirectory(dirname(target)).catch(() => void 0);
|
|
6979
|
+
}
|
|
6980
|
+
throw permissionFailure(cause);
|
|
6981
|
+
}
|
|
6982
|
+
}
|
|
6983
|
+
async writeJson(path2, value) {
|
|
6984
|
+
const target = this.resolveRelative(path2);
|
|
6985
|
+
const temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
6986
|
+
await this.ensureParent(target);
|
|
6987
|
+
try {
|
|
6988
|
+
await this.writeSecret(relative(this.root, temporary), Buffer.from(`${JSON.stringify(value)}
|
|
6989
|
+
`, "utf8"), {
|
|
6990
|
+
exclusive: true
|
|
6991
|
+
});
|
|
6992
|
+
try {
|
|
6993
|
+
await lstat(target);
|
|
6994
|
+
await this.permissions.verify(target, false);
|
|
6995
|
+
} catch (cause) {
|
|
6996
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
6997
|
+
}
|
|
6998
|
+
await rename(temporary, target);
|
|
6999
|
+
await this.permissions.verify(target, false);
|
|
7000
|
+
await this.syncDirectory(dirname(target));
|
|
7001
|
+
} catch (cause) {
|
|
7002
|
+
await rm(temporary, { force: true }).catch(() => void 0);
|
|
7003
|
+
throw cause;
|
|
7004
|
+
}
|
|
7005
|
+
}
|
|
7006
|
+
async readSecret(path2) {
|
|
7007
|
+
const target = this.resolveRelative(path2);
|
|
7008
|
+
try {
|
|
7009
|
+
await this.permissions.verify(target, false);
|
|
7010
|
+
return await readFile(target);
|
|
7011
|
+
} catch (cause) {
|
|
7012
|
+
if (cause.code === "ENOENT") {
|
|
7013
|
+
throw new CliStateError("CLI_STATE_NOT_FOUND", { cause });
|
|
7014
|
+
}
|
|
7015
|
+
if (cause instanceof CliStateError) throw cause;
|
|
7016
|
+
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
7017
|
+
}
|
|
7018
|
+
}
|
|
7019
|
+
async readJson(path2) {
|
|
7020
|
+
try {
|
|
7021
|
+
return JSON.parse((await this.readSecret(path2)).toString("utf8"));
|
|
7022
|
+
} catch (cause) {
|
|
7023
|
+
if (cause instanceof CliStateError && cause.code !== "CLI_STATE_CORRUPT") throw cause;
|
|
7024
|
+
throw new CliStateError("CLI_STATE_CORRUPT", { cause });
|
|
7025
|
+
}
|
|
7026
|
+
}
|
|
7027
|
+
async remove(path2, options = {}) {
|
|
7028
|
+
const target = this.resolveRelative(path2);
|
|
7029
|
+
await rm(target, { force: true, recursive: options.recursive ?? false });
|
|
7030
|
+
await this.syncDirectory(dirname(target)).catch((cause) => {
|
|
7031
|
+
if (cause.code !== "ENOENT") throw cause;
|
|
7032
|
+
});
|
|
7033
|
+
}
|
|
7034
|
+
};
|
|
7035
|
+
}
|
|
7036
|
+
});
|
|
7037
|
+
|
|
6609
7038
|
// src/cli-error.ts
|
|
7039
|
+
function localFailureMessage(error51) {
|
|
7040
|
+
if (error51 instanceof CliStateError) {
|
|
7041
|
+
if (error51.code === "CLI_STATE_PERMISSIONS_INVALID") {
|
|
7042
|
+
return process.platform === "win32" ? "Protected local state has unsafe ownership or ACLs. Restrict it to the current Windows user and retry." : "Protected local state has unsafe permissions. Directories must be mode 0700 and files mode 0600.";
|
|
7043
|
+
}
|
|
7044
|
+
if (error51.diagnostic === "profile") {
|
|
7045
|
+
return error51.code === "CLI_STATE_NOT_FOUND" ? "Finch endpoint override is missing. Remove the override to use PROD, or provision protected FINCHTECH_HOME/profile.json." : "Finch endpoint override is invalid. Remove it to use PROD, or reprovision protected FINCHTECH_HOME/profile.json.";
|
|
7046
|
+
}
|
|
7047
|
+
if (error51.diagnostic === "wallet_bootstrap") {
|
|
7048
|
+
return "Local authorization remains without a wallet. Restore the original wallet state, or run finch logout --local to discard local authorization before importing.";
|
|
7049
|
+
}
|
|
7050
|
+
if (error51.diagnostic === "wallet") {
|
|
7051
|
+
return error51.code === "CLI_STATE_NOT_FOUND" ? "No current wallet was found in FINCHTECH_HOME (default: ~/.finchtech). Run finch wallet create, or finch wallet use --file <PATH> to import an existing private key into a new wallet store." : "Local wallet state in FINCHTECH_HOME (default: ~/.finchtech) is incomplete or invalid. Restore the wallet files from backup; do not overwrite the existing wallet.";
|
|
7052
|
+
}
|
|
7053
|
+
if (error51.diagnostic === "session") {
|
|
7054
|
+
return error51.code === "CLI_STATE_NOT_FOUND" ? "No active Finch login was found. Run finch login --help for supported authentication chains, then finch login --chain-id <CHAIN_ID>." : "The active Finch login is invalid. Run finch logout --local, then finch login --help to select an authentication chain and log in again.";
|
|
7055
|
+
}
|
|
7056
|
+
if (error51.code === "CLI_STATE_NOT_FOUND") {
|
|
7057
|
+
return "Required protected local state is missing. Check the command prerequisites and retry.";
|
|
7058
|
+
}
|
|
7059
|
+
if (error51.code === "CLI_STATE_CONFLICT") {
|
|
7060
|
+
return "Protected local state already exists or changed concurrently. Refresh state and retry.";
|
|
7061
|
+
}
|
|
7062
|
+
return "Protected local state is corrupt. Recreate the affected profile, login, grant, or recovery state.";
|
|
7063
|
+
}
|
|
7064
|
+
if (error51.message === "FINCHTECH_HOME is required.") {
|
|
7065
|
+
return "FINCHTECH_HOME is required. Set it to the protected Finch Agent CLI state directory.";
|
|
7066
|
+
}
|
|
7067
|
+
const missingScope = /^No active MCP grant with ([a-z_:]+) is available for this account and endpoint\.$/u.exec(error51.message);
|
|
7068
|
+
if (missingScope !== null) {
|
|
7069
|
+
return `Required MCP grant scope ${missingScope[1]} is missing. Create or refresh that scoped grant.`;
|
|
7070
|
+
}
|
|
7071
|
+
if (/^The selected .* MCP grant is invalid\.$/u.test(error51.message)) {
|
|
7072
|
+
return "The selected MCP grant is invalid. Recreate the grant for the active account and endpoint.";
|
|
7073
|
+
}
|
|
7074
|
+
return "Command input does not match the expected schema. Check finch help for this command.";
|
|
7075
|
+
}
|
|
6610
7076
|
var CliAuthorityError, CliProductionUnavailableError, BrowserOpenError, PUBLIC_RECOVERY_COMMAND, CliRecoveryBlockedError, CliSkillSourceError, CliSkillPublicationError;
|
|
6611
7077
|
var init_cli_error = __esm({
|
|
6612
7078
|
"src/cli-error.ts"() {
|
|
6613
7079
|
"use strict";
|
|
7080
|
+
init_state();
|
|
6614
7081
|
CliAuthorityError = class extends Error {
|
|
6615
7082
|
code = "MCP_CLI_WALLET_MISMATCH";
|
|
6616
7083
|
constructor(code = "MCP_CLI_WALLET_MISMATCH") {
|
|
@@ -6757,11 +7224,32 @@ var init_delivery_message = __esm({
|
|
|
6757
7224
|
}
|
|
6758
7225
|
});
|
|
6759
7226
|
|
|
7227
|
+
// ../../packages/skill-content/src/image-signature.ts
|
|
7228
|
+
function matchesAscii(bytes, offset, tag) {
|
|
7229
|
+
if (bytes.length < offset + tag.length) return false;
|
|
7230
|
+
return [...tag].every((character, index2) => bytes[offset + index2] === character.charCodeAt(0));
|
|
7231
|
+
}
|
|
7232
|
+
function detectedImageMediaType(bytes) {
|
|
7233
|
+
if (bytes.length >= PNG_SIGNATURE.length && PNG_SIGNATURE.every((byte, index2) => bytes[index2] === byte)) return "image/png";
|
|
7234
|
+
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
7235
|
+
if (matchesAscii(bytes, 0, "GIF87a") || matchesAscii(bytes, 0, "GIF89a")) return "image/gif";
|
|
7236
|
+
if (matchesAscii(bytes, 0, "RIFF") && matchesAscii(bytes, 8, "WEBP")) return "image/webp";
|
|
7237
|
+
return null;
|
|
7238
|
+
}
|
|
7239
|
+
var PNG_SIGNATURE;
|
|
7240
|
+
var init_image_signature = __esm({
|
|
7241
|
+
"../../packages/skill-content/src/image-signature.ts"() {
|
|
7242
|
+
"use strict";
|
|
7243
|
+
PNG_SIGNATURE = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
7244
|
+
}
|
|
7245
|
+
});
|
|
7246
|
+
|
|
6760
7247
|
// ../../packages/skill-content/src/static-detail.ts
|
|
6761
7248
|
var MAX_SKILL_DETAIL_OBJECT_BYTES, MAX_SKILL_DETAIL_DOCUMENT_BYTES, NAMED_REFERENCES;
|
|
6762
7249
|
var init_static_detail = __esm({
|
|
6763
7250
|
"../../packages/skill-content/src/static-detail.ts"() {
|
|
6764
7251
|
"use strict";
|
|
7252
|
+
init_image_signature();
|
|
6765
7253
|
MAX_SKILL_DETAIL_OBJECT_BYTES = 4 * 1024 * 1024;
|
|
6766
7254
|
MAX_SKILL_DETAIL_DOCUMENT_BYTES = MAX_SKILL_DETAIL_OBJECT_BYTES;
|
|
6767
7255
|
NAMED_REFERENCES = Object.freeze({
|
|
@@ -11270,6 +11758,7 @@ var init_src2 = __esm({
|
|
|
11270
11758
|
init_crypto();
|
|
11271
11759
|
init_delivery_message();
|
|
11272
11760
|
init_detail_documents();
|
|
11761
|
+
init_image_signature();
|
|
11273
11762
|
init_manifest();
|
|
11274
11763
|
init_packaging();
|
|
11275
11764
|
init_static_detail();
|
|
@@ -14454,11 +14943,13 @@ function assertSortedIdentityScopes(scopes) {
|
|
|
14454
14943
|
previous = scope;
|
|
14455
14944
|
}
|
|
14456
14945
|
}
|
|
14457
|
-
var identityScopeValues, IDENTITY_SCOPE_ORDER_CONTRACT, identityEnvironmentValues, environmentSchema, chainIdSchema, dateTimeSchema, grantScopesSchema, identityChallengeRequestSchema, identityChallengeResponseSchema, identityLoginRequestSchema, identitySessionResponseSchema, identitySessionStatusResponseSchema, identityLogoutRequestSchema, identityMcpActorResponseSchema, identityGrantCreateRequestSchema, identityGrantCreateResponseSchema, identityGrantShowResponseSchema, identityGrantRevokeRequestSchema, identityErrorCodes, identityErrorEnvelopeSchema, IDENTITY_V1_ROUTES, IDENTITY_V1_OPERATIONS, IDENTITY_MCP_V1_MANIFEST, CLI_GRANT_SHOW_WIRE;
|
|
14946
|
+
var PRODUCTION_AUTH_CHAIN_IDS, TEST_AUTH_CHAIN_IDS, identityScopeValues, IDENTITY_SCOPE_ORDER_CONTRACT, identityEnvironmentValues, environmentSchema, chainIdSchema, dateTimeSchema, grantScopesSchema, identityChallengeRequestSchema, identityChallengeResponseSchema, identityLoginRequestSchema, identitySessionResponseSchema, identitySessionStatusResponseSchema, identityLogoutRequestSchema, identityMcpActorResponseSchema, identityGrantCreateRequestSchema, identityGrantCreateResponseSchema, identityGrantShowResponseSchema, identityGrantRevokeRequestSchema, identityErrorCodes, identityErrorEnvelopeSchema, IDENTITY_V1_ROUTES, IDENTITY_V1_OPERATIONS, IDENTITY_MCP_V1_MANIFEST, CLI_GRANT_SHOW_WIRE;
|
|
14458
14947
|
var init_identity = __esm({
|
|
14459
14948
|
"../../packages/agent-market-contracts/src/v1/identity.ts"() {
|
|
14460
14949
|
"use strict";
|
|
14461
14950
|
init_common2();
|
|
14951
|
+
PRODUCTION_AUTH_CHAIN_IDS = Object.freeze([1, 10, 56, 8453, 42161]);
|
|
14952
|
+
TEST_AUTH_CHAIN_IDS = Object.freeze([84532]);
|
|
14462
14953
|
identityScopeValues = [
|
|
14463
14954
|
"account:read",
|
|
14464
14955
|
"agent_market:buyer:invoke",
|
|
@@ -15025,7 +15516,7 @@ var init_merchant = __esm({
|
|
|
15025
15516
|
});
|
|
15026
15517
|
publicationOperationResultSchema = strictObjectSchema({
|
|
15027
15518
|
serviceVersionId: canonicalUuidSchema,
|
|
15028
|
-
publicationGeneration: nonNegativeGenerationSchema,
|
|
15519
|
+
publicationGeneration: { anyOf: [nonNegativeGenerationSchema, nullSchema3] },
|
|
15029
15520
|
activeVersionRevision: positiveRevisionSchema
|
|
15030
15521
|
});
|
|
15031
15522
|
localPurchaseOperationResultSchema = strictObjectSchema({
|
|
@@ -16915,6 +17406,7 @@ var init_schemas = __esm({
|
|
|
16915
17406
|
perPage: { type: "integer", minimum: 1, maximum: 100 }
|
|
16916
17407
|
}, []);
|
|
16917
17408
|
createTaskDraftRequestSchema = strictObjectSchema2({
|
|
17409
|
+
expectedUpdatedAt: { type: "string", format: "date-time" },
|
|
16918
17410
|
title: { type: "string", minLength: 1, maxLength: 80 },
|
|
16919
17411
|
description: { type: "string", minLength: 1, maxLength: 500 },
|
|
16920
17412
|
goal: { type: "string", minLength: 1, maxLength: 2e3 },
|
|
@@ -16940,7 +17432,7 @@ var init_schemas = __esm({
|
|
|
16940
17432
|
startAt: nullableString,
|
|
16941
17433
|
goals: { type: "array", maxItems: 5, items: openObjectSchema },
|
|
16942
17434
|
publishNow: { const: false }
|
|
16943
|
-
}, ["title", "
|
|
17435
|
+
}, ["title", "goal", "rewardAmount", "publishNow"]);
|
|
16944
17436
|
taskSubmissionRequestSchema = strictObjectSchema2({
|
|
16945
17437
|
content: { type: "string", minLength: 1, maxLength: 1e5 },
|
|
16946
17438
|
proofUrl: nullableString,
|
|
@@ -18051,7 +18543,7 @@ var init_errors2 = __esm({
|
|
|
18051
18543
|
});
|
|
18052
18544
|
|
|
18053
18545
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js
|
|
18054
|
-
var _parse,
|
|
18546
|
+
var _parse, parse2, _parseAsync, parseAsync, _safeParse, safeParse, _safeParseAsync, safeParseAsync, _encode, encode5, _decode, decode7, _encodeAsync, encodeAsync, _decodeAsync, decodeAsync, _safeEncode, safeEncode, _safeDecode, safeDecode, _safeEncodeAsync, safeEncodeAsync, _safeDecodeAsync, safeDecodeAsync;
|
|
18055
18547
|
var init_parse = __esm({
|
|
18056
18548
|
"../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js"() {
|
|
18057
18549
|
init_core();
|
|
@@ -18070,7 +18562,7 @@ var init_parse = __esm({
|
|
|
18070
18562
|
}
|
|
18071
18563
|
return result2.value;
|
|
18072
18564
|
};
|
|
18073
|
-
|
|
18565
|
+
parse2 = /* @__PURE__ */ _parse($ZodRealError);
|
|
18074
18566
|
_parseAsync = (_Err) => async (schema4, value, _ctx, params) => {
|
|
18075
18567
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
18076
18568
|
let result2 = schema4._zod.run({ value, issues: [] }, ctx);
|
|
@@ -20916,10 +21408,10 @@ var init_schemas2 = __esm({
|
|
|
20916
21408
|
throw new Error("implement() must be called with a function");
|
|
20917
21409
|
}
|
|
20918
21410
|
return function(...args) {
|
|
20919
|
-
const parsedArgs = inst._def.input ?
|
|
21411
|
+
const parsedArgs = inst._def.input ? parse2(inst._def.input, args) : args;
|
|
20920
21412
|
const result2 = Reflect.apply(func, this, parsedArgs);
|
|
20921
21413
|
if (inst._def.output) {
|
|
20922
|
-
return
|
|
21414
|
+
return parse2(inst._def.output, result2);
|
|
20923
21415
|
}
|
|
20924
21416
|
return result2;
|
|
20925
21417
|
};
|
|
@@ -29714,7 +30206,7 @@ __export(core_exports2, {
|
|
|
29714
30206
|
isValidJWT: () => isValidJWT,
|
|
29715
30207
|
locales: () => locales_exports,
|
|
29716
30208
|
meta: () => meta,
|
|
29717
|
-
parse: () =>
|
|
30209
|
+
parse: () => parse2,
|
|
29718
30210
|
parseAsync: () => parseAsync,
|
|
29719
30211
|
prettifyError: () => prettifyError,
|
|
29720
30212
|
process: () => process2,
|
|
@@ -29889,12 +30381,12 @@ var init_errors3 = __esm({
|
|
|
29889
30381
|
});
|
|
29890
30382
|
|
|
29891
30383
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/parse.js
|
|
29892
|
-
var
|
|
30384
|
+
var parse3, parseAsync2, safeParse2, safeParseAsync2, encode6, decode8, encodeAsync2, decodeAsync2, safeEncode2, safeDecode2, safeEncodeAsync2, safeDecodeAsync2;
|
|
29893
30385
|
var init_parse2 = __esm({
|
|
29894
30386
|
"../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/parse.js"() {
|
|
29895
30387
|
init_core2();
|
|
29896
30388
|
init_errors3();
|
|
29897
|
-
|
|
30389
|
+
parse3 = /* @__PURE__ */ _parse(ZodRealError);
|
|
29898
30390
|
parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
29899
30391
|
safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
29900
30392
|
safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -30614,7 +31106,7 @@ var init_schemas3 = __esm({
|
|
|
30614
31106
|
inst.def = def;
|
|
30615
31107
|
inst.type = def.type;
|
|
30616
31108
|
Object.defineProperty(inst, "_def", { value: def });
|
|
30617
|
-
inst.parse = (data, params) =>
|
|
31109
|
+
inst.parse = (data, params) => parse3(inst, data, params, { callee: inst.parse });
|
|
30618
31110
|
inst.safeParse = (data, params) => safeParse2(inst, data, params);
|
|
30619
31111
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
|
30620
31112
|
inst.safeParseAsync = async (data, params) => safeParseAsync2(inst, data, params);
|
|
@@ -32114,7 +32606,7 @@ __export(external_exports, {
|
|
|
32114
32606
|
object: () => object,
|
|
32115
32607
|
optional: () => optional,
|
|
32116
32608
|
overwrite: () => _overwrite,
|
|
32117
|
-
parse: () =>
|
|
32609
|
+
parse: () => parse3,
|
|
32118
32610
|
parseAsync: () => parseAsync2,
|
|
32119
32611
|
partialRecord: () => partialRecord,
|
|
32120
32612
|
pipe: () => pipe,
|
|
@@ -32470,12 +32962,12 @@ var init_skill_protocol = __esm({
|
|
|
32470
32962
|
name: "BNB Smart Chain",
|
|
32471
32963
|
explorerUrl: "https://bscscan.com",
|
|
32472
32964
|
defaultRpcUrls: [
|
|
32965
|
+
"https://bsc.rpc.blxrbdn.com",
|
|
32473
32966
|
"https://0.48.club",
|
|
32474
|
-
"https://bsc-mainnet.public.blastapi.io",
|
|
32475
32967
|
"https://bsc-dataseed.binance.org",
|
|
32476
32968
|
"https://bsc-dataseed1.defibit.io"
|
|
32477
32969
|
],
|
|
32478
|
-
maxLogBlockRange:
|
|
32970
|
+
maxLogBlockRange: 5e3
|
|
32479
32971
|
},
|
|
32480
32972
|
sourceRepository: "FinchipAI/finchip-protocol",
|
|
32481
32973
|
sourceCommit: "89e54d6ff46dbfba61122e90c5e77f409f02fe5e",
|
|
@@ -32501,8 +32993,7 @@ var init_skill_protocol = __esm({
|
|
|
32501
32993
|
name: "Base",
|
|
32502
32994
|
explorerUrl: "https://basescan.org",
|
|
32503
32995
|
defaultRpcUrls: [
|
|
32504
|
-
"https://mainnet.base.org"
|
|
32505
|
-
"https://base.drpc.org"
|
|
32996
|
+
"https://mainnet.base.org"
|
|
32506
32997
|
],
|
|
32507
32998
|
maxLogBlockRange: 2e3
|
|
32508
32999
|
},
|
|
@@ -33160,7 +33651,7 @@ var init_web_localization = __esm({
|
|
|
33160
33651
|
languageSwitchEnabled: true
|
|
33161
33652
|
},
|
|
33162
33653
|
production: {
|
|
33163
|
-
languageSwitchEnabled:
|
|
33654
|
+
languageSwitchEnabled: true
|
|
33164
33655
|
}
|
|
33165
33656
|
}
|
|
33166
33657
|
};
|
|
@@ -33388,12 +33879,12 @@ var init_merchant_avatar_video = __esm({
|
|
|
33388
33879
|
test: {
|
|
33389
33880
|
enabled: true,
|
|
33390
33881
|
offerId: "e72aeda7-da44-5685-b5a0-4d90cfa63c2d",
|
|
33391
|
-
dailyLimit:
|
|
33882
|
+
dailyLimit: 3
|
|
33392
33883
|
},
|
|
33393
33884
|
production: {
|
|
33394
33885
|
enabled: true,
|
|
33395
33886
|
offerId: "1cf22f74-a451-54f0-894a-baff6221693d",
|
|
33396
|
-
dailyLimit:
|
|
33887
|
+
dailyLimit: 3
|
|
33397
33888
|
}
|
|
33398
33889
|
}
|
|
33399
33890
|
};
|
|
@@ -54610,6 +55101,7 @@ var init_api2 = __esm({
|
|
|
54610
55101
|
ajv = new import_ajv.default({ allErrors: true, strict: false });
|
|
54611
55102
|
ajv.addFormat("date-time", (value) => Number.isFinite(Date.parse(value)));
|
|
54612
55103
|
ajv.addFormat("uri", (value) => URL.canParse(value));
|
|
55104
|
+
ajv.addFormat("uuid", new RegExp(canonicalUuidPattern, "u"));
|
|
54613
55105
|
validatorCache = /* @__PURE__ */ new WeakMap();
|
|
54614
55106
|
skillUnsignedIntentResponseSchema = {
|
|
54615
55107
|
type: "object",
|
|
@@ -55576,435 +56068,6 @@ var init_accounts = __esm({
|
|
|
55576
56068
|
}
|
|
55577
56069
|
});
|
|
55578
56070
|
|
|
55579
|
-
// src/state.ts
|
|
55580
|
-
import { execFile } from "node:child_process";
|
|
55581
|
-
import { constants } from "node:fs";
|
|
55582
|
-
import {
|
|
55583
|
-
chmod,
|
|
55584
|
-
lstat,
|
|
55585
|
-
mkdir,
|
|
55586
|
-
open,
|
|
55587
|
-
readFile,
|
|
55588
|
-
realpath,
|
|
55589
|
-
rename,
|
|
55590
|
-
rm
|
|
55591
|
-
} from "node:fs/promises";
|
|
55592
|
-
import {
|
|
55593
|
-
dirname,
|
|
55594
|
-
isAbsolute,
|
|
55595
|
-
join,
|
|
55596
|
-
normalize,
|
|
55597
|
-
parse as parse3,
|
|
55598
|
-
relative,
|
|
55599
|
-
resolve,
|
|
55600
|
-
sep
|
|
55601
|
-
} from "node:path";
|
|
55602
|
-
import { homedir } from "node:os";
|
|
55603
|
-
import { promisify } from "node:util";
|
|
55604
|
-
function resolveFinchtechHome(environment3, userHome = homedir()) {
|
|
55605
|
-
const explicit = environment3.FINCHTECH_HOME;
|
|
55606
|
-
if (typeof explicit === "string" && explicit.length > 0) return explicit;
|
|
55607
|
-
return resolve(userHome, ".finchtech");
|
|
55608
|
-
}
|
|
55609
|
-
function windowsSystemExecutable(name) {
|
|
55610
|
-
const systemRoot = process.env.SystemRoot;
|
|
55611
|
-
if (typeof systemRoot !== "string" || systemRoot.length === 0 || !isAbsolute(systemRoot)) {
|
|
55612
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55613
|
-
}
|
|
55614
|
-
return join(systemRoot, "System32", name);
|
|
55615
|
-
}
|
|
55616
|
-
async function queryCurrentWindowsSid() {
|
|
55617
|
-
try {
|
|
55618
|
-
const { stdout } = await execFileAsync(
|
|
55619
|
-
windowsSystemExecutable("whoami.exe"),
|
|
55620
|
-
["/user", "/fo", "csv", "/nh"],
|
|
55621
|
-
{ windowsHide: true }
|
|
55622
|
-
);
|
|
55623
|
-
const match = /,"(S-[0-9-]+)"\s*$/u.exec(stdout.trim());
|
|
55624
|
-
if (!match?.[1]) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55625
|
-
return match[1];
|
|
55626
|
-
} catch (cause) {
|
|
55627
|
-
if (cause instanceof CliStateError) throw cause;
|
|
55628
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
55629
|
-
}
|
|
55630
|
-
}
|
|
55631
|
-
function currentWindowsSid() {
|
|
55632
|
-
windowsSidPromise ??= queryCurrentWindowsSid();
|
|
55633
|
-
return windowsSidPromise;
|
|
55634
|
-
}
|
|
55635
|
-
async function hardenWindows(path2, directory) {
|
|
55636
|
-
const sid = await currentWindowsSid();
|
|
55637
|
-
const permission = directory ? `*${sid}:(OI)(CI)F` : `*${sid}:F`;
|
|
55638
|
-
try {
|
|
55639
|
-
await execFileAsync(
|
|
55640
|
-
windowsSystemExecutable("icacls.exe"),
|
|
55641
|
-
[path2, "/setowner", `*${sid}`],
|
|
55642
|
-
{ windowsHide: true }
|
|
55643
|
-
);
|
|
55644
|
-
await execFileAsync(
|
|
55645
|
-
windowsSystemExecutable("icacls.exe"),
|
|
55646
|
-
[path2, "/inheritance:r", "/grant:r", permission],
|
|
55647
|
-
{ windowsHide: true }
|
|
55648
|
-
);
|
|
55649
|
-
} catch (cause) {
|
|
55650
|
-
if (cause instanceof CliStateError) throw cause;
|
|
55651
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
55652
|
-
}
|
|
55653
|
-
}
|
|
55654
|
-
async function verifyWindowsAcl(path2) {
|
|
55655
|
-
const expectedSid = await currentWindowsSid();
|
|
55656
|
-
try {
|
|
55657
|
-
const arguments_ = ["-NoLogo", "-NoProfile", "-NonInteractive", "-Command", inspectWindowsAclScript];
|
|
55658
|
-
const options = {
|
|
55659
|
-
env: { ...process.env, FINCHTECH_ACL_PATH: path2 },
|
|
55660
|
-
windowsHide: true
|
|
55661
|
-
};
|
|
55662
|
-
let stdout;
|
|
55663
|
-
try {
|
|
55664
|
-
({ stdout } = await execFileAsync("pwsh.exe", arguments_, options));
|
|
55665
|
-
} catch {
|
|
55666
|
-
({ stdout } = await execFileAsync("powershell.exe", arguments_, options));
|
|
55667
|
-
}
|
|
55668
|
-
const inspection = JSON.parse(stdout);
|
|
55669
|
-
const unrelatedRule = inspection.rules.some((rule) => rule.sid !== expectedSid && !WINDOWS_PLATFORM_ADMINISTRATOR_SIDS.has(rule.sid) || rule.type !== "Allow");
|
|
55670
|
-
if (inspection.ownerSid !== expectedSid || !inspection.protected || inspection.rules.length < 1 || inspection.rules.some((rule) => rule.inherited) || unrelatedRule) {
|
|
55671
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55672
|
-
}
|
|
55673
|
-
} catch (cause) {
|
|
55674
|
-
if (cause instanceof CliStateError) throw cause;
|
|
55675
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
55676
|
-
}
|
|
55677
|
-
}
|
|
55678
|
-
async function harden(path2, directory) {
|
|
55679
|
-
if (process.platform === "win32") {
|
|
55680
|
-
await hardenWindows(path2, directory);
|
|
55681
|
-
} else {
|
|
55682
|
-
await chmod(path2, directory ? 448 : 384);
|
|
55683
|
-
}
|
|
55684
|
-
}
|
|
55685
|
-
async function writeExclusiveProtectedFile(path2, value) {
|
|
55686
|
-
const target = resolve(path2);
|
|
55687
|
-
const parent = dirname(target);
|
|
55688
|
-
if (!isAbsolute(path2) || parse3(target).root === target) {
|
|
55689
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55690
|
-
}
|
|
55691
|
-
let handle;
|
|
55692
|
-
let created = false;
|
|
55693
|
-
try {
|
|
55694
|
-
await assertSafePathComponents(parent);
|
|
55695
|
-
await defaultPermissionPolicy.verify(parent, true);
|
|
55696
|
-
handle = await open(target, "wx", 384);
|
|
55697
|
-
created = true;
|
|
55698
|
-
const opened = await handle.stat();
|
|
55699
|
-
const pathAfterOpen = await lstat(target);
|
|
55700
|
-
if (!opened.isFile() || opened.nlink !== 1 || !sameIdentity(opened, pathAfterOpen)) {
|
|
55701
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55702
|
-
}
|
|
55703
|
-
await defaultPermissionPolicy.hardenNew(target, false);
|
|
55704
|
-
await defaultPermissionPolicy.verify(target, false);
|
|
55705
|
-
const openedAfterAcl = await handle.stat();
|
|
55706
|
-
const pathAfterAcl = await lstat(target);
|
|
55707
|
-
if (!sameIdentity(opened, openedAfterAcl) || !sameIdentity(opened, pathAfterAcl) || openedAfterAcl.nlink !== 1) {
|
|
55708
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55709
|
-
}
|
|
55710
|
-
await handle.writeFile(value);
|
|
55711
|
-
await handle.sync();
|
|
55712
|
-
await handle.close();
|
|
55713
|
-
handle = void 0;
|
|
55714
|
-
await defaultSyncDirectory(parent);
|
|
55715
|
-
} catch (cause) {
|
|
55716
|
-
await handle?.close().catch(() => void 0);
|
|
55717
|
-
if (cause.code === "EEXIST") {
|
|
55718
|
-
throw new CliStateError("CLI_STATE_CONFLICT", { cause });
|
|
55719
|
-
}
|
|
55720
|
-
if (created) {
|
|
55721
|
-
await rm(target, { force: true }).catch(() => void 0);
|
|
55722
|
-
await defaultSyncDirectory(parent).catch(() => void 0);
|
|
55723
|
-
}
|
|
55724
|
-
throw permissionFailure(cause);
|
|
55725
|
-
}
|
|
55726
|
-
}
|
|
55727
|
-
async function assertSafeNode(path2, directory) {
|
|
55728
|
-
const metadata = await lstat(path2);
|
|
55729
|
-
if (metadata.isSymbolicLink() || (directory ? !metadata.isDirectory() : !metadata.isFile())) {
|
|
55730
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55731
|
-
}
|
|
55732
|
-
if (!directory && metadata.nlink !== 1) {
|
|
55733
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55734
|
-
}
|
|
55735
|
-
if (process.platform !== "win32") {
|
|
55736
|
-
const expected = directory ? 448 : 384;
|
|
55737
|
-
if ((metadata.mode & 511) !== expected || typeof process.getuid === "function" && metadata.uid !== process.getuid()) {
|
|
55738
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55739
|
-
}
|
|
55740
|
-
} else {
|
|
55741
|
-
await verifyWindowsAcl(path2);
|
|
55742
|
-
}
|
|
55743
|
-
}
|
|
55744
|
-
async function defaultSyncDirectory(path2) {
|
|
55745
|
-
const handle = await open(path2, process.platform === "win32" ? "r+" : "r");
|
|
55746
|
-
try {
|
|
55747
|
-
await handle.sync();
|
|
55748
|
-
} finally {
|
|
55749
|
-
await handle.close();
|
|
55750
|
-
}
|
|
55751
|
-
}
|
|
55752
|
-
function samePath(left, right) {
|
|
55753
|
-
const normalizedLeft = normalize(left);
|
|
55754
|
-
const normalizedRight = normalize(right);
|
|
55755
|
-
return process.platform === "win32" ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase() : normalizedLeft === normalizedRight;
|
|
55756
|
-
}
|
|
55757
|
-
async function assertSafePathComponents(path2) {
|
|
55758
|
-
const target = resolve(path2);
|
|
55759
|
-
const root = parse3(target).root;
|
|
55760
|
-
let cursor = root;
|
|
55761
|
-
for (const component of relative(root, target).split(sep).filter(Boolean)) {
|
|
55762
|
-
cursor = resolve(cursor, component);
|
|
55763
|
-
const metadata = await lstat(cursor);
|
|
55764
|
-
if (metadata.isSymbolicLink()) {
|
|
55765
|
-
const darwinSystemTarget = process.platform === "darwin" ? DARWIN_SYSTEM_LINK_TARGETS.get(cursor) : void 0;
|
|
55766
|
-
if (darwinSystemTarget === void 0 || !samePath(await realpath(cursor), darwinSystemTarget)) {
|
|
55767
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55768
|
-
}
|
|
55769
|
-
}
|
|
55770
|
-
}
|
|
55771
|
-
}
|
|
55772
|
-
function permissionFailure(cause) {
|
|
55773
|
-
return cause instanceof CliStateError ? cause : new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
55774
|
-
}
|
|
55775
|
-
function sameIdentity(left, right) {
|
|
55776
|
-
return left.dev === right.dev && left.ino === right.ino;
|
|
55777
|
-
}
|
|
55778
|
-
async function readProtectedFile(path2, maximumBytes) {
|
|
55779
|
-
const target = resolve(path2);
|
|
55780
|
-
const parent = dirname(target);
|
|
55781
|
-
if (!isAbsolute(path2) || !Number.isInteger(maximumBytes) || maximumBytes < 1) {
|
|
55782
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55783
|
-
}
|
|
55784
|
-
let handle;
|
|
55785
|
-
try {
|
|
55786
|
-
await assertSafePathComponents(parent);
|
|
55787
|
-
await defaultPermissionPolicy.verify(parent, true);
|
|
55788
|
-
const before = await lstat(target);
|
|
55789
|
-
const noFollow = constants.O_NOFOLLOW ?? 0;
|
|
55790
|
-
handle = await open(target, constants.O_RDONLY | noFollow);
|
|
55791
|
-
const opened = await handle.stat();
|
|
55792
|
-
const afterOpen = await lstat(target);
|
|
55793
|
-
if (!sameIdentity(before, opened) || !sameIdentity(opened, afterOpen) || !opened.isFile() || opened.nlink !== 1 || opened.size < 1 || opened.size > maximumBytes) {
|
|
55794
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55795
|
-
}
|
|
55796
|
-
if (process.platform === "win32") {
|
|
55797
|
-
await verifyWindowsAcl(target);
|
|
55798
|
-
} else if ((opened.mode & 511) !== 384 || typeof process.getuid === "function" && opened.uid !== process.getuid()) {
|
|
55799
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55800
|
-
}
|
|
55801
|
-
const afterAcl = await lstat(target);
|
|
55802
|
-
if (!sameIdentity(opened, afterAcl)) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55803
|
-
const body = Buffer.alloc(opened.size);
|
|
55804
|
-
let offset = 0;
|
|
55805
|
-
while (offset < body.length) {
|
|
55806
|
-
const chunk = await handle.read(body, offset, body.length - offset, offset);
|
|
55807
|
-
if (chunk.bytesRead === 0) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55808
|
-
offset += chunk.bytesRead;
|
|
55809
|
-
}
|
|
55810
|
-
const stable = await handle.stat();
|
|
55811
|
-
if (!sameIdentity(opened, stable) || stable.size !== opened.size) {
|
|
55812
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55813
|
-
}
|
|
55814
|
-
return body;
|
|
55815
|
-
} catch (cause) {
|
|
55816
|
-
throw permissionFailure(cause);
|
|
55817
|
-
} finally {
|
|
55818
|
-
await handle?.close().catch(() => void 0);
|
|
55819
|
-
}
|
|
55820
|
-
}
|
|
55821
|
-
var execFileAsync, DARWIN_SYSTEM_LINK_TARGETS, CliStateError, windowsSidPromise, WINDOWS_PLATFORM_ADMINISTRATOR_SIDS, inspectWindowsAclScript, defaultPermissionPolicy, HardenedFileStore;
|
|
55822
|
-
var init_state = __esm({
|
|
55823
|
-
"src/state.ts"() {
|
|
55824
|
-
"use strict";
|
|
55825
|
-
execFileAsync = promisify(execFile);
|
|
55826
|
-
DARWIN_SYSTEM_LINK_TARGETS = /* @__PURE__ */ new Map([
|
|
55827
|
-
["/etc", "/private/etc"],
|
|
55828
|
-
["/tmp", "/private/tmp"],
|
|
55829
|
-
["/var", "/private/var"]
|
|
55830
|
-
]);
|
|
55831
|
-
CliStateError = class extends Error {
|
|
55832
|
-
code;
|
|
55833
|
-
diagnostic;
|
|
55834
|
-
constructor(code, options) {
|
|
55835
|
-
super(code, options);
|
|
55836
|
-
this.name = "CliStateError";
|
|
55837
|
-
this.code = code;
|
|
55838
|
-
this.diagnostic = options?.diagnostic ?? null;
|
|
55839
|
-
}
|
|
55840
|
-
};
|
|
55841
|
-
WINDOWS_PLATFORM_ADMINISTRATOR_SIDS = /* @__PURE__ */ new Set([
|
|
55842
|
-
"S-1-5-18",
|
|
55843
|
-
// LocalSystem
|
|
55844
|
-
"S-1-5-32-544"
|
|
55845
|
-
// BUILTIN\Administrators
|
|
55846
|
-
]);
|
|
55847
|
-
inspectWindowsAclScript = String.raw`
|
|
55848
|
-
$ErrorActionPreference = 'Stop'
|
|
55849
|
-
$acl = Get-Acl -LiteralPath $env:FINCHTECH_ACL_PATH
|
|
55850
|
-
$ownerSid = ([System.Security.Principal.NTAccount]$acl.Owner).Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
55851
|
-
$rules = @($acl.Access | ForEach-Object {
|
|
55852
|
-
$sid = $_.IdentityReference.Translate([System.Security.Principal.SecurityIdentifier]).Value
|
|
55853
|
-
[pscustomobject]@{ sid = $sid; inherited = $_.IsInherited; type = $_.AccessControlType.ToString() }
|
|
55854
|
-
})
|
|
55855
|
-
[pscustomobject]@{ ownerSid = $ownerSid; protected = $acl.AreAccessRulesProtected; rules = $rules } |
|
|
55856
|
-
ConvertTo-Json -Compress -Depth 4
|
|
55857
|
-
`;
|
|
55858
|
-
defaultPermissionPolicy = Object.freeze({
|
|
55859
|
-
hardenNew: harden,
|
|
55860
|
-
verify: assertSafeNode
|
|
55861
|
-
});
|
|
55862
|
-
HardenedFileStore = class {
|
|
55863
|
-
root;
|
|
55864
|
-
permissions;
|
|
55865
|
-
syncDirectory;
|
|
55866
|
-
constructor(root, options = {}) {
|
|
55867
|
-
const resolved = resolve(root);
|
|
55868
|
-
if (!isAbsolute(root) || samePath(resolved, parse3(resolved).root)) {
|
|
55869
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55870
|
-
}
|
|
55871
|
-
this.root = resolved;
|
|
55872
|
-
this.permissions = options.permissions ?? defaultPermissionPolicy;
|
|
55873
|
-
this.syncDirectory = options.syncDirectory ?? defaultSyncDirectory;
|
|
55874
|
-
}
|
|
55875
|
-
resolveRelative(path2) {
|
|
55876
|
-
if (path2.length === 0 || isAbsolute(path2) || path2.includes("\0")) {
|
|
55877
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55878
|
-
}
|
|
55879
|
-
const target = resolve(this.root, path2);
|
|
55880
|
-
const fromRoot = relative(this.root, target);
|
|
55881
|
-
if (fromRoot === ".." || fromRoot.startsWith(`..${sep}`) || isAbsolute(fromRoot)) {
|
|
55882
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55883
|
-
}
|
|
55884
|
-
return target;
|
|
55885
|
-
}
|
|
55886
|
-
async ensureDirectory(path2) {
|
|
55887
|
-
try {
|
|
55888
|
-
const metadata = await lstat(path2);
|
|
55889
|
-
if (metadata.isSymbolicLink() || !metadata.isDirectory()) {
|
|
55890
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55891
|
-
}
|
|
55892
|
-
await assertSafePathComponents(path2);
|
|
55893
|
-
await this.permissions.verify(path2, true);
|
|
55894
|
-
return;
|
|
55895
|
-
} catch (cause) {
|
|
55896
|
-
if (cause.code !== "ENOENT") throw permissionFailure(cause);
|
|
55897
|
-
}
|
|
55898
|
-
const parent = dirname(path2);
|
|
55899
|
-
await assertSafePathComponents(parent);
|
|
55900
|
-
let created = false;
|
|
55901
|
-
try {
|
|
55902
|
-
await mkdir(path2, { recursive: false, mode: 448 });
|
|
55903
|
-
created = true;
|
|
55904
|
-
await this.permissions.hardenNew(path2, true);
|
|
55905
|
-
await this.permissions.verify(path2, true);
|
|
55906
|
-
await this.syncDirectory(parent);
|
|
55907
|
-
} catch (cause) {
|
|
55908
|
-
if (created) {
|
|
55909
|
-
await rm(path2, { recursive: true, force: true }).catch(() => void 0);
|
|
55910
|
-
await this.syncDirectory(parent).catch(() => void 0);
|
|
55911
|
-
}
|
|
55912
|
-
throw permissionFailure(cause);
|
|
55913
|
-
}
|
|
55914
|
-
}
|
|
55915
|
-
async ensureParent(target) {
|
|
55916
|
-
await this.ensureDirectory(this.root);
|
|
55917
|
-
const parent = resolve(target, "..");
|
|
55918
|
-
const fromRoot = relative(this.root, parent);
|
|
55919
|
-
let cursor = this.root;
|
|
55920
|
-
for (const component of fromRoot.split(sep).filter(Boolean)) {
|
|
55921
|
-
cursor = resolve(cursor, component);
|
|
55922
|
-
await this.ensureDirectory(cursor);
|
|
55923
|
-
}
|
|
55924
|
-
}
|
|
55925
|
-
async writeSecret(path2, value, options) {
|
|
55926
|
-
if (!options.exclusive) throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID");
|
|
55927
|
-
const target = this.resolveRelative(path2);
|
|
55928
|
-
await this.ensureParent(target);
|
|
55929
|
-
let handle;
|
|
55930
|
-
let created = false;
|
|
55931
|
-
try {
|
|
55932
|
-
handle = await open(target, "wx", 384);
|
|
55933
|
-
created = true;
|
|
55934
|
-
await this.permissions.hardenNew(target, false);
|
|
55935
|
-
await this.permissions.verify(target, false);
|
|
55936
|
-
await handle.writeFile(value);
|
|
55937
|
-
await handle.sync();
|
|
55938
|
-
await handle.close();
|
|
55939
|
-
handle = void 0;
|
|
55940
|
-
await this.syncDirectory(dirname(target));
|
|
55941
|
-
} catch (cause) {
|
|
55942
|
-
await handle?.close().catch(() => void 0);
|
|
55943
|
-
if (cause.code === "EEXIST") {
|
|
55944
|
-
throw new CliStateError("CLI_STATE_CONFLICT", { cause });
|
|
55945
|
-
}
|
|
55946
|
-
if (created) {
|
|
55947
|
-
await rm(target, { force: true }).catch(() => void 0);
|
|
55948
|
-
await this.syncDirectory(dirname(target)).catch(() => void 0);
|
|
55949
|
-
}
|
|
55950
|
-
throw permissionFailure(cause);
|
|
55951
|
-
}
|
|
55952
|
-
}
|
|
55953
|
-
async writeJson(path2, value) {
|
|
55954
|
-
const target = this.resolveRelative(path2);
|
|
55955
|
-
const temporary = `${target}.tmp-${process.pid}-${Date.now()}`;
|
|
55956
|
-
await this.ensureParent(target);
|
|
55957
|
-
try {
|
|
55958
|
-
await this.writeSecret(relative(this.root, temporary), Buffer.from(`${JSON.stringify(value)}
|
|
55959
|
-
`, "utf8"), {
|
|
55960
|
-
exclusive: true
|
|
55961
|
-
});
|
|
55962
|
-
try {
|
|
55963
|
-
await lstat(target);
|
|
55964
|
-
await this.permissions.verify(target, false);
|
|
55965
|
-
} catch (cause) {
|
|
55966
|
-
if (cause.code !== "ENOENT") throw cause;
|
|
55967
|
-
}
|
|
55968
|
-
await rename(temporary, target);
|
|
55969
|
-
await this.permissions.verify(target, false);
|
|
55970
|
-
await this.syncDirectory(dirname(target));
|
|
55971
|
-
} catch (cause) {
|
|
55972
|
-
await rm(temporary, { force: true }).catch(() => void 0);
|
|
55973
|
-
throw cause;
|
|
55974
|
-
}
|
|
55975
|
-
}
|
|
55976
|
-
async readSecret(path2) {
|
|
55977
|
-
const target = this.resolveRelative(path2);
|
|
55978
|
-
try {
|
|
55979
|
-
await this.permissions.verify(target, false);
|
|
55980
|
-
return await readFile(target);
|
|
55981
|
-
} catch (cause) {
|
|
55982
|
-
if (cause.code === "ENOENT") {
|
|
55983
|
-
throw new CliStateError("CLI_STATE_NOT_FOUND", { cause });
|
|
55984
|
-
}
|
|
55985
|
-
if (cause instanceof CliStateError) throw cause;
|
|
55986
|
-
throw new CliStateError("CLI_STATE_PERMISSIONS_INVALID", { cause });
|
|
55987
|
-
}
|
|
55988
|
-
}
|
|
55989
|
-
async readJson(path2) {
|
|
55990
|
-
try {
|
|
55991
|
-
return JSON.parse((await this.readSecret(path2)).toString("utf8"));
|
|
55992
|
-
} catch (cause) {
|
|
55993
|
-
if (cause instanceof CliStateError && cause.code !== "CLI_STATE_CORRUPT") throw cause;
|
|
55994
|
-
throw new CliStateError("CLI_STATE_CORRUPT", { cause });
|
|
55995
|
-
}
|
|
55996
|
-
}
|
|
55997
|
-
async remove(path2, options = {}) {
|
|
55998
|
-
const target = this.resolveRelative(path2);
|
|
55999
|
-
await rm(target, { force: true, recursive: options.recursive ?? false });
|
|
56000
|
-
await this.syncDirectory(dirname(target)).catch((cause) => {
|
|
56001
|
-
if (cause.code !== "ENOENT") throw cause;
|
|
56002
|
-
});
|
|
56003
|
-
}
|
|
56004
|
-
};
|
|
56005
|
-
}
|
|
56006
|
-
});
|
|
56007
|
-
|
|
56008
56071
|
// src/skill-local-flow.ts
|
|
56009
56072
|
import { createHash } from "node:crypto";
|
|
56010
56073
|
import { lstat as lstat2, readFile as readFile2, readdir } from "node:fs/promises";
|
|
@@ -56262,15 +56325,6 @@ function assetPlan(value, walletAddress2) {
|
|
|
56262
56325
|
}
|
|
56263
56326
|
return { chainId, skillId, acceptedMediaTypes: accepted, maxBytes: plan.maxBytes };
|
|
56264
56327
|
}
|
|
56265
|
-
function coverMediaType(bytes) {
|
|
56266
|
-
const png = [137, 80, 78, 71, 13, 10, 26, 10];
|
|
56267
|
-
if (bytes.length >= 8 && png.every((byte, index2) => bytes[index2] === byte)) return "image/png";
|
|
56268
|
-
if (bytes.length >= 3 && bytes[0] === 255 && bytes[1] === 216 && bytes[2] === 255) return "image/jpeg";
|
|
56269
|
-
const head = new TextDecoder().decode(bytes.slice(0, 12));
|
|
56270
|
-
if (head.startsWith("GIF87a") || head.startsWith("GIF89a")) return "image/gif";
|
|
56271
|
-
if (head.startsWith("RIFF") && head.slice(8, 12) === "WEBP") return "image/webp";
|
|
56272
|
-
return null;
|
|
56273
|
-
}
|
|
56274
56328
|
function isPlaintextSkillDelivery(value) {
|
|
56275
56329
|
return value !== null && typeof value === "object" && !Array.isArray(value) && value.plaintext === true;
|
|
56276
56330
|
}
|
|
@@ -57147,7 +57201,7 @@ var init_skill_local_flow = __esm({
|
|
|
57147
57201
|
if (!stat.isFile() || stat.isSymbolicLink()) throw new TypeError("Skill cover path must be one regular file.");
|
|
57148
57202
|
if (stat.size < 1 || stat.size > plan.maxBytes) throw new TypeError("Skill cover size is outside the prepared bound.");
|
|
57149
57203
|
const bytes = new Uint8Array(await readFile2(absolute));
|
|
57150
|
-
const mediaType =
|
|
57204
|
+
const mediaType = detectedImageMediaType(bytes);
|
|
57151
57205
|
if (mediaType === null || !plan.acceptedMediaTypes.includes(mediaType)) {
|
|
57152
57206
|
throw new TypeError("Skill cover bytes do not match an accepted image type.");
|
|
57153
57207
|
}
|
|
@@ -58100,32 +58154,89 @@ var init_application = __esm({
|
|
|
58100
58154
|
return result2;
|
|
58101
58155
|
}
|
|
58102
58156
|
async doctorMcp() {
|
|
58103
|
-
const
|
|
58104
|
-
|
|
58105
|
-
|
|
58106
|
-
|
|
58107
|
-
|
|
58108
|
-
|
|
58109
|
-
|
|
58110
|
-
|
|
58111
|
-
|
|
58112
|
-
|
|
58113
|
-
|
|
58157
|
+
const inspect = async (run, guidance) => {
|
|
58158
|
+
try {
|
|
58159
|
+
const value = await run();
|
|
58160
|
+
return [value, { status: "ok" }];
|
|
58161
|
+
} catch (cause) {
|
|
58162
|
+
if (cause instanceof CliStateError) {
|
|
58163
|
+
return [null, {
|
|
58164
|
+
status: cause.code === "CLI_STATE_NOT_FOUND" ? "missing" : "invalid",
|
|
58165
|
+
message: localFailureMessage(cause)
|
|
58166
|
+
}];
|
|
58167
|
+
} else if (cause instanceof CliHttpError) {
|
|
58168
|
+
return [null, { status: cause.status === 401 || cause.status === 403 || cause.code === "CLI_RESPONSE_INVALID" ? "invalid" : "unavailable", message: guidance }];
|
|
58169
|
+
} else if (cause instanceof TypeError || cause instanceof CliAuthorityError) {
|
|
58170
|
+
return [null, { status: "invalid", message: guidance }];
|
|
58171
|
+
} else {
|
|
58172
|
+
throw cause;
|
|
58173
|
+
}
|
|
58174
|
+
}
|
|
58175
|
+
};
|
|
58176
|
+
const [profile, endpointCheck] = await inspect(
|
|
58177
|
+
() => this.authorizedProfile(),
|
|
58178
|
+
"Check the protected FINCHTECH_HOME/profile.json endpoint override."
|
|
58179
|
+
);
|
|
58180
|
+
const [wallet, walletCheck] = await inspect(
|
|
58181
|
+
() => required3(this.state.readWalletAddress, "state.readWalletAddress").call(this.state),
|
|
58182
|
+
"Restore the current wallet from backup."
|
|
58183
|
+
);
|
|
58184
|
+
const [session, sessionCheck] = await inspect(
|
|
58185
|
+
() => required3(this.state.readSession, "state.readSession").call(this.state),
|
|
58186
|
+
"Run finch logout --local, then finch login --help to log in again."
|
|
58187
|
+
);
|
|
58188
|
+
let remoteSession = null;
|
|
58189
|
+
let remoteSessionCheck;
|
|
58190
|
+
if (profile !== null && session !== null && wallet !== null) {
|
|
58191
|
+
[remoteSession, remoteSessionCheck] = await inspect(async () => {
|
|
58192
|
+
if (session.origin !== profile.origin || wallet.walletAddress !== session.walletAddress) {
|
|
58193
|
+
throw new CliAuthorityError();
|
|
58194
|
+
}
|
|
58195
|
+
const remote = asObject(await required3(this.api.getSession, "api.getSession").call(this.api, session.rawBearer), "MCP doctor Session");
|
|
58196
|
+
if (remote.sessionId !== session.sessionId || remote.accountId !== session.accountId || remote.walletAddress !== session.walletAddress || remote.environment !== session.environment || remote.audience !== "agent_cli" || typeof remote.sessionExpiresAt !== "string" || !Number.isFinite(Date.parse(remote.sessionExpiresAt)) || Date.parse(remote.sessionExpiresAt) <= Date.now()) throw new CliAuthorityError();
|
|
58197
|
+
return { sessionExpiresAt: remote.sessionExpiresAt };
|
|
58198
|
+
}, "Verify the endpoint and current wallet, then run finch login --help to renew the CLI login. If the server is unavailable, retry later.");
|
|
58199
|
+
} else {
|
|
58200
|
+
remoteSessionCheck = { status: "skipped", message: "Requires a valid endpoint, local wallet, and CLI Session." };
|
|
58201
|
+
}
|
|
58202
|
+
let mcp = null;
|
|
58203
|
+
let mcpDiscoveryCheck;
|
|
58204
|
+
if (profile !== null) {
|
|
58205
|
+
[mcp, mcpDiscoveryCheck] = await inspect(async () => {
|
|
58206
|
+
const discovery = await required3(this.api.inspectRemoteMcp, "api.inspectRemoteMcp").call(this.api);
|
|
58207
|
+
if (discovery.url !== `${profile.origin}/mcp` || discovery.authorization !== "oauth" || JSON.stringify(discovery.supportedClients) !== JSON.stringify(["codex"])) {
|
|
58208
|
+
throw new TypeError("MCP discovery authority mismatch.");
|
|
58209
|
+
}
|
|
58210
|
+
return discovery;
|
|
58211
|
+
}, "Check endpoint connectivity and MCP OAuth discovery configuration, then retry finch mcp doctor.");
|
|
58212
|
+
} else {
|
|
58213
|
+
mcpDiscoveryCheck = { status: "skipped", message: "Requires a valid endpoint." };
|
|
58214
|
+
}
|
|
58215
|
+
const checks = {
|
|
58216
|
+
endpoint: endpointCheck,
|
|
58217
|
+
wallet: walletCheck,
|
|
58218
|
+
session: sessionCheck,
|
|
58219
|
+
remoteSession: remoteSessionCheck,
|
|
58220
|
+
mcpDiscovery: mcpDiscoveryCheck
|
|
58221
|
+
};
|
|
58114
58222
|
return Object.freeze({
|
|
58115
|
-
ready:
|
|
58223
|
+
ready: Object.values(checks).every((check2) => check2.status === "ok"),
|
|
58116
58224
|
readinessScope: "cli_session_and_mcp_discovery",
|
|
58117
|
-
|
|
58225
|
+
checks,
|
|
58226
|
+
harnessOAuth: {
|
|
58118
58227
|
status: "not_checked",
|
|
58119
58228
|
guidance: "Verify the connected MCP identity with identity_actor_get and compare it with finch status. If the Harness reports missing or expired OAuth credentials, reauthorize through its native MCP login flow."
|
|
58120
|
-
}
|
|
58121
|
-
|
|
58122
|
-
|
|
58123
|
-
|
|
58124
|
-
|
|
58125
|
-
|
|
58126
|
-
|
|
58127
|
-
|
|
58128
|
-
|
|
58229
|
+
},
|
|
58230
|
+
...profile === null ? {} : { origin: profile.origin },
|
|
58231
|
+
...wallet === null ? {} : { walletAddress: wallet.walletAddress },
|
|
58232
|
+
...session === null ? {} : {
|
|
58233
|
+
environment: session.environment,
|
|
58234
|
+
loginChainId: session.loginChainId,
|
|
58235
|
+
accountId: session.accountId,
|
|
58236
|
+
sessionId: session.sessionId
|
|
58237
|
+
},
|
|
58238
|
+
...remoteSession === null ? {} : { sessionExpiresAt: remoteSession.sessionExpiresAt },
|
|
58239
|
+
...mcp === null ? {} : { mcp }
|
|
58129
58240
|
});
|
|
58130
58241
|
}
|
|
58131
58242
|
async assertNoBlockingRecoveries() {
|
|
@@ -58185,7 +58296,16 @@ var init_application = __esm({
|
|
|
58185
58296
|
throw new TypeError("wallet use requires exactly one registered address or --file path.");
|
|
58186
58297
|
}
|
|
58187
58298
|
await this.assertNoBlockingRecoveries();
|
|
58188
|
-
|
|
58299
|
+
let current;
|
|
58300
|
+
try {
|
|
58301
|
+
current = await required3(this.state.readWallet, "state.readWallet").call(this.state);
|
|
58302
|
+
} catch (cause) {
|
|
58303
|
+
if (!(cause instanceof CliStateError) || cause.code !== "CLI_STATE_NOT_FOUND" || input.file === void 0) throw cause;
|
|
58304
|
+
const wallets = await required3(this.state.listWalletAddresses, "state.listWalletAddresses").call(this.state);
|
|
58305
|
+
if (wallets.length !== 0) throw new CliStateError("CLI_STATE_CORRUPT", { cause, diagnostic: "wallet" });
|
|
58306
|
+
await required3(this.state.assertWalletBootstrap, "state.assertWalletBootstrap").call(this.state);
|
|
58307
|
+
return required3(this.state.importWallet, "state.importWallet").call(this.state, input.file);
|
|
58308
|
+
}
|
|
58189
58309
|
const target = input.file === void 0 ? { walletAddress: normalizeCliEvmAddress(input.address, "Wallet address") } : await required3(this.state.importWallet, "state.importWallet").call(this.state, input.file);
|
|
58190
58310
|
if (!/^0x[0-9a-f]{40}$/u.test(target.walletAddress)) throw new TypeError("Wallet address is invalid.");
|
|
58191
58311
|
if (input.file === void 0) {
|
|
@@ -58937,7 +59057,7 @@ var init_package = __esm({
|
|
|
58937
59057
|
"package.json"() {
|
|
58938
59058
|
package_default = {
|
|
58939
59059
|
name: "@finchtech/cli",
|
|
58940
|
-
version: "0.3.
|
|
59060
|
+
version: "0.3.5",
|
|
58941
59061
|
description: "Finch Agent CLI for wallet-owned marketplace and MCP workflows.",
|
|
58942
59062
|
type: "module",
|
|
58943
59063
|
bin: {
|
|
@@ -59066,7 +59186,11 @@ async function routeCommand(arguments_, application) {
|
|
|
59066
59186
|
return Object.freeze({
|
|
59067
59187
|
command: "help",
|
|
59068
59188
|
topic: commandTopic,
|
|
59069
|
-
usage: FINCH_COMMAND_USAGE[commandTopic]
|
|
59189
|
+
usage: FINCH_COMMAND_USAGE[commandTopic],
|
|
59190
|
+
...commandTopic === "login" ? {
|
|
59191
|
+
authenticationChainIds: PRODUCTION_AUTH_CHAIN_IDS,
|
|
59192
|
+
guidance: "Choose an authentication chain, for example finch login --chain-id 56. The server validates support; market transaction chains are independent."
|
|
59193
|
+
} : {}
|
|
59070
59194
|
});
|
|
59071
59195
|
}
|
|
59072
59196
|
const commands = FINCH_COMMANDS.filter((command) => command.startsWith(`${topic} `));
|
|
@@ -59422,6 +59546,7 @@ var init_command_router = __esm({
|
|
|
59422
59546
|
"src/command-router.ts"() {
|
|
59423
59547
|
"use strict";
|
|
59424
59548
|
init_package();
|
|
59549
|
+
init_v1();
|
|
59425
59550
|
FINCH_COMMANDS = Object.freeze([
|
|
59426
59551
|
"wallet create",
|
|
59427
59552
|
"wallet show",
|
|
@@ -59899,7 +60024,14 @@ var init_wallet_store = __esm({
|
|
|
59899
60024
|
await this.migrateLegacy();
|
|
59900
60025
|
const current = record4(await this.files.readJson("wallet/current.json"));
|
|
59901
60026
|
if (current.version !== 1) throw new CliStateError("CLI_STATE_CORRUPT");
|
|
59902
|
-
|
|
60027
|
+
try {
|
|
60028
|
+
return await this.readRegistered(walletAddress(current.walletAddress));
|
|
60029
|
+
} catch (cause) {
|
|
60030
|
+
if (cause instanceof CliStateError && cause.code === "CLI_STATE_NOT_FOUND") {
|
|
60031
|
+
throw new CliStateError("CLI_STATE_CORRUPT", { cause });
|
|
60032
|
+
}
|
|
60033
|
+
throw cause;
|
|
60034
|
+
}
|
|
59903
60035
|
}
|
|
59904
60036
|
async readCurrentAddress() {
|
|
59905
60037
|
const current = await this.readCurrent();
|
|
@@ -59911,6 +60043,8 @@ var init_wallet_store = __esm({
|
|
|
59911
60043
|
|
|
59912
60044
|
// src/local-state.ts
|
|
59913
60045
|
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
60046
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
60047
|
+
import { join as join3 } from "node:path";
|
|
59914
60048
|
function object5(value, name) {
|
|
59915
60049
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
59916
60050
|
throw new CliStateError("CLI_STATE_CORRUPT", { cause: new TypeError(`${name} must be an object.`) });
|
|
@@ -60183,10 +60317,27 @@ var init_local_state = __esm({
|
|
|
60183
60317
|
await this.wallets.register(input);
|
|
60184
60318
|
}
|
|
60185
60319
|
async readWallet() {
|
|
60186
|
-
|
|
60320
|
+
try {
|
|
60321
|
+
return await this.wallets.readCurrent();
|
|
60322
|
+
} catch (cause) {
|
|
60323
|
+
if (cause instanceof CliStateError) throw new CliStateError(cause.code, { cause, diagnostic: "wallet" });
|
|
60324
|
+
throw cause;
|
|
60325
|
+
}
|
|
60187
60326
|
}
|
|
60188
60327
|
async readWalletAddress() {
|
|
60189
|
-
|
|
60328
|
+
const wallet = await this.readWallet();
|
|
60329
|
+
return Object.freeze({ walletAddress: wallet.walletAddress });
|
|
60330
|
+
}
|
|
60331
|
+
async assertWalletBootstrap() {
|
|
60332
|
+
for (const path2 of ["session", "grants"]) {
|
|
60333
|
+
try {
|
|
60334
|
+
await lstat3(join3(this.files.root, path2));
|
|
60335
|
+
} catch (cause) {
|
|
60336
|
+
if (cause.code === "ENOENT") continue;
|
|
60337
|
+
throw cause;
|
|
60338
|
+
}
|
|
60339
|
+
throw new CliStateError("CLI_STATE_CONFLICT", { diagnostic: "wallet_bootstrap" });
|
|
60340
|
+
}
|
|
60190
60341
|
}
|
|
60191
60342
|
async importWallet(path2) {
|
|
60192
60343
|
return this.wallets.importFromFile(path2);
|
|
@@ -61985,7 +62136,7 @@ async function resolveInstalledProfile(readProfile) {
|
|
|
61985
62136
|
}
|
|
61986
62137
|
function commandNeedsHttp(arguments_) {
|
|
61987
62138
|
if (arguments_.length === 0 || arguments_.length === 1 && ["--help", "help", "--version"].includes(arguments_[0]) || arguments_[0] === "help" && arguments_.length > 1 || arguments_.length > 1 && arguments_.at(-1) === "--help" && arguments_.filter((argument) => argument === "--help").length === 1) return false;
|
|
61988
|
-
return arguments_[0] !== "wallet" && !(arguments_.length === 2 && arguments_[0] === "logout" && arguments_[1] === "--local");
|
|
62139
|
+
return (arguments_[0] !== "wallet" || arguments_[1] === "use") && !(arguments_.length === 2 && arguments_[0] === "logout" && arguments_[1] === "--local");
|
|
61989
62140
|
}
|
|
61990
62141
|
async function runInstalledCli(arguments_, environment3 = process.env, authorities = {}) {
|
|
61991
62142
|
const profileFree = arguments_.length === 0 || arguments_.length === 1 && ["--help", "help", "--version"].includes(arguments_[0]) || arguments_[0] === "help" && arguments_.length > 1 || arguments_.length > 1 && arguments_.at(-1) === "--help" && arguments_.filter((argument) => argument === "--help").length === 1;
|
|
@@ -61993,7 +62144,14 @@ async function runInstalledCli(arguments_, environment3 = process.env, authoriti
|
|
|
61993
62144
|
const home = resolveFinchtechHome(environment3);
|
|
61994
62145
|
const state = new LocalCliState(home);
|
|
61995
62146
|
const needsHttp = commandNeedsHttp(arguments_);
|
|
61996
|
-
|
|
62147
|
+
let profile;
|
|
62148
|
+
if (needsHttp) {
|
|
62149
|
+
try {
|
|
62150
|
+
profile = await resolveInstalledProfile(async () => state.readProfile());
|
|
62151
|
+
} catch (cause) {
|
|
62152
|
+
if (!(cause instanceof CliStateError) || arguments_.length !== 2 || arguments_[0] !== "mcp" || arguments_[1] !== "doctor") throw cause;
|
|
62153
|
+
}
|
|
62154
|
+
}
|
|
61997
62155
|
const api = profile === void 0 ? {} : new AgentCliHttpClient(profile.origin);
|
|
61998
62156
|
const signer = new InstalledWalletSigner();
|
|
61999
62157
|
const skillIntentSender = profile === void 0 || arguments_[0] !== "skill" ? void 0 : new InstalledSkillIntentSender();
|
|
@@ -62043,37 +62201,6 @@ function remoteExitCode(status) {
|
|
|
62043
62201
|
}
|
|
62044
62202
|
return AGENT_CLI_OUTPUT_CONTRACT.exitCodes.remoteOrProviderUnavailable;
|
|
62045
62203
|
}
|
|
62046
|
-
function localFailureMessage(error51) {
|
|
62047
|
-
if (error51 instanceof CliStateError) {
|
|
62048
|
-
if (error51.code === "CLI_STATE_PERMISSIONS_INVALID") {
|
|
62049
|
-
return process.platform === "win32" ? "Protected local state has unsafe ownership or ACLs. Restrict it to the current Windows user and retry." : "Protected local state has unsafe permissions. Directories must be mode 0700 and files mode 0600.";
|
|
62050
|
-
}
|
|
62051
|
-
if (error51.diagnostic === "profile") {
|
|
62052
|
-
return error51.code === "CLI_STATE_NOT_FOUND" ? "Finch endpoint override is missing. Remove the override to use PROD, or provision protected FINCHTECH_HOME/profile.json." : "Finch endpoint override is invalid. Remove it to use PROD, or reprovision protected FINCHTECH_HOME/profile.json.";
|
|
62053
|
-
}
|
|
62054
|
-
if (error51.diagnostic === "session") {
|
|
62055
|
-
return error51.code === "CLI_STATE_NOT_FOUND" ? "No active Finch login was found. Run finch login --chain-id <SUPPORTED_AUTH_CHAIN_ID>." : "The active Finch login is invalid. Run finch logout --local, then finch login --chain-id <SUPPORTED_AUTH_CHAIN_ID>.";
|
|
62056
|
-
}
|
|
62057
|
-
if (error51.code === "CLI_STATE_NOT_FOUND") {
|
|
62058
|
-
return "Required protected local state is missing. Check the command prerequisites and retry.";
|
|
62059
|
-
}
|
|
62060
|
-
if (error51.code === "CLI_STATE_CONFLICT") {
|
|
62061
|
-
return "Protected local state already exists or changed concurrently. Refresh state and retry.";
|
|
62062
|
-
}
|
|
62063
|
-
return "Protected local state is corrupt. Recreate the affected profile, login, grant, or recovery state.";
|
|
62064
|
-
}
|
|
62065
|
-
if (error51.message === "FINCHTECH_HOME is required.") {
|
|
62066
|
-
return "FINCHTECH_HOME is required. Set it to the protected Finch Agent CLI state directory.";
|
|
62067
|
-
}
|
|
62068
|
-
const missingScope = /^No active MCP grant with ([a-z_:]+) is available for this account and endpoint\.$/u.exec(error51.message);
|
|
62069
|
-
if (missingScope !== null) {
|
|
62070
|
-
return `Required MCP grant scope ${missingScope[1]} is missing. Create or refresh that scoped grant.`;
|
|
62071
|
-
}
|
|
62072
|
-
if (/^The selected .* MCP grant is invalid\.$/u.test(error51.message)) {
|
|
62073
|
-
return "The selected MCP grant is invalid. Recreate the grant for the active account and endpoint.";
|
|
62074
|
-
}
|
|
62075
|
-
return "Command input does not match the expected schema. Check finch help for this command.";
|
|
62076
|
-
}
|
|
62077
62204
|
function failure(error51) {
|
|
62078
62205
|
if (error51 instanceof CliSkillSourceError) {
|
|
62079
62206
|
return {
|
|
@@ -62123,6 +62250,9 @@ function emitCliOutcome(outcome, sinks) {
|
|
|
62123
62250
|
if (!(outcome instanceof Error)) {
|
|
62124
62251
|
sinks.stdout(`${JSON.stringify(outcome)}
|
|
62125
62252
|
`);
|
|
62253
|
+
if (typeof outcome === "object" && outcome !== null && "command" in outcome && outcome.command === "mcp.doctor" && "data" in outcome && typeof outcome.data === "object" && outcome.data !== null && "ready" in outcome.data && outcome.data.ready === false) {
|
|
62254
|
+
return AGENT_CLI_OUTPUT_CONTRACT.exitCodes.usageOrLocalSchema;
|
|
62255
|
+
}
|
|
62126
62256
|
return AGENT_CLI_OUTPUT_CONTRACT.exitCodes.success;
|
|
62127
62257
|
}
|
|
62128
62258
|
if (outcome instanceof CliSkillPublicationError) {
|