@finchtech/cli 0.3.3 → 0.3.4
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 +120 -112
- package/dist/finch.js +621 -503
- package/package.json +4 -4
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") {
|
|
@@ -14454,11 +14921,13 @@ function assertSortedIdentityScopes(scopes) {
|
|
|
14454
14921
|
previous = scope;
|
|
14455
14922
|
}
|
|
14456
14923
|
}
|
|
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;
|
|
14924
|
+
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
14925
|
var init_identity = __esm({
|
|
14459
14926
|
"../../packages/agent-market-contracts/src/v1/identity.ts"() {
|
|
14460
14927
|
"use strict";
|
|
14461
14928
|
init_common2();
|
|
14929
|
+
PRODUCTION_AUTH_CHAIN_IDS = Object.freeze([1, 10, 56, 8453, 42161]);
|
|
14930
|
+
TEST_AUTH_CHAIN_IDS = Object.freeze([84532]);
|
|
14462
14931
|
identityScopeValues = [
|
|
14463
14932
|
"account:read",
|
|
14464
14933
|
"agent_market:buyer:invoke",
|
|
@@ -15025,7 +15494,7 @@ var init_merchant = __esm({
|
|
|
15025
15494
|
});
|
|
15026
15495
|
publicationOperationResultSchema = strictObjectSchema({
|
|
15027
15496
|
serviceVersionId: canonicalUuidSchema,
|
|
15028
|
-
publicationGeneration: nonNegativeGenerationSchema,
|
|
15497
|
+
publicationGeneration: { anyOf: [nonNegativeGenerationSchema, nullSchema3] },
|
|
15029
15498
|
activeVersionRevision: positiveRevisionSchema
|
|
15030
15499
|
});
|
|
15031
15500
|
localPurchaseOperationResultSchema = strictObjectSchema({
|
|
@@ -16915,6 +17384,7 @@ var init_schemas = __esm({
|
|
|
16915
17384
|
perPage: { type: "integer", minimum: 1, maximum: 100 }
|
|
16916
17385
|
}, []);
|
|
16917
17386
|
createTaskDraftRequestSchema = strictObjectSchema2({
|
|
17387
|
+
expectedUpdatedAt: { type: "string", format: "date-time" },
|
|
16918
17388
|
title: { type: "string", minLength: 1, maxLength: 80 },
|
|
16919
17389
|
description: { type: "string", minLength: 1, maxLength: 500 },
|
|
16920
17390
|
goal: { type: "string", minLength: 1, maxLength: 2e3 },
|
|
@@ -16940,7 +17410,7 @@ var init_schemas = __esm({
|
|
|
16940
17410
|
startAt: nullableString,
|
|
16941
17411
|
goals: { type: "array", maxItems: 5, items: openObjectSchema },
|
|
16942
17412
|
publishNow: { const: false }
|
|
16943
|
-
}, ["title", "
|
|
17413
|
+
}, ["title", "goal", "rewardAmount", "publishNow"]);
|
|
16944
17414
|
taskSubmissionRequestSchema = strictObjectSchema2({
|
|
16945
17415
|
content: { type: "string", minLength: 1, maxLength: 1e5 },
|
|
16946
17416
|
proofUrl: nullableString,
|
|
@@ -18051,7 +18521,7 @@ var init_errors2 = __esm({
|
|
|
18051
18521
|
});
|
|
18052
18522
|
|
|
18053
18523
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js
|
|
18054
|
-
var _parse,
|
|
18524
|
+
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
18525
|
var init_parse = __esm({
|
|
18056
18526
|
"../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/core/parse.js"() {
|
|
18057
18527
|
init_core();
|
|
@@ -18070,7 +18540,7 @@ var init_parse = __esm({
|
|
|
18070
18540
|
}
|
|
18071
18541
|
return result2.value;
|
|
18072
18542
|
};
|
|
18073
|
-
|
|
18543
|
+
parse2 = /* @__PURE__ */ _parse($ZodRealError);
|
|
18074
18544
|
_parseAsync = (_Err) => async (schema4, value, _ctx, params) => {
|
|
18075
18545
|
const ctx = _ctx ? { ..._ctx, async: true } : { async: true };
|
|
18076
18546
|
let result2 = schema4._zod.run({ value, issues: [] }, ctx);
|
|
@@ -20916,10 +21386,10 @@ var init_schemas2 = __esm({
|
|
|
20916
21386
|
throw new Error("implement() must be called with a function");
|
|
20917
21387
|
}
|
|
20918
21388
|
return function(...args) {
|
|
20919
|
-
const parsedArgs = inst._def.input ?
|
|
21389
|
+
const parsedArgs = inst._def.input ? parse2(inst._def.input, args) : args;
|
|
20920
21390
|
const result2 = Reflect.apply(func, this, parsedArgs);
|
|
20921
21391
|
if (inst._def.output) {
|
|
20922
|
-
return
|
|
21392
|
+
return parse2(inst._def.output, result2);
|
|
20923
21393
|
}
|
|
20924
21394
|
return result2;
|
|
20925
21395
|
};
|
|
@@ -29714,7 +30184,7 @@ __export(core_exports2, {
|
|
|
29714
30184
|
isValidJWT: () => isValidJWT,
|
|
29715
30185
|
locales: () => locales_exports,
|
|
29716
30186
|
meta: () => meta,
|
|
29717
|
-
parse: () =>
|
|
30187
|
+
parse: () => parse2,
|
|
29718
30188
|
parseAsync: () => parseAsync,
|
|
29719
30189
|
prettifyError: () => prettifyError,
|
|
29720
30190
|
process: () => process2,
|
|
@@ -29889,12 +30359,12 @@ var init_errors3 = __esm({
|
|
|
29889
30359
|
});
|
|
29890
30360
|
|
|
29891
30361
|
// ../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/parse.js
|
|
29892
|
-
var
|
|
30362
|
+
var parse3, parseAsync2, safeParse2, safeParseAsync2, encode6, decode8, encodeAsync2, decodeAsync2, safeEncode2, safeDecode2, safeEncodeAsync2, safeDecodeAsync2;
|
|
29893
30363
|
var init_parse2 = __esm({
|
|
29894
30364
|
"../../node_modules/.pnpm/zod@4.4.3/node_modules/zod/v4/classic/parse.js"() {
|
|
29895
30365
|
init_core2();
|
|
29896
30366
|
init_errors3();
|
|
29897
|
-
|
|
30367
|
+
parse3 = /* @__PURE__ */ _parse(ZodRealError);
|
|
29898
30368
|
parseAsync2 = /* @__PURE__ */ _parseAsync(ZodRealError);
|
|
29899
30369
|
safeParse2 = /* @__PURE__ */ _safeParse(ZodRealError);
|
|
29900
30370
|
safeParseAsync2 = /* @__PURE__ */ _safeParseAsync(ZodRealError);
|
|
@@ -30614,7 +31084,7 @@ var init_schemas3 = __esm({
|
|
|
30614
31084
|
inst.def = def;
|
|
30615
31085
|
inst.type = def.type;
|
|
30616
31086
|
Object.defineProperty(inst, "_def", { value: def });
|
|
30617
|
-
inst.parse = (data, params) =>
|
|
31087
|
+
inst.parse = (data, params) => parse3(inst, data, params, { callee: inst.parse });
|
|
30618
31088
|
inst.safeParse = (data, params) => safeParse2(inst, data, params);
|
|
30619
31089
|
inst.parseAsync = async (data, params) => parseAsync2(inst, data, params, { callee: inst.parseAsync });
|
|
30620
31090
|
inst.safeParseAsync = async (data, params) => safeParseAsync2(inst, data, params);
|
|
@@ -32114,7 +32584,7 @@ __export(external_exports, {
|
|
|
32114
32584
|
object: () => object,
|
|
32115
32585
|
optional: () => optional,
|
|
32116
32586
|
overwrite: () => _overwrite,
|
|
32117
|
-
parse: () =>
|
|
32587
|
+
parse: () => parse3,
|
|
32118
32588
|
parseAsync: () => parseAsync2,
|
|
32119
32589
|
partialRecord: () => partialRecord,
|
|
32120
32590
|
pipe: () => pipe,
|
|
@@ -33160,7 +33630,7 @@ var init_web_localization = __esm({
|
|
|
33160
33630
|
languageSwitchEnabled: true
|
|
33161
33631
|
},
|
|
33162
33632
|
production: {
|
|
33163
|
-
languageSwitchEnabled:
|
|
33633
|
+
languageSwitchEnabled: true
|
|
33164
33634
|
}
|
|
33165
33635
|
}
|
|
33166
33636
|
};
|
|
@@ -54610,6 +55080,7 @@ var init_api2 = __esm({
|
|
|
54610
55080
|
ajv = new import_ajv.default({ allErrors: true, strict: false });
|
|
54611
55081
|
ajv.addFormat("date-time", (value) => Number.isFinite(Date.parse(value)));
|
|
54612
55082
|
ajv.addFormat("uri", (value) => URL.canParse(value));
|
|
55083
|
+
ajv.addFormat("uuid", new RegExp(canonicalUuidPattern, "u"));
|
|
54613
55084
|
validatorCache = /* @__PURE__ */ new WeakMap();
|
|
54614
55085
|
skillUnsignedIntentResponseSchema = {
|
|
54615
55086
|
type: "object",
|
|
@@ -55576,435 +56047,6 @@ var init_accounts = __esm({
|
|
|
55576
56047
|
}
|
|
55577
56048
|
});
|
|
55578
56049
|
|
|
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
56050
|
// src/skill-local-flow.ts
|
|
56009
56051
|
import { createHash } from "node:crypto";
|
|
56010
56052
|
import { lstat as lstat2, readFile as readFile2, readdir } from "node:fs/promises";
|
|
@@ -58100,32 +58142,89 @@ var init_application = __esm({
|
|
|
58100
58142
|
return result2;
|
|
58101
58143
|
}
|
|
58102
58144
|
async doctorMcp() {
|
|
58103
|
-
const
|
|
58104
|
-
|
|
58105
|
-
|
|
58106
|
-
|
|
58107
|
-
|
|
58108
|
-
|
|
58109
|
-
|
|
58110
|
-
|
|
58111
|
-
|
|
58112
|
-
|
|
58113
|
-
|
|
58145
|
+
const inspect = async (run, guidance) => {
|
|
58146
|
+
try {
|
|
58147
|
+
const value = await run();
|
|
58148
|
+
return [value, { status: "ok" }];
|
|
58149
|
+
} catch (cause) {
|
|
58150
|
+
if (cause instanceof CliStateError) {
|
|
58151
|
+
return [null, {
|
|
58152
|
+
status: cause.code === "CLI_STATE_NOT_FOUND" ? "missing" : "invalid",
|
|
58153
|
+
message: localFailureMessage(cause)
|
|
58154
|
+
}];
|
|
58155
|
+
} else if (cause instanceof CliHttpError) {
|
|
58156
|
+
return [null, { status: cause.status === 401 || cause.status === 403 || cause.code === "CLI_RESPONSE_INVALID" ? "invalid" : "unavailable", message: guidance }];
|
|
58157
|
+
} else if (cause instanceof TypeError || cause instanceof CliAuthorityError) {
|
|
58158
|
+
return [null, { status: "invalid", message: guidance }];
|
|
58159
|
+
} else {
|
|
58160
|
+
throw cause;
|
|
58161
|
+
}
|
|
58162
|
+
}
|
|
58163
|
+
};
|
|
58164
|
+
const [profile, endpointCheck] = await inspect(
|
|
58165
|
+
() => this.authorizedProfile(),
|
|
58166
|
+
"Check the protected FINCHTECH_HOME/profile.json endpoint override."
|
|
58167
|
+
);
|
|
58168
|
+
const [wallet, walletCheck] = await inspect(
|
|
58169
|
+
() => required3(this.state.readWalletAddress, "state.readWalletAddress").call(this.state),
|
|
58170
|
+
"Restore the current wallet from backup."
|
|
58171
|
+
);
|
|
58172
|
+
const [session, sessionCheck] = await inspect(
|
|
58173
|
+
() => required3(this.state.readSession, "state.readSession").call(this.state),
|
|
58174
|
+
"Run finch logout --local, then finch login --help to log in again."
|
|
58175
|
+
);
|
|
58176
|
+
let remoteSession = null;
|
|
58177
|
+
let remoteSessionCheck;
|
|
58178
|
+
if (profile !== null && session !== null && wallet !== null) {
|
|
58179
|
+
[remoteSession, remoteSessionCheck] = await inspect(async () => {
|
|
58180
|
+
if (session.origin !== profile.origin || wallet.walletAddress !== session.walletAddress) {
|
|
58181
|
+
throw new CliAuthorityError();
|
|
58182
|
+
}
|
|
58183
|
+
const remote = asObject(await required3(this.api.getSession, "api.getSession").call(this.api, session.rawBearer), "MCP doctor Session");
|
|
58184
|
+
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();
|
|
58185
|
+
return { sessionExpiresAt: remote.sessionExpiresAt };
|
|
58186
|
+
}, "Verify the endpoint and current wallet, then run finch login --help to renew the CLI login. If the server is unavailable, retry later.");
|
|
58187
|
+
} else {
|
|
58188
|
+
remoteSessionCheck = { status: "skipped", message: "Requires a valid endpoint, local wallet, and CLI Session." };
|
|
58189
|
+
}
|
|
58190
|
+
let mcp = null;
|
|
58191
|
+
let mcpDiscoveryCheck;
|
|
58192
|
+
if (profile !== null) {
|
|
58193
|
+
[mcp, mcpDiscoveryCheck] = await inspect(async () => {
|
|
58194
|
+
const discovery = await required3(this.api.inspectRemoteMcp, "api.inspectRemoteMcp").call(this.api);
|
|
58195
|
+
if (discovery.url !== `${profile.origin}/mcp` || discovery.authorization !== "oauth" || JSON.stringify(discovery.supportedClients) !== JSON.stringify(["codex"])) {
|
|
58196
|
+
throw new TypeError("MCP discovery authority mismatch.");
|
|
58197
|
+
}
|
|
58198
|
+
return discovery;
|
|
58199
|
+
}, "Check endpoint connectivity and MCP OAuth discovery configuration, then retry finch mcp doctor.");
|
|
58200
|
+
} else {
|
|
58201
|
+
mcpDiscoveryCheck = { status: "skipped", message: "Requires a valid endpoint." };
|
|
58202
|
+
}
|
|
58203
|
+
const checks = {
|
|
58204
|
+
endpoint: endpointCheck,
|
|
58205
|
+
wallet: walletCheck,
|
|
58206
|
+
session: sessionCheck,
|
|
58207
|
+
remoteSession: remoteSessionCheck,
|
|
58208
|
+
mcpDiscovery: mcpDiscoveryCheck
|
|
58209
|
+
};
|
|
58114
58210
|
return Object.freeze({
|
|
58115
|
-
ready:
|
|
58211
|
+
ready: Object.values(checks).every((check2) => check2.status === "ok"),
|
|
58116
58212
|
readinessScope: "cli_session_and_mcp_discovery",
|
|
58117
|
-
|
|
58213
|
+
checks,
|
|
58214
|
+
harnessOAuth: {
|
|
58118
58215
|
status: "not_checked",
|
|
58119
58216
|
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
|
-
|
|
58217
|
+
},
|
|
58218
|
+
...profile === null ? {} : { origin: profile.origin },
|
|
58219
|
+
...wallet === null ? {} : { walletAddress: wallet.walletAddress },
|
|
58220
|
+
...session === null ? {} : {
|
|
58221
|
+
environment: session.environment,
|
|
58222
|
+
loginChainId: session.loginChainId,
|
|
58223
|
+
accountId: session.accountId,
|
|
58224
|
+
sessionId: session.sessionId
|
|
58225
|
+
},
|
|
58226
|
+
...remoteSession === null ? {} : { sessionExpiresAt: remoteSession.sessionExpiresAt },
|
|
58227
|
+
...mcp === null ? {} : { mcp }
|
|
58129
58228
|
});
|
|
58130
58229
|
}
|
|
58131
58230
|
async assertNoBlockingRecoveries() {
|
|
@@ -58185,7 +58284,16 @@ var init_application = __esm({
|
|
|
58185
58284
|
throw new TypeError("wallet use requires exactly one registered address or --file path.");
|
|
58186
58285
|
}
|
|
58187
58286
|
await this.assertNoBlockingRecoveries();
|
|
58188
|
-
|
|
58287
|
+
let current;
|
|
58288
|
+
try {
|
|
58289
|
+
current = await required3(this.state.readWallet, "state.readWallet").call(this.state);
|
|
58290
|
+
} catch (cause) {
|
|
58291
|
+
if (!(cause instanceof CliStateError) || cause.code !== "CLI_STATE_NOT_FOUND" || input.file === void 0) throw cause;
|
|
58292
|
+
const wallets = await required3(this.state.listWalletAddresses, "state.listWalletAddresses").call(this.state);
|
|
58293
|
+
if (wallets.length !== 0) throw new CliStateError("CLI_STATE_CORRUPT", { cause, diagnostic: "wallet" });
|
|
58294
|
+
await required3(this.state.assertWalletBootstrap, "state.assertWalletBootstrap").call(this.state);
|
|
58295
|
+
return required3(this.state.importWallet, "state.importWallet").call(this.state, input.file);
|
|
58296
|
+
}
|
|
58189
58297
|
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
58298
|
if (!/^0x[0-9a-f]{40}$/u.test(target.walletAddress)) throw new TypeError("Wallet address is invalid.");
|
|
58191
58299
|
if (input.file === void 0) {
|
|
@@ -58937,7 +59045,7 @@ var init_package = __esm({
|
|
|
58937
59045
|
"package.json"() {
|
|
58938
59046
|
package_default = {
|
|
58939
59047
|
name: "@finchtech/cli",
|
|
58940
|
-
version: "0.3.
|
|
59048
|
+
version: "0.3.4",
|
|
58941
59049
|
description: "Finch Agent CLI for wallet-owned marketplace and MCP workflows.",
|
|
58942
59050
|
type: "module",
|
|
58943
59051
|
bin: {
|
|
@@ -59066,7 +59174,11 @@ async function routeCommand(arguments_, application) {
|
|
|
59066
59174
|
return Object.freeze({
|
|
59067
59175
|
command: "help",
|
|
59068
59176
|
topic: commandTopic,
|
|
59069
|
-
usage: FINCH_COMMAND_USAGE[commandTopic]
|
|
59177
|
+
usage: FINCH_COMMAND_USAGE[commandTopic],
|
|
59178
|
+
...commandTopic === "login" ? {
|
|
59179
|
+
authenticationChainIds: PRODUCTION_AUTH_CHAIN_IDS,
|
|
59180
|
+
guidance: "Choose an authentication chain, for example finch login --chain-id 56. The server validates support; market transaction chains are independent."
|
|
59181
|
+
} : {}
|
|
59070
59182
|
});
|
|
59071
59183
|
}
|
|
59072
59184
|
const commands = FINCH_COMMANDS.filter((command) => command.startsWith(`${topic} `));
|
|
@@ -59422,6 +59534,7 @@ var init_command_router = __esm({
|
|
|
59422
59534
|
"src/command-router.ts"() {
|
|
59423
59535
|
"use strict";
|
|
59424
59536
|
init_package();
|
|
59537
|
+
init_v1();
|
|
59425
59538
|
FINCH_COMMANDS = Object.freeze([
|
|
59426
59539
|
"wallet create",
|
|
59427
59540
|
"wallet show",
|
|
@@ -59899,7 +60012,14 @@ var init_wallet_store = __esm({
|
|
|
59899
60012
|
await this.migrateLegacy();
|
|
59900
60013
|
const current = record4(await this.files.readJson("wallet/current.json"));
|
|
59901
60014
|
if (current.version !== 1) throw new CliStateError("CLI_STATE_CORRUPT");
|
|
59902
|
-
|
|
60015
|
+
try {
|
|
60016
|
+
return await this.readRegistered(walletAddress(current.walletAddress));
|
|
60017
|
+
} catch (cause) {
|
|
60018
|
+
if (cause instanceof CliStateError && cause.code === "CLI_STATE_NOT_FOUND") {
|
|
60019
|
+
throw new CliStateError("CLI_STATE_CORRUPT", { cause });
|
|
60020
|
+
}
|
|
60021
|
+
throw cause;
|
|
60022
|
+
}
|
|
59903
60023
|
}
|
|
59904
60024
|
async readCurrentAddress() {
|
|
59905
60025
|
const current = await this.readCurrent();
|
|
@@ -59911,6 +60031,8 @@ var init_wallet_store = __esm({
|
|
|
59911
60031
|
|
|
59912
60032
|
// src/local-state.ts
|
|
59913
60033
|
import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
|
|
60034
|
+
import { lstat as lstat3 } from "node:fs/promises";
|
|
60035
|
+
import { join as join3 } from "node:path";
|
|
59914
60036
|
function object5(value, name) {
|
|
59915
60037
|
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
59916
60038
|
throw new CliStateError("CLI_STATE_CORRUPT", { cause: new TypeError(`${name} must be an object.`) });
|
|
@@ -60183,10 +60305,27 @@ var init_local_state = __esm({
|
|
|
60183
60305
|
await this.wallets.register(input);
|
|
60184
60306
|
}
|
|
60185
60307
|
async readWallet() {
|
|
60186
|
-
|
|
60308
|
+
try {
|
|
60309
|
+
return await this.wallets.readCurrent();
|
|
60310
|
+
} catch (cause) {
|
|
60311
|
+
if (cause instanceof CliStateError) throw new CliStateError(cause.code, { cause, diagnostic: "wallet" });
|
|
60312
|
+
throw cause;
|
|
60313
|
+
}
|
|
60187
60314
|
}
|
|
60188
60315
|
async readWalletAddress() {
|
|
60189
|
-
|
|
60316
|
+
const wallet = await this.readWallet();
|
|
60317
|
+
return Object.freeze({ walletAddress: wallet.walletAddress });
|
|
60318
|
+
}
|
|
60319
|
+
async assertWalletBootstrap() {
|
|
60320
|
+
for (const path2 of ["session", "grants"]) {
|
|
60321
|
+
try {
|
|
60322
|
+
await lstat3(join3(this.files.root, path2));
|
|
60323
|
+
} catch (cause) {
|
|
60324
|
+
if (cause.code === "ENOENT") continue;
|
|
60325
|
+
throw cause;
|
|
60326
|
+
}
|
|
60327
|
+
throw new CliStateError("CLI_STATE_CONFLICT", { diagnostic: "wallet_bootstrap" });
|
|
60328
|
+
}
|
|
60190
60329
|
}
|
|
60191
60330
|
async importWallet(path2) {
|
|
60192
60331
|
return this.wallets.importFromFile(path2);
|
|
@@ -61985,7 +62124,7 @@ async function resolveInstalledProfile(readProfile) {
|
|
|
61985
62124
|
}
|
|
61986
62125
|
function commandNeedsHttp(arguments_) {
|
|
61987
62126
|
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");
|
|
62127
|
+
return (arguments_[0] !== "wallet" || arguments_[1] === "use") && !(arguments_.length === 2 && arguments_[0] === "logout" && arguments_[1] === "--local");
|
|
61989
62128
|
}
|
|
61990
62129
|
async function runInstalledCli(arguments_, environment3 = process.env, authorities = {}) {
|
|
61991
62130
|
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 +62132,14 @@ async function runInstalledCli(arguments_, environment3 = process.env, authoriti
|
|
|
61993
62132
|
const home = resolveFinchtechHome(environment3);
|
|
61994
62133
|
const state = new LocalCliState(home);
|
|
61995
62134
|
const needsHttp = commandNeedsHttp(arguments_);
|
|
61996
|
-
|
|
62135
|
+
let profile;
|
|
62136
|
+
if (needsHttp) {
|
|
62137
|
+
try {
|
|
62138
|
+
profile = await resolveInstalledProfile(async () => state.readProfile());
|
|
62139
|
+
} catch (cause) {
|
|
62140
|
+
if (!(cause instanceof CliStateError) || arguments_.length !== 2 || arguments_[0] !== "mcp" || arguments_[1] !== "doctor") throw cause;
|
|
62141
|
+
}
|
|
62142
|
+
}
|
|
61997
62143
|
const api = profile === void 0 ? {} : new AgentCliHttpClient(profile.origin);
|
|
61998
62144
|
const signer = new InstalledWalletSigner();
|
|
61999
62145
|
const skillIntentSender = profile === void 0 || arguments_[0] !== "skill" ? void 0 : new InstalledSkillIntentSender();
|
|
@@ -62043,37 +62189,6 @@ function remoteExitCode(status) {
|
|
|
62043
62189
|
}
|
|
62044
62190
|
return AGENT_CLI_OUTPUT_CONTRACT.exitCodes.remoteOrProviderUnavailable;
|
|
62045
62191
|
}
|
|
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
62192
|
function failure(error51) {
|
|
62078
62193
|
if (error51 instanceof CliSkillSourceError) {
|
|
62079
62194
|
return {
|
|
@@ -62123,6 +62238,9 @@ function emitCliOutcome(outcome, sinks) {
|
|
|
62123
62238
|
if (!(outcome instanceof Error)) {
|
|
62124
62239
|
sinks.stdout(`${JSON.stringify(outcome)}
|
|
62125
62240
|
`);
|
|
62241
|
+
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) {
|
|
62242
|
+
return AGENT_CLI_OUTPUT_CONTRACT.exitCodes.usageOrLocalSchema;
|
|
62243
|
+
}
|
|
62126
62244
|
return AGENT_CLI_OUTPUT_CONTRACT.exitCodes.success;
|
|
62127
62245
|
}
|
|
62128
62246
|
if (outcome instanceof CliSkillPublicationError) {
|