@ametyst/cli 0.3.5 → 0.3.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +564 -173
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -106446,7 +106446,12 @@ function loadConfig() {
|
|
|
106446
106446
|
// pbi-wallet-3 — projected, or the cached stance would survive exactly one
|
|
106447
106447
|
// process and then vanish (the whitelist warning above).
|
|
106448
106448
|
...typeof p.requireWalletPassphrase === "boolean" ? { requireWalletPassphrase: p.requireWalletPassphrase } : {},
|
|
106449
|
-
...typeof p.requireWalletPassphraseObservedAt === "string" ? { requireWalletPassphraseObservedAt: p.requireWalletPassphraseObservedAt } : {}
|
|
106449
|
+
...typeof p.requireWalletPassphraseObservedAt === "string" ? { requireWalletPassphraseObservedAt: p.requireWalletPassphraseObservedAt } : {},
|
|
106450
|
+
// The workspace binding — projected, or a switch could never be detected:
|
|
106451
|
+
// the key would survive exactly one process and then vanish (the whitelist
|
|
106452
|
+
// warning above), making every login look like a first binding.
|
|
106453
|
+
...typeof p.workspaceId === "string" ? { workspaceId: p.workspaceId } : {},
|
|
106454
|
+
...typeof p.workspaceIdObservedAt === "string" ? { workspaceIdObservedAt: p.workspaceIdObservedAt } : {}
|
|
106450
106455
|
};
|
|
106451
106456
|
} catch {
|
|
106452
106457
|
return null;
|
|
@@ -111782,7 +111787,8 @@ __export(core_exports, {
|
|
|
111782
111787
|
reencryptPlaintextVault: () => reencryptPlaintextVault,
|
|
111783
111788
|
safeVaultErrorMessage: () => safeVaultErrorMessage,
|
|
111784
111789
|
saveWallet: () => saveWallet,
|
|
111785
|
-
wipeVault: () => wipeVault
|
|
111790
|
+
wipeVault: () => wipeVault,
|
|
111791
|
+
writeSecretFileAtomic: () => writeSecretFileAtomic
|
|
111786
111792
|
});
|
|
111787
111793
|
import {
|
|
111788
111794
|
chmodSync as chmodSync2,
|
|
@@ -111873,8 +111879,11 @@ function writeAllSync(fd, buf) {
|
|
|
111873
111879
|
}
|
|
111874
111880
|
function writeVaultFileAtomic(contents) {
|
|
111875
111881
|
ensureDirectories();
|
|
111876
|
-
|
|
111877
|
-
|
|
111882
|
+
writeSecretFileAtomic(VAULT_PATH, contents);
|
|
111883
|
+
}
|
|
111884
|
+
function writeSecretFileAtomic(path2, contents) {
|
|
111885
|
+
const dir = dirname5(path2);
|
|
111886
|
+
const tmp = join5(dir, `.${basename3(path2)}.tmp-${process.pid}-${randomBytes3(8).toString("hex")}`);
|
|
111878
111887
|
let fd;
|
|
111879
111888
|
try {
|
|
111880
111889
|
fd = openSync(tmp, "wx", 384);
|
|
@@ -111883,7 +111892,7 @@ function writeVaultFileAtomic(contents) {
|
|
|
111883
111892
|
fsyncSync(fd);
|
|
111884
111893
|
closeSync(fd);
|
|
111885
111894
|
fd = void 0;
|
|
111886
|
-
renameSync(tmp,
|
|
111895
|
+
renameSync(tmp, path2);
|
|
111887
111896
|
fsyncDir(dir);
|
|
111888
111897
|
} catch (e) {
|
|
111889
111898
|
if (fd !== void 0) {
|
|
@@ -112013,6 +112022,212 @@ var init_core = __esm({
|
|
|
112013
112022
|
}
|
|
112014
112023
|
});
|
|
112015
112024
|
|
|
112025
|
+
// src/wallet/grant-keystore.ts
|
|
112026
|
+
var grant_keystore_exports = {};
|
|
112027
|
+
__export(grant_keystore_exports, {
|
|
112028
|
+
GRANT_ENTRY_FORMAT: () => GRANT_ENTRY_FORMAT,
|
|
112029
|
+
buildGrantVault: () => buildGrantVault,
|
|
112030
|
+
createGrantKeyIssuer: () => createGrantKeyIssuer,
|
|
112031
|
+
deleteGrantEntry: () => deleteGrantEntry,
|
|
112032
|
+
ensureGrantsDir: () => ensureGrantsDir,
|
|
112033
|
+
generateGrantKey: () => generateGrantKey,
|
|
112034
|
+
grantEntryPath: () => grantEntryPath,
|
|
112035
|
+
grantsDir: () => grantsDir,
|
|
112036
|
+
listGrantAddresses: () => listGrantAddresses,
|
|
112037
|
+
listGrantEntries: () => listGrantEntries,
|
|
112038
|
+
missingGrantKeyMessage: () => missingGrantKeyMessage,
|
|
112039
|
+
normalizeGrantAddress: () => normalizeGrantAddress,
|
|
112040
|
+
parseGrantEntry: () => parseGrantEntry,
|
|
112041
|
+
persistGrantKey: () => persistGrantKey,
|
|
112042
|
+
readGrantEntry: () => readGrantEntry,
|
|
112043
|
+
recordGrantRowId: () => recordGrantRowId,
|
|
112044
|
+
resolveGrantSigningKey: () => resolveGrantSigningKey
|
|
112045
|
+
});
|
|
112046
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, readFileSync as readFileSync7, readdirSync as readdirSync4, unlinkSync as unlinkSync5 } from "fs";
|
|
112047
|
+
import { join as join6 } from "path";
|
|
112048
|
+
function grantsDir() {
|
|
112049
|
+
return join6(WALLETS_DIR, "grants");
|
|
112050
|
+
}
|
|
112051
|
+
function resolveDir(dir) {
|
|
112052
|
+
return dir ?? grantsDir();
|
|
112053
|
+
}
|
|
112054
|
+
function ensureGrantsDir(dir) {
|
|
112055
|
+
const target = resolveDir(dir);
|
|
112056
|
+
if (target === grantsDir()) ensureDirectories();
|
|
112057
|
+
if (!existsSync7(target)) mkdirSync6(target, { mode: 448, recursive: true });
|
|
112058
|
+
return target;
|
|
112059
|
+
}
|
|
112060
|
+
function normalizeGrantAddress(address) {
|
|
112061
|
+
if (typeof address !== "string" || !ADDRESS_RE2.test(address)) {
|
|
112062
|
+
throw new Error(`Not a 0x-hex EOA address: ${JSON.stringify(address)}`);
|
|
112063
|
+
}
|
|
112064
|
+
return address.toLowerCase();
|
|
112065
|
+
}
|
|
112066
|
+
function grantEntryPath(address, dir) {
|
|
112067
|
+
return join6(resolveDir(dir), `${normalizeGrantAddress(address)}.json`);
|
|
112068
|
+
}
|
|
112069
|
+
function generateGrantKey() {
|
|
112070
|
+
const derived = derivePlaintextWallet(generatePrivateKey());
|
|
112071
|
+
return { address: derived.address.toLowerCase(), privateKey: derived.privateKey };
|
|
112072
|
+
}
|
|
112073
|
+
function buildGrantVault(address, privateKey, options) {
|
|
112074
|
+
if (options.encrypted) {
|
|
112075
|
+
if (!options.passphrase) {
|
|
112076
|
+
throw new Error(
|
|
112077
|
+
"Refusing to store a session key unencrypted: this machine's wallet is passphrase-protected but no passphrase is in memory. Call start_session({ passphrase }) first."
|
|
112078
|
+
);
|
|
112079
|
+
}
|
|
112080
|
+
return JSON.parse(importWallet(privateKey, options.passphrase));
|
|
112081
|
+
}
|
|
112082
|
+
return JSON.parse(serializePlaintextVault(address, privateKey));
|
|
112083
|
+
}
|
|
112084
|
+
function persistGrantKey(params, dir) {
|
|
112085
|
+
const address = normalizeGrantAddress(params.address);
|
|
112086
|
+
const target = ensureGrantsDir(dir);
|
|
112087
|
+
const entry = {
|
|
112088
|
+
format: GRANT_ENTRY_FORMAT,
|
|
112089
|
+
address,
|
|
112090
|
+
createdAt: params.createdAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
112091
|
+
workspaceId: params.workspaceId ?? null,
|
|
112092
|
+
policyId: params.policyId ?? null,
|
|
112093
|
+
backendWalletRowId: params.backendWalletRowId ?? null,
|
|
112094
|
+
vault: buildGrantVault(address, params.privateKey, {
|
|
112095
|
+
encrypted: params.encrypted,
|
|
112096
|
+
passphrase: params.passphrase
|
|
112097
|
+
})
|
|
112098
|
+
};
|
|
112099
|
+
writeSecretFileAtomic(join6(target, `${address}.json`), JSON.stringify(entry, null, 2));
|
|
112100
|
+
return entry;
|
|
112101
|
+
}
|
|
112102
|
+
function parseGrantEntry(json) {
|
|
112103
|
+
let parsed;
|
|
112104
|
+
try {
|
|
112105
|
+
parsed = JSON.parse(json);
|
|
112106
|
+
} catch {
|
|
112107
|
+
return null;
|
|
112108
|
+
}
|
|
112109
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
112110
|
+
const p = parsed;
|
|
112111
|
+
if (p.format !== GRANT_ENTRY_FORMAT) return null;
|
|
112112
|
+
if (typeof p.address !== "string" || !ADDRESS_RE2.test(p.address)) return null;
|
|
112113
|
+
if (p.vault === null || typeof p.vault !== "object" || Array.isArray(p.vault)) return null;
|
|
112114
|
+
return {
|
|
112115
|
+
format: GRANT_ENTRY_FORMAT,
|
|
112116
|
+
address: p.address.toLowerCase(),
|
|
112117
|
+
createdAt: typeof p.createdAt === "string" ? p.createdAt : "",
|
|
112118
|
+
workspaceId: typeof p.workspaceId === "string" ? p.workspaceId : null,
|
|
112119
|
+
policyId: typeof p.policyId === "number" ? p.policyId : null,
|
|
112120
|
+
backendWalletRowId: typeof p.backendWalletRowId === "string" ? p.backendWalletRowId : null,
|
|
112121
|
+
vault: p.vault
|
|
112122
|
+
};
|
|
112123
|
+
}
|
|
112124
|
+
function readGrantEntry(address, dir) {
|
|
112125
|
+
let path2;
|
|
112126
|
+
try {
|
|
112127
|
+
path2 = grantEntryPath(address, dir);
|
|
112128
|
+
} catch {
|
|
112129
|
+
return null;
|
|
112130
|
+
}
|
|
112131
|
+
try {
|
|
112132
|
+
return parseGrantEntry(readFileSync7(path2, "utf-8"));
|
|
112133
|
+
} catch {
|
|
112134
|
+
return null;
|
|
112135
|
+
}
|
|
112136
|
+
}
|
|
112137
|
+
function listGrantEntries(dir) {
|
|
112138
|
+
const target = resolveDir(dir);
|
|
112139
|
+
let names;
|
|
112140
|
+
try {
|
|
112141
|
+
names = readdirSync4(target);
|
|
112142
|
+
} catch {
|
|
112143
|
+
return [];
|
|
112144
|
+
}
|
|
112145
|
+
const entries = [];
|
|
112146
|
+
for (const name of names) {
|
|
112147
|
+
if (!name.endsWith(".json")) continue;
|
|
112148
|
+
let raw;
|
|
112149
|
+
try {
|
|
112150
|
+
raw = readFileSync7(join6(target, name), "utf-8");
|
|
112151
|
+
} catch {
|
|
112152
|
+
continue;
|
|
112153
|
+
}
|
|
112154
|
+
const entry = parseGrantEntry(raw);
|
|
112155
|
+
if (entry) entries.push(entry);
|
|
112156
|
+
}
|
|
112157
|
+
return entries;
|
|
112158
|
+
}
|
|
112159
|
+
function listGrantAddresses(options = {}, dir) {
|
|
112160
|
+
const active = options.workspaceId ?? null;
|
|
112161
|
+
return listGrantEntries(dir).filter((e) => active === null || e.workspaceId === null || e.workspaceId === active).map((e) => e.address);
|
|
112162
|
+
}
|
|
112163
|
+
function recordGrantRowId(address, rowId, dir) {
|
|
112164
|
+
const entry = readGrantEntry(address, dir);
|
|
112165
|
+
if (!entry) return false;
|
|
112166
|
+
try {
|
|
112167
|
+
const next = { ...entry, backendWalletRowId: rowId ?? null };
|
|
112168
|
+
writeSecretFileAtomic(grantEntryPath(address, dir), JSON.stringify(next, null, 2));
|
|
112169
|
+
return true;
|
|
112170
|
+
} catch {
|
|
112171
|
+
return false;
|
|
112172
|
+
}
|
|
112173
|
+
}
|
|
112174
|
+
function deleteGrantEntry(address, dir) {
|
|
112175
|
+
try {
|
|
112176
|
+
unlinkSync5(grantEntryPath(address, dir));
|
|
112177
|
+
} catch {
|
|
112178
|
+
}
|
|
112179
|
+
}
|
|
112180
|
+
function missingGrantKeyMessage(address) {
|
|
112181
|
+
return `no local key for grant address ${address} \u2014 this machine cannot sign for that grant. Run requestAccess again to mint a fresh session key and have your admin approve it.`;
|
|
112182
|
+
}
|
|
112183
|
+
function resolveGrantSigningKey(params) {
|
|
112184
|
+
const address = normalizeGrantAddress(params.address);
|
|
112185
|
+
const entry = readGrantEntry(address, params.dir);
|
|
112186
|
+
if (entry) {
|
|
112187
|
+
return readVaultPrivateKey(JSON.stringify(entry.vault), params.passphrase);
|
|
112188
|
+
}
|
|
112189
|
+
const vaultAddress = params.vaultAddress;
|
|
112190
|
+
if (params.vaultJson && typeof vaultAddress === "string" && vaultAddress.toLowerCase() === address) {
|
|
112191
|
+
return readVaultPrivateKey(params.vaultJson, params.passphrase);
|
|
112192
|
+
}
|
|
112193
|
+
throw new Error(missingGrantKeyMessage(address));
|
|
112194
|
+
}
|
|
112195
|
+
function createGrantKeyIssuer(options) {
|
|
112196
|
+
return {
|
|
112197
|
+
issue({ policyId }) {
|
|
112198
|
+
const { address, privateKey } = generateGrantKey();
|
|
112199
|
+
persistGrantKey(
|
|
112200
|
+
{
|
|
112201
|
+
address,
|
|
112202
|
+
privateKey,
|
|
112203
|
+
encrypted: options.encrypted,
|
|
112204
|
+
passphrase: options.passphrase,
|
|
112205
|
+
workspaceId: options.workspaceId ?? null,
|
|
112206
|
+
policyId
|
|
112207
|
+
},
|
|
112208
|
+
options.dir
|
|
112209
|
+
);
|
|
112210
|
+
return address;
|
|
112211
|
+
},
|
|
112212
|
+
recordRow(address, rowId) {
|
|
112213
|
+
recordGrantRowId(address, rowId, options.dir);
|
|
112214
|
+
}
|
|
112215
|
+
};
|
|
112216
|
+
}
|
|
112217
|
+
var GRANT_ENTRY_FORMAT, ADDRESS_RE2;
|
|
112218
|
+
var init_grant_keystore = __esm({
|
|
112219
|
+
"src/wallet/grant-keystore.ts"() {
|
|
112220
|
+
"use strict";
|
|
112221
|
+
init_esm_shims();
|
|
112222
|
+
init_accounts();
|
|
112223
|
+
init_paths();
|
|
112224
|
+
init_core();
|
|
112225
|
+
init_plaintext_vault();
|
|
112226
|
+
GRANT_ENTRY_FORMAT = "grant-key-v1";
|
|
112227
|
+
ADDRESS_RE2 = /^0x[0-9a-fA-F]{40}$/;
|
|
112228
|
+
}
|
|
112229
|
+
});
|
|
112230
|
+
|
|
112016
112231
|
// src/commands/init-claim.ts
|
|
112017
112232
|
var init_claim_exports = {};
|
|
112018
112233
|
__export(init_claim_exports, {
|
|
@@ -112026,7 +112241,7 @@ __export(init_claim_exports, {
|
|
|
112026
112241
|
vaultBackupPath: () => vaultBackupPath,
|
|
112027
112242
|
vaultBackupSuffix: () => vaultBackupSuffix
|
|
112028
112243
|
});
|
|
112029
|
-
import { chmodSync as chmodSync3, constants as fsConstants, copyFileSync as copyFileSync3, existsSync as
|
|
112244
|
+
import { chmodSync as chmodSync3, constants as fsConstants, copyFileSync as copyFileSync3, existsSync as existsSync8 } from "fs";
|
|
112030
112245
|
import { createDecipheriv as createDecipheriv2 } from "crypto";
|
|
112031
112246
|
function claimErrorMessage(failure) {
|
|
112032
112247
|
switch (failure.kind) {
|
|
@@ -112169,7 +112384,7 @@ function vaultBackupPath(vaultPath, at) {
|
|
|
112169
112384
|
return `${vaultPath}.bak-${vaultBackupSuffix(at)}`;
|
|
112170
112385
|
}
|
|
112171
112386
|
function backupVaultFile(vaultPath, at = /* @__PURE__ */ new Date()) {
|
|
112172
|
-
if (!
|
|
112387
|
+
if (!existsSync8(vaultPath)) return null;
|
|
112173
112388
|
const target = vaultBackupPath(vaultPath, at);
|
|
112174
112389
|
try {
|
|
112175
112390
|
copyFileSync3(vaultPath, target, fsConstants.COPYFILE_EXCL);
|
|
@@ -112197,13 +112412,13 @@ var init_version5 = __esm({
|
|
|
112197
112412
|
"src/version.ts"() {
|
|
112198
112413
|
"use strict";
|
|
112199
112414
|
init_esm_shims();
|
|
112200
|
-
CLI_VERSION = true ? "0.3.
|
|
112415
|
+
CLI_VERSION = true ? "0.3.6" : "0.0.0-dev";
|
|
112201
112416
|
}
|
|
112202
112417
|
});
|
|
112203
112418
|
|
|
112204
112419
|
// src/connections/catalog.ts
|
|
112205
|
-
import { existsSync as
|
|
112206
|
-
import { join as
|
|
112420
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync7 } from "fs";
|
|
112421
|
+
import { join as join7 } from "path";
|
|
112207
112422
|
function declaredVerbsHeader() {
|
|
112208
112423
|
return SUPPORTED_HTTP_METHODS.join(",");
|
|
112209
112424
|
}
|
|
@@ -112459,8 +112674,8 @@ function parseConnectorsResponseDetailed(body, onWarn = warnToStderr) {
|
|
|
112459
112674
|
function readCachedCatalogStatus() {
|
|
112460
112675
|
let raw;
|
|
112461
112676
|
try {
|
|
112462
|
-
if (!
|
|
112463
|
-
raw = JSON.parse(
|
|
112677
|
+
if (!existsSync9(CONNECTOR_CACHE_PATH)) return { state: "absent" };
|
|
112678
|
+
raw = JSON.parse(readFileSync8(CONNECTOR_CACHE_PATH, "utf-8"));
|
|
112464
112679
|
} catch (e) {
|
|
112465
112680
|
return { state: "unreadable", reason: e instanceof Error ? e.message : String(e) };
|
|
112466
112681
|
}
|
|
@@ -112496,8 +112711,8 @@ function readMirrorRowsFromDisk() {
|
|
|
112496
112711
|
}
|
|
112497
112712
|
function readMirrorContainer() {
|
|
112498
112713
|
try {
|
|
112499
|
-
if (!
|
|
112500
|
-
const raw = JSON.parse(
|
|
112714
|
+
if (!existsSync9(CONNECTOR_CACHE_PATH)) return { state: "absent" };
|
|
112715
|
+
const raw = JSON.parse(readFileSync8(CONNECTOR_CACHE_PATH, "utf-8"));
|
|
112501
112716
|
if (!isRecord(raw)) {
|
|
112502
112717
|
return { state: "unreadable", reason: "the mirror's top level is not an object" };
|
|
112503
112718
|
}
|
|
@@ -112511,8 +112726,8 @@ function readMirrorContainer() {
|
|
|
112511
112726
|
}
|
|
112512
112727
|
function readMirrorFetchedAt() {
|
|
112513
112728
|
try {
|
|
112514
|
-
if (!
|
|
112515
|
-
const raw = JSON.parse(
|
|
112729
|
+
if (!existsSync9(CONNECTOR_CACHE_PATH)) return "";
|
|
112730
|
+
const raw = JSON.parse(readFileSync8(CONNECTOR_CACHE_PATH, "utf-8"));
|
|
112516
112731
|
if (!isRecord(raw)) return "";
|
|
112517
112732
|
return typeof raw.fetchedAt === "string" ? raw.fetchedAt : "";
|
|
112518
112733
|
} catch {
|
|
@@ -112763,7 +112978,7 @@ var init_catalog = __esm({
|
|
|
112763
112978
|
SOURCE_OPERATORS = ["provider", "third_party", "ametyst"];
|
|
112764
112979
|
SOURCE_PREFERENCE_RULES = ["official-mcp"];
|
|
112765
112980
|
CONNECTOR_FEED_PATH = "/api/v1/virtual-wallets/capabilities/connectors";
|
|
112766
|
-
CONNECTOR_CACHE_PATH =
|
|
112981
|
+
CONNECTOR_CACHE_PATH = join7(AMETYST_DIR, "connectors.json");
|
|
112767
112982
|
CatalogUnavailableError = class extends Error {
|
|
112768
112983
|
constructor(reason, message) {
|
|
112769
112984
|
super(message);
|
|
@@ -113501,15 +113716,15 @@ var init_call2 = __esm({
|
|
|
113501
113716
|
});
|
|
113502
113717
|
|
|
113503
113718
|
// src/connections/engine-store.ts
|
|
113504
|
-
import { chmodSync as chmodSync4, closeSync as closeSync2, existsSync as
|
|
113505
|
-
import { dirname as dirname7, join as
|
|
113719
|
+
import { chmodSync as chmodSync4, closeSync as closeSync2, existsSync as existsSync12, mkdirSync as mkdirSync8, openSync as openSync2 } from "fs";
|
|
113720
|
+
import { dirname as dirname7, join as join17 } from "path";
|
|
113506
113721
|
function connectionsDbPath() {
|
|
113507
|
-
return
|
|
113722
|
+
return join17(AMETYST_DIR, CONNECTIONS_DB_FILENAME);
|
|
113508
113723
|
}
|
|
113509
113724
|
function ensureOwnerOnlyFile(path2) {
|
|
113510
113725
|
if (path2 === IN_MEMORY_DB) return;
|
|
113511
|
-
|
|
113512
|
-
if (!
|
|
113726
|
+
mkdirSync8(dirname7(path2), { recursive: true, mode: DIR_MODE });
|
|
113727
|
+
if (!existsSync12(path2)) closeSync2(openSync2(path2, "a", CONNECTIONS_DB_FILE_MODE));
|
|
113513
113728
|
chmodSync4(path2, CONNECTIONS_DB_FILE_MODE);
|
|
113514
113729
|
}
|
|
113515
113730
|
function sqliteWasmDriver(db, raw) {
|
|
@@ -114718,6 +114933,44 @@ function hostNudges(rows) {
|
|
|
114718
114933
|
|
|
114719
114934
|
// src/commands/login.ts
|
|
114720
114935
|
init_resolve();
|
|
114936
|
+
|
|
114937
|
+
// src/config/workspace-binding.ts
|
|
114938
|
+
init_esm_shims();
|
|
114939
|
+
init_config();
|
|
114940
|
+
function workspaceKeyFrom(profile) {
|
|
114941
|
+
const name = profile?.companyName;
|
|
114942
|
+
if (typeof name !== "string") return null;
|
|
114943
|
+
const trimmed = name.trim().toLowerCase();
|
|
114944
|
+
return trimmed === "" ? null : trimmed;
|
|
114945
|
+
}
|
|
114946
|
+
function isWorkspaceSwitch(previous, next) {
|
|
114947
|
+
if (previous === null || next === null) return false;
|
|
114948
|
+
return previous !== next;
|
|
114949
|
+
}
|
|
114950
|
+
function readBoundWorkspace() {
|
|
114951
|
+
const config = loadConfig();
|
|
114952
|
+
const key = config?.workspaceId;
|
|
114953
|
+
return typeof key === "string" && key.trim() !== "" ? key : null;
|
|
114954
|
+
}
|
|
114955
|
+
function recordBoundWorkspace(key, observedAt) {
|
|
114956
|
+
const config = loadConfig();
|
|
114957
|
+
if (!config) return false;
|
|
114958
|
+
const { apiKey: _injected, apiKeySource: _src, ...persisted } = config;
|
|
114959
|
+
saveConfig({ ...persisted, workspaceId: key, workspaceIdObservedAt: observedAt });
|
|
114960
|
+
return true;
|
|
114961
|
+
}
|
|
114962
|
+
function workspaceSwitchNotice(previous, next) {
|
|
114963
|
+
return `Workspace changed: "${previous}" -> "${next}". Dropping the cached session and wallet binding from the previous workspace \u2014 its session keys are kept on disk (they stay signable if you switch back) but are no longer selected here.`;
|
|
114964
|
+
}
|
|
114965
|
+
function applyObservedWorkspace(profile, observedAt = (/* @__PURE__ */ new Date()).toISOString()) {
|
|
114966
|
+
const key = workspaceKeyFrom(profile);
|
|
114967
|
+
const previous = readBoundWorkspace();
|
|
114968
|
+
const switched = isWorkspaceSwitch(previous, key);
|
|
114969
|
+
if (key !== null && key !== previous) recordBoundWorkspace(key, observedAt);
|
|
114970
|
+
return { key, previous, switched };
|
|
114971
|
+
}
|
|
114972
|
+
|
|
114973
|
+
// src/commands/login.ts
|
|
114721
114974
|
function lastObservedStanceLine(config) {
|
|
114722
114975
|
const observed = config?.requireWalletPassphrase;
|
|
114723
114976
|
if (typeof observed !== "boolean") {
|
|
@@ -114779,6 +115032,10 @@ async function loginCommand(options) {
|
|
|
114779
115032
|
if (observedStance !== void 0) {
|
|
114780
115033
|
setRequireWalletPassphrase(observedStance, (/* @__PURE__ */ new Date()).toISOString());
|
|
114781
115034
|
}
|
|
115035
|
+
const workspace = applyObservedWorkspace(profile);
|
|
115036
|
+
if (workspace.switched && workspace.previous && workspace.key) {
|
|
115037
|
+
console.log(workspaceSwitchNotice(workspace.previous, workspace.key));
|
|
115038
|
+
}
|
|
114782
115039
|
const stance = resolveWalletPassphraseStance(observedStance, config.requireWalletPassphrase);
|
|
114783
115040
|
const who = profile ? `Logged in as ${profile.employeeName} @ ${profile.companyName ?? "(no company)"}.` : "Logged in.";
|
|
114784
115041
|
console.log(who);
|
|
@@ -114926,8 +115183,9 @@ async function fetchAvailablePolicies(client, apiKey) {
|
|
|
114926
115183
|
const response = await client.getPoliciesFromVirtualWalletsManager(apiKey);
|
|
114927
115184
|
return normalizePoliciesResponse(response);
|
|
114928
115185
|
}
|
|
114929
|
-
async function
|
|
114930
|
-
|
|
115186
|
+
async function submitRotatedAccessRequest(client, apiKey, policyId, issuer) {
|
|
115187
|
+
const eoaAddress = await issuer.issue({ policyId });
|
|
115188
|
+
const result = await client.createVirtualWallet(
|
|
114931
115189
|
apiKey,
|
|
114932
115190
|
CLI_WALLET_NAME,
|
|
114933
115191
|
CLI_WALLET_URL,
|
|
@@ -114935,6 +115193,11 @@ async function submitAccessRequest(client, apiKey, eoaAddress, policyId) {
|
|
|
114935
115193
|
policyId,
|
|
114936
115194
|
eoaAddress
|
|
114937
115195
|
);
|
|
115196
|
+
try {
|
|
115197
|
+
issuer.recordRow?.(eoaAddress, result?.id ? String(result.id) : void 0);
|
|
115198
|
+
} catch {
|
|
115199
|
+
}
|
|
115200
|
+
return { ...result, eoaAddress };
|
|
114938
115201
|
}
|
|
114939
115202
|
function pickDefaultPolicyIndex(policies) {
|
|
114940
115203
|
const i = policies.findIndex(
|
|
@@ -115133,6 +115396,7 @@ async function freshWalletRecovery(apiKey, options) {
|
|
|
115133
115396
|
}
|
|
115134
115397
|
}
|
|
115135
115398
|
let address;
|
|
115399
|
+
let vaultPassphrase;
|
|
115136
115400
|
if (options.noPassphrase) {
|
|
115137
115401
|
const wallet = createPlaintextWallet();
|
|
115138
115402
|
persistPlaintextVault(wallet.address, wallet.privateKey);
|
|
@@ -115153,6 +115417,7 @@ New wallet created. Address: ${address}`);
|
|
|
115153
115417
|
process.exit(1);
|
|
115154
115418
|
return false;
|
|
115155
115419
|
}
|
|
115420
|
+
vaultPassphrase = passphrase;
|
|
115156
115421
|
const keystoreJson = createWallet(passphrase);
|
|
115157
115422
|
persistVault(keystoreJson);
|
|
115158
115423
|
address = getWalletAddress(keystoreJson);
|
|
@@ -115201,8 +115466,15 @@ Create one in the Ametyst web app (admin panel \u2192 policies), then re-run 'am
|
|
|
115201
115466
|
process.exit(1);
|
|
115202
115467
|
return false;
|
|
115203
115468
|
}
|
|
115469
|
+
let grantAddress;
|
|
115204
115470
|
try {
|
|
115205
|
-
await
|
|
115471
|
+
const { createGrantKeyIssuer: createGrantKeyIssuer2 } = await Promise.resolve().then(() => (init_grant_keystore(), grant_keystore_exports));
|
|
115472
|
+
const submitted = await submitRotatedAccessRequest(client, apiKey, policyId, createGrantKeyIssuer2({
|
|
115473
|
+
encrypted: !options.noPassphrase,
|
|
115474
|
+
passphrase: vaultPassphrase,
|
|
115475
|
+
workspaceId: readBoundWorkspace()
|
|
115476
|
+
}));
|
|
115477
|
+
grantAddress = submitted.eoaAddress;
|
|
115206
115478
|
} catch (e) {
|
|
115207
115479
|
const msg = e instanceof Error ? e.message : String(e);
|
|
115208
115480
|
console.error(`
|
|
@@ -115214,8 +115486,9 @@ Could not submit the authorization request (${msg}).`);
|
|
|
115214
115486
|
const policyName = typeof chosen.name === "string" && chosen.name ? chosen.name : `policy ${policyId}`;
|
|
115215
115487
|
console.log(`
|
|
115216
115488
|
Authorization request sent \u2014 PENDING approval.
|
|
115217
|
-
Wallet:
|
|
115218
|
-
|
|
115489
|
+
Wallet: ${address}
|
|
115490
|
+
Session key: ${grantAddress}
|
|
115491
|
+
Policy: ${policyName} (id ${policyId})
|
|
115219
115492
|
|
|
115220
115493
|
Next step: approve the request in the Ametyst web app admin panel (check your
|
|
115221
115494
|
notifications there). Once it's approved, you're done \u2014 run 'ametyst status' to
|
|
@@ -115437,14 +115710,19 @@ function isNewerGrant(candidate, incumbent) {
|
|
|
115437
115710
|
}
|
|
115438
115711
|
return candidateIdUsable && !incumbentIdUsable;
|
|
115439
115712
|
}
|
|
115440
|
-
function
|
|
115713
|
+
function selectNewestApprovedWalletAmong(wallets, addresses) {
|
|
115441
115714
|
if (!Array.isArray(wallets)) return void 0;
|
|
115442
|
-
const
|
|
115443
|
-
|
|
115715
|
+
const candidates = /* @__PURE__ */ new Set();
|
|
115716
|
+
for (const address of addresses ?? []) {
|
|
115717
|
+
const normalized = nonEmptyLowerCase(address);
|
|
115718
|
+
if (normalized) candidates.add(normalized);
|
|
115719
|
+
}
|
|
115720
|
+
if (candidates.size === 0) return void 0;
|
|
115444
115721
|
let newest;
|
|
115445
115722
|
for (const wallet of wallets) {
|
|
115446
115723
|
if (wallet?.status !== "approved") continue;
|
|
115447
|
-
|
|
115724
|
+
const rowAddress = nonEmptyLowerCase(wallet?.address);
|
|
115725
|
+
if (!rowAddress || !candidates.has(rowAddress)) continue;
|
|
115448
115726
|
if (newest === void 0 || isNewerGrant(wallet, newest)) newest = wallet;
|
|
115449
115727
|
}
|
|
115450
115728
|
return newest;
|
|
@@ -115454,7 +115732,10 @@ function findCurrentApprovedWallet(wallets, eoaAddress, pendingWalletId) {
|
|
|
115454
115732
|
if (pendingWalletId) {
|
|
115455
115733
|
return wallets.find((w) => String(w?.id) === String(pendingWalletId) && w?.status === "approved");
|
|
115456
115734
|
}
|
|
115457
|
-
return
|
|
115735
|
+
return selectNewestApprovedWalletAmong(
|
|
115736
|
+
wallets,
|
|
115737
|
+
Array.isArray(eoaAddress) ? eoaAddress : eoaAddress === void 0 ? [] : [eoaAddress]
|
|
115738
|
+
);
|
|
115458
115739
|
}
|
|
115459
115740
|
|
|
115460
115741
|
// src/mcp-server/start-session.ts
|
|
@@ -115594,7 +115875,10 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
|
|
|
115594
115875
|
let activeWallet;
|
|
115595
115876
|
let grantChanged = false;
|
|
115596
115877
|
try {
|
|
115597
|
-
activeWallet =
|
|
115878
|
+
activeWallet = selectNewestApprovedWalletAmong(wallets, [
|
|
115879
|
+
ctx.eoaAddress,
|
|
115880
|
+
...ctx.candidateAddresses ?? []
|
|
115881
|
+
]);
|
|
115598
115882
|
if (activeWallet) {
|
|
115599
115883
|
const newestId = String(activeWallet.id);
|
|
115600
115884
|
const rowWalletAddress = activeWallet.walletAddress || activeWallet.kernelAccountAddress;
|
|
@@ -115614,6 +115898,9 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
|
|
|
115614
115898
|
policyId = rowPolicyId ?? policyId;
|
|
115615
115899
|
}
|
|
115616
115900
|
virtualWalletId = newestId;
|
|
115901
|
+
if (typeof activeWallet.address === "string" && activeWallet.address.trim() !== "") {
|
|
115902
|
+
credentials.signerAddress = activeWallet.address;
|
|
115903
|
+
}
|
|
115617
115904
|
credentials.walletAddress = walletAddress;
|
|
115618
115905
|
credentials.virtualWalletId = virtualWalletId;
|
|
115619
115906
|
credentials.paymentManagerAddress = paymentManagerAddress;
|
|
@@ -115646,7 +115933,8 @@ async function runStartSession(ctx, timing = new TimingCollector("start_session"
|
|
|
115646
115933
|
if (walletAddress && virtualWalletId && paymentManagerAddress && policyId) {
|
|
115647
115934
|
let privateKey = null;
|
|
115648
115935
|
try {
|
|
115649
|
-
|
|
115936
|
+
const signerAddress = credentials.signerAddress ?? ctx.eoaAddress;
|
|
115937
|
+
privateKey = ctx.resolveSigningKey && signerAddress ? ctx.resolveSigningKey(signerAddress) : readVaultPrivateKey(ctx.walletKeystoreJson, ctx.passphrase);
|
|
115650
115938
|
const { virtualWalletsManagers: virtualWalletsManagers2, financialAccounts: financialAccounts2 } = ctx.sdk;
|
|
115651
115939
|
const walletData = await virtualWalletsManagers2.getVirtualWalletDataByApiKey(
|
|
115652
115940
|
ctx.apiKey,
|
|
@@ -116946,13 +117234,13 @@ init_local_state();
|
|
|
116946
117234
|
// src/mcp-server/materialize-task.ts
|
|
116947
117235
|
init_esm_shims();
|
|
116948
117236
|
init_paths();
|
|
116949
|
-
import { mkdirSync as
|
|
116950
|
-
import { join as
|
|
117237
|
+
import { mkdirSync as mkdirSync7, writeFileSync as writeFileSync8 } from "fs";
|
|
117238
|
+
import { join as join9 } from "path";
|
|
116951
117239
|
|
|
116952
117240
|
// src/loops/state-docs.ts
|
|
116953
117241
|
init_esm_shims();
|
|
116954
|
-
import { existsSync as
|
|
116955
|
-
import { join as
|
|
117242
|
+
import { existsSync as existsSync10, readFileSync as readFileSync9 } from "fs";
|
|
117243
|
+
import { join as join8 } from "path";
|
|
116956
117244
|
|
|
116957
117245
|
// src/loops/shipback.ts
|
|
116958
117246
|
init_esm_shims();
|
|
@@ -117385,12 +117673,12 @@ function docMergeRefusal(boot, fresh, next, cleanAppend) {
|
|
|
117385
117673
|
async function shipBackStateDocs(sdk, apiKey, slug, dir, boot) {
|
|
117386
117674
|
const outcomes = [];
|
|
117387
117675
|
for (const doc of boot) {
|
|
117388
|
-
const path2 =
|
|
117389
|
-
if (!
|
|
117676
|
+
const path2 = join8(dir, doc.filename);
|
|
117677
|
+
if (!existsSync10(path2)) {
|
|
117390
117678
|
outcomes.push({ key: doc.key, outcome: "unchanged" });
|
|
117391
117679
|
continue;
|
|
117392
117680
|
}
|
|
117393
|
-
const materialized =
|
|
117681
|
+
const materialized = readFileSync9(path2, "utf-8");
|
|
117394
117682
|
const reread = await readScoped(sdk, apiKey, slug, doc.key, doc.scope);
|
|
117395
117683
|
const fresh = reread.status === "ok" ? reread.body : reread.status === "absent" && doc.body === "" ? "" : void 0;
|
|
117396
117684
|
if (fresh === void 0) {
|
|
@@ -117460,20 +117748,20 @@ var TASK_DEFINITION_FILES = [
|
|
|
117460
117748
|
];
|
|
117461
117749
|
function materializeTask(task, runId) {
|
|
117462
117750
|
const dir = loopFireDir(task.slug, runId);
|
|
117463
|
-
|
|
117751
|
+
mkdirSync7(dir, { recursive: true, mode: 448 });
|
|
117464
117752
|
const files = {};
|
|
117465
117753
|
const skipped = [];
|
|
117466
117754
|
for (const [label2, filename, field] of TASK_DEFINITION_FILES) {
|
|
117467
117755
|
const body = task[field];
|
|
117468
117756
|
if (typeof body === "string" && body.length > 0) {
|
|
117469
|
-
writeFileSync8(
|
|
117757
|
+
writeFileSync8(join9(dir, filename), body, { mode: 384 });
|
|
117470
117758
|
files[label2] = filename;
|
|
117471
117759
|
} else {
|
|
117472
117760
|
skipped.push(label2);
|
|
117473
117761
|
}
|
|
117474
117762
|
}
|
|
117475
117763
|
writeFileSync8(
|
|
117476
|
-
|
|
117764
|
+
join9(dir, "STATUS.md"),
|
|
117477
117765
|
`# STATUS \u2014 ${task.slug}
|
|
117478
117766
|
|
|
117479
117767
|
task_id: ${task.id}
|
|
@@ -117504,7 +117792,7 @@ async function materializeMemoryDocs(sdk, apiKey, task, dir) {
|
|
|
117504
117792
|
notes.push(`memory doc '${f.key}' not materialized: ${f.reason}`);
|
|
117505
117793
|
}
|
|
117506
117794
|
for (const doc of fetched) {
|
|
117507
|
-
writeFileSync8(
|
|
117795
|
+
writeFileSync8(join9(dir, doc.filename), doc.body, { mode: 384 });
|
|
117508
117796
|
files[`doc:${doc.key}`] = doc.filename;
|
|
117509
117797
|
notes.push(describeSource(doc));
|
|
117510
117798
|
}
|
|
@@ -117518,14 +117806,14 @@ async function materializeMemoryDocs(sdk, apiKey, task, dir) {
|
|
|
117518
117806
|
}
|
|
117519
117807
|
|
|
117520
117808
|
// src/mcp-server/index.ts
|
|
117521
|
-
import { existsSync as
|
|
117809
|
+
import { existsSync as existsSync13 } from "fs";
|
|
117522
117810
|
|
|
117523
117811
|
// src/loops/dashboard.ts
|
|
117524
117812
|
init_esm_shims();
|
|
117525
117813
|
import { createServer as createServer2 } from "http";
|
|
117526
117814
|
import * as realFs from "fs";
|
|
117527
117815
|
import { spawn as realSpawn } from "child_process";
|
|
117528
|
-
import { join as
|
|
117816
|
+
import { join as join10 } from "path";
|
|
117529
117817
|
var DEFAULT_PORT = 4477;
|
|
117530
117818
|
var DASHBOARD_PORT_ENV = "AMETYST_LOOP_DASHBOARD_PORT";
|
|
117531
117819
|
var DASHBOARD_NO_OPEN_ENV = "AMETYST_DASHBOARD_NO_OPEN";
|
|
@@ -117586,14 +117874,14 @@ function startDashboardServer(args) {
|
|
|
117586
117874
|
const data = {};
|
|
117587
117875
|
for (const name of files) {
|
|
117588
117876
|
try {
|
|
117589
|
-
const p =
|
|
117877
|
+
const p = join10(args.loopDir, name);
|
|
117590
117878
|
if (fs.existsSync(p)) data[name] = fs.readFileSync(p, "utf-8");
|
|
117591
117879
|
} catch {
|
|
117592
117880
|
}
|
|
117593
117881
|
}
|
|
117594
117882
|
const state = {};
|
|
117595
117883
|
try {
|
|
117596
|
-
const p =
|
|
117884
|
+
const p = join10(args.loopDir, ".state", "fires.jsonl");
|
|
117597
117885
|
if (fs.existsSync(p)) state["fires.jsonl"] = fs.readFileSync(p, "utf-8");
|
|
117598
117886
|
} catch {
|
|
117599
117887
|
}
|
|
@@ -117992,7 +118280,7 @@ function injectDefaultDashboard(loop2) {
|
|
|
117992
118280
|
|
|
117993
118281
|
// src/loops/memory-verbs.ts
|
|
117994
118282
|
init_esm_shims();
|
|
117995
|
-
import { readFileSync as
|
|
118283
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
117996
118284
|
|
|
117997
118285
|
// src/loops/sdk.ts
|
|
117998
118286
|
init_esm_shims();
|
|
@@ -118252,7 +118540,7 @@ async function taskMemoryAppendVerb(rawSlug, opts) {
|
|
|
118252
118540
|
const archived = opts.archived === true;
|
|
118253
118541
|
const note = opts.note?.trim() || void 0;
|
|
118254
118542
|
const scope = parseScopeFlag(opts.scope, false);
|
|
118255
|
-
const content = opts.file !== void 0 ?
|
|
118543
|
+
const content = opts.file !== void 0 ? readFileSync10(opts.file, "utf-8") : opts.content ?? "";
|
|
118256
118544
|
if (!content) {
|
|
118257
118545
|
throw new Error("nothing to store: pass --content <text> or --file <path>");
|
|
118258
118546
|
}
|
|
@@ -118389,14 +118677,14 @@ ${full.markdownBody ?? ""}`;
|
|
|
118389
118677
|
|
|
118390
118678
|
// src/mcp-server/read-file-body.ts
|
|
118391
118679
|
init_esm_shims();
|
|
118392
|
-
import { readFileSync as
|
|
118680
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
118393
118681
|
import { resolve as resolvePath } from "path";
|
|
118394
118682
|
function readMarkdownFile(filePath) {
|
|
118395
118683
|
const raw = typeof filePath === "string" ? filePath.trim() : "";
|
|
118396
118684
|
if (!raw) throw new Error("filePath is empty.");
|
|
118397
118685
|
const abs = resolvePath(raw);
|
|
118398
118686
|
try {
|
|
118399
|
-
return
|
|
118687
|
+
return readFileSync11(abs, "utf8");
|
|
118400
118688
|
} catch (err) {
|
|
118401
118689
|
const reason = err instanceof Error ? err.message : String(err);
|
|
118402
118690
|
throw new Error(`Could not read file at "${abs}": ${reason}`);
|
|
@@ -118725,7 +119013,7 @@ function enforceResponseCap(payload) {
|
|
|
118725
119013
|
|
|
118726
119014
|
// src/mcp-server/delegate-tools.ts
|
|
118727
119015
|
init_esm_shims();
|
|
118728
|
-
import { existsSync as
|
|
119016
|
+
import { existsSync as existsSync11, statSync as statSync2 } from "fs";
|
|
118729
119017
|
import { z as z3 } from "zod";
|
|
118730
119018
|
|
|
118731
119019
|
// src/delegate/jobs.ts
|
|
@@ -118827,7 +119115,7 @@ import * as nodeFs from "fs";
|
|
|
118827
119115
|
import { execFileSync } from "child_process";
|
|
118828
119116
|
import { createHash } from "crypto";
|
|
118829
119117
|
import { homedir as homedir5 } from "os";
|
|
118830
|
-
import { join as
|
|
119118
|
+
import { join as join11 } from "path";
|
|
118831
119119
|
var OPENCODE_VERSION = "1.18.13";
|
|
118832
119120
|
var OPENCODE_CHECKSUMS = {
|
|
118833
119121
|
"darwin-arm64": "6a85ae6de1aeb8e39ae4d977337b03f49168c2a827ee37b6f82c39471d711c63",
|
|
@@ -118849,10 +119137,10 @@ function resolveOpencodePlatform(platform, arch) {
|
|
|
118849
119137
|
return void 0;
|
|
118850
119138
|
}
|
|
118851
119139
|
function opencodeBinDir(home = homedir5()) {
|
|
118852
|
-
return
|
|
119140
|
+
return join11(home, `.ametyst${ENV_SUFFIX}`, "bin");
|
|
118853
119141
|
}
|
|
118854
119142
|
function opencodeBinaryPath(home = homedir5()) {
|
|
118855
|
-
return
|
|
119143
|
+
return join11(opencodeBinDir(home), `opencode-${OPENCODE_VERSION}`);
|
|
118856
119144
|
}
|
|
118857
119145
|
function opencodeSidecarPath(home = homedir5()) {
|
|
118858
119146
|
return `${opencodeBinaryPath(home)}.sha256`;
|
|
@@ -118907,7 +119195,7 @@ async function ensureOpencodeBinary(deps = {}) {
|
|
|
118907
119195
|
const bytes = new Uint8Array(await res.arrayBuffer());
|
|
118908
119196
|
const expected = (deps.checksums ?? OPENCODE_CHECKSUMS)[key];
|
|
118909
119197
|
const actual = sha256Hex(bytes);
|
|
118910
|
-
const tmpArchive =
|
|
119198
|
+
const tmpArchive = join11(binDir, `.${artifact}.download-${process.pid}`);
|
|
118911
119199
|
fs.writeFileSync(tmpArchive, bytes);
|
|
118912
119200
|
if (actual !== expected) {
|
|
118913
119201
|
fs.rmSync(tmpArchive, { force: true });
|
|
@@ -118915,7 +119203,7 @@ async function ensureOpencodeBinary(deps = {}) {
|
|
|
118915
119203
|
`opencode bootstrap: checksum mismatch for ${artifact} (expected ${expected}, got ${actual}). The download was deleted; refusing to install an unverified binary.`
|
|
118916
119204
|
);
|
|
118917
119205
|
}
|
|
118918
|
-
const tmpExtractDir =
|
|
119206
|
+
const tmpExtractDir = join11(binDir, `.extract-${OPENCODE_VERSION}-${process.pid}`);
|
|
118919
119207
|
fs.rmSync(tmpExtractDir, { recursive: true, force: true });
|
|
118920
119208
|
fs.mkdirSync(tmpExtractDir, { recursive: true, mode: 448 });
|
|
118921
119209
|
try {
|
|
@@ -118924,7 +119212,7 @@ async function ensureOpencodeBinary(deps = {}) {
|
|
|
118924
119212
|
} else {
|
|
118925
119213
|
exec("tar", ["-xzf", tmpArchive, "-C", tmpExtractDir]);
|
|
118926
119214
|
}
|
|
118927
|
-
const extracted =
|
|
119215
|
+
const extracted = join11(tmpExtractDir, "opencode");
|
|
118928
119216
|
if (!fs.existsSync(extracted)) {
|
|
118929
119217
|
const entries = fs.readdirSync(tmpExtractDir).join(", ") || "<empty>";
|
|
118930
119218
|
throw new Error(
|
|
@@ -118995,15 +119283,15 @@ import { randomUUID } from "crypto";
|
|
|
118995
119283
|
init_esm_shims();
|
|
118996
119284
|
init_paths();
|
|
118997
119285
|
import { homedir as homedir6 } from "os";
|
|
118998
|
-
import { join as
|
|
119286
|
+
import { join as join12 } from "path";
|
|
118999
119287
|
function bridgeRunDir(home = homedir6()) {
|
|
119000
|
-
return
|
|
119288
|
+
return join12(home, `.ametyst${ENV_SUFFIX}`, "run");
|
|
119001
119289
|
}
|
|
119002
119290
|
function bridgeSocketPath(pid, home = homedir6()) {
|
|
119003
|
-
return
|
|
119291
|
+
return join12(bridgeRunDir(home), `delegate-${pid}.sock`);
|
|
119004
119292
|
}
|
|
119005
119293
|
function bridgeNoncePath(pid, home = homedir6()) {
|
|
119006
|
-
return
|
|
119294
|
+
return join12(bridgeRunDir(home), `delegate-${pid}.nonce`);
|
|
119007
119295
|
}
|
|
119008
119296
|
function bridgeNoncePathForSocket(socketPath) {
|
|
119009
119297
|
return socketPath.replace(/\.sock$/, ".nonce");
|
|
@@ -119025,7 +119313,7 @@ function listBridgeSockets(fs, home = homedir6()) {
|
|
|
119025
119313
|
const entries = [];
|
|
119026
119314
|
for (const name of names) {
|
|
119027
119315
|
if (pidFromSocketName(name) === void 0) continue;
|
|
119028
|
-
const path2 =
|
|
119316
|
+
const path2 = join12(dir, name);
|
|
119029
119317
|
try {
|
|
119030
119318
|
entries.push({ path: path2, mtimeMs: fs.statSync(path2).mtimeMs });
|
|
119031
119319
|
} catch {
|
|
@@ -119374,7 +119662,7 @@ async function startShim(deps) {
|
|
|
119374
119662
|
init_esm_shims();
|
|
119375
119663
|
import * as nodeFs3 from "fs";
|
|
119376
119664
|
import { tmpdir } from "os";
|
|
119377
|
-
import { join as
|
|
119665
|
+
import { join as join13 } from "path";
|
|
119378
119666
|
var DELEGATE_PROVIDER_ID = "ametyst";
|
|
119379
119667
|
var DELEGATE_SHIM_API_KEY = "ametyst-local-shim";
|
|
119380
119668
|
function buildDelegateConfig(opts) {
|
|
@@ -119398,8 +119686,8 @@ function delegateModelRef(model) {
|
|
|
119398
119686
|
}
|
|
119399
119687
|
function writeTempDelegateConfig(config, deps = {}) {
|
|
119400
119688
|
const fs = deps.fs ?? nodeFs3;
|
|
119401
|
-
const dir = fs.mkdtempSync(
|
|
119402
|
-
const path2 =
|
|
119689
|
+
const dir = fs.mkdtempSync(join13(deps.tmp ?? tmpdir(), "ametyst-delegate-"));
|
|
119690
|
+
const path2 = join13(dir, "opencode.json");
|
|
119403
119691
|
fs.writeFileSync(path2, `${JSON.stringify(config, null, 2)}
|
|
119404
119692
|
`, { mode: 384 });
|
|
119405
119693
|
let done = false;
|
|
@@ -119468,7 +119756,7 @@ init_paths();
|
|
|
119468
119756
|
import * as nodeFs4 from "fs";
|
|
119469
119757
|
import { execFileSync as execFileSync2 } from "child_process";
|
|
119470
119758
|
import { homedir as homedir8 } from "os";
|
|
119471
|
-
import { basename as basename4, join as
|
|
119759
|
+
import { basename as basename4, join as join14 } from "path";
|
|
119472
119760
|
var DELEGATED_CHILD_ENV_VAR = "AMETYST_DELEGATED";
|
|
119473
119761
|
var SPEND_GRANT_TOKEN_ENV_VAR = "AMETYST_DELEGATE_SPEND_TOKEN";
|
|
119474
119762
|
var SPEND_KILL_SWITCH_ENV_VAR = "AMETYST_DELEGATE_NO_SPEND";
|
|
@@ -119550,11 +119838,11 @@ function isDelegatedByAncestry(deps = {}) {
|
|
|
119550
119838
|
return classifyAncestry(deps) === "delegated";
|
|
119551
119839
|
}
|
|
119552
119840
|
function delegateChildConfigHome(home = homedir8()) {
|
|
119553
|
-
return
|
|
119841
|
+
return join14(home, `.ametyst${ENV_SUFFIX}`, "delegate-xdg");
|
|
119554
119842
|
}
|
|
119555
119843
|
function ensureChildConfigHome(fs = nodeFs4, home = homedir8()) {
|
|
119556
119844
|
const root2 = delegateChildConfigHome(home);
|
|
119557
|
-
const inner =
|
|
119845
|
+
const inner = join14(root2, "opencode");
|
|
119558
119846
|
if (!fs.existsSync(inner)) fs.mkdirSync(inner, { recursive: true, mode: 448 });
|
|
119559
119847
|
return root2;
|
|
119560
119848
|
}
|
|
@@ -119564,7 +119852,7 @@ function userOpencodeConfigDir(deps = {}) {
|
|
|
119564
119852
|
const env = deps.env ?? process.env;
|
|
119565
119853
|
const home = deps.home ?? homedir8();
|
|
119566
119854
|
const xdg = env.XDG_CONFIG_HOME?.trim();
|
|
119567
|
-
return
|
|
119855
|
+
return join14(xdg ? xdg : join14(home, ".config"), "opencode");
|
|
119568
119856
|
}
|
|
119569
119857
|
function stripJsonComments(text) {
|
|
119570
119858
|
let out = "";
|
|
@@ -119608,9 +119896,9 @@ function readUserMcpServers(deps = {}) {
|
|
|
119608
119896
|
const warn = deps.warn ?? ((line) => console.error(line));
|
|
119609
119897
|
const home = deps.home ?? homedir8();
|
|
119610
119898
|
const dir = userOpencodeConfigDir(deps);
|
|
119611
|
-
if (dir ===
|
|
119899
|
+
if (dir === join14(delegateChildConfigHome(home), "opencode")) return {};
|
|
119612
119900
|
for (const filename of USER_OPENCODE_CONFIG_FILENAMES) {
|
|
119613
|
-
const path2 =
|
|
119901
|
+
const path2 = join14(dir, filename);
|
|
119614
119902
|
let raw;
|
|
119615
119903
|
try {
|
|
119616
119904
|
if (!fs.existsSync(path2)) continue;
|
|
@@ -120708,7 +120996,7 @@ init_esm_shims();
|
|
|
120708
120996
|
import * as nodeFs6 from "fs";
|
|
120709
120997
|
import { createHash as createHash2, randomBytes as randomBytes6, timingSafeEqual as timingSafeEqual3 } from "crypto";
|
|
120710
120998
|
import { homedir as homedir10 } from "os";
|
|
120711
|
-
import { join as
|
|
120999
|
+
import { join as join15 } from "path";
|
|
120712
121000
|
var SPEND_GRANT_RECORD_VERSION = 1;
|
|
120713
121001
|
var TOKEN_BYTES = 32;
|
|
120714
121002
|
var SPEND_GRANT_GRACE_MS = 5 * 6e4;
|
|
@@ -120716,7 +121004,7 @@ function spendGrantPath(jobId, home = homedir10()) {
|
|
|
120716
121004
|
if (!/^[A-Za-z0-9_-]{1,64}$/.test(jobId)) {
|
|
120717
121005
|
throw new Error(`refusing to build a grant path for job id ${JSON.stringify(jobId)}`);
|
|
120718
121006
|
}
|
|
120719
|
-
return
|
|
121007
|
+
return join15(bridgeRunDir(home), `spend-grant-${jobId}.json`);
|
|
120720
121008
|
}
|
|
120721
121009
|
function hashSpendGrantToken(token) {
|
|
120722
121010
|
return createHash2("sha256").update(token, "utf-8").digest("hex");
|
|
@@ -120822,13 +121110,13 @@ init_esm_shims();
|
|
|
120822
121110
|
init_paths();
|
|
120823
121111
|
import * as nodeFs7 from "fs";
|
|
120824
121112
|
import { homedir as homedir11 } from "os";
|
|
120825
|
-
import { dirname as dirname6, join as
|
|
121113
|
+
import { dirname as dirname6, join as join16 } from "path";
|
|
120826
121114
|
var DELEGATE_CREDIT_CAPABILITY = "credit";
|
|
120827
121115
|
var DELEGATE_CREDIT_PRICE_USD = 1;
|
|
120828
121116
|
var DELEGATE_CREDIT_REQUEST_BODY = "{}";
|
|
120829
121117
|
var DELEGATE_CREDIT_SPEND_SNIPPET = `spend({ merchant_slug: "${DELEGATE_MERCHANT_SLUG}", capability: "${DELEGATE_CREDIT_CAPABILITY}", inputs: "${DELEGATE_CREDIT_REQUEST_BODY}" })`;
|
|
120830
121118
|
function creditLedgerPath(home = homedir11()) {
|
|
120831
|
-
return
|
|
121119
|
+
return join16(home, `.ametyst${ENV_SUFFIX}`, "run", "delegate-credit.json");
|
|
120832
121120
|
}
|
|
120833
121121
|
function readCreditLedger(fs = nodeFs7, path2 = creditLedgerPath()) {
|
|
120834
121122
|
try {
|
|
@@ -121219,7 +121507,7 @@ function registerDelegateTools(deps) {
|
|
|
121219
121507
|
const validated = validateStartInput(params, {
|
|
121220
121508
|
isDirectory: (path2) => {
|
|
121221
121509
|
try {
|
|
121222
|
-
return
|
|
121510
|
+
return existsSync11(path2) && statSync2(path2).isDirectory();
|
|
121223
121511
|
} catch {
|
|
121224
121512
|
return false;
|
|
121225
121513
|
}
|
|
@@ -121474,7 +121762,7 @@ init_resolve();
|
|
|
121474
121762
|
|
|
121475
121763
|
// src/mcp-server/identity-reload.ts
|
|
121476
121764
|
init_esm_shims();
|
|
121477
|
-
import { readFileSync as
|
|
121765
|
+
import { readFileSync as readFileSync13, statSync as statSync3 } from "fs";
|
|
121478
121766
|
var NO_VAULT = "absent";
|
|
121479
121767
|
var IDENTITY_CHECK_THROTTLE_MS = 1e3;
|
|
121480
121768
|
function fingerprintVault(stat) {
|
|
@@ -121495,7 +121783,11 @@ var IdentityWatch = class {
|
|
|
121495
121783
|
stat;
|
|
121496
121784
|
/** False for an env-supplied key: `poll` is then a guaranteed no-op. */
|
|
121497
121785
|
armed;
|
|
121786
|
+
configPath;
|
|
121787
|
+
readWorkspaceKey;
|
|
121498
121788
|
fingerprint;
|
|
121789
|
+
configStamp;
|
|
121790
|
+
workspaceKey;
|
|
121499
121791
|
lastCheckedAt = null;
|
|
121500
121792
|
constructor(options) {
|
|
121501
121793
|
this.vaultPath = options.vaultPath;
|
|
@@ -121503,12 +121795,43 @@ var IdentityWatch = class {
|
|
|
121503
121795
|
this.throttleMs = options.throttleMs ?? IDENTITY_CHECK_THROTTLE_MS;
|
|
121504
121796
|
this.stat = options.stat ?? statVaultFile;
|
|
121505
121797
|
this.armed = options.source !== "env";
|
|
121506
|
-
this.
|
|
121798
|
+
this.configPath = options.configPath ?? null;
|
|
121799
|
+
this.readWorkspaceKey = options.readWorkspaceKey ?? null;
|
|
121800
|
+
this.configStamp = this.configPath ? fingerprintVault(this.stat(this.configPath)) : NO_VAULT;
|
|
121801
|
+
this.workspaceKey = this.readWorkspaceKeySafely();
|
|
121802
|
+
this.fingerprint = this.composeFingerprint();
|
|
121803
|
+
}
|
|
121804
|
+
/** Never let a config read take the watch — or the tool call behind it — down. */
|
|
121805
|
+
readWorkspaceKeySafely() {
|
|
121806
|
+
if (!this.readWorkspaceKey) return null;
|
|
121807
|
+
try {
|
|
121808
|
+
return this.readWorkspaceKey();
|
|
121809
|
+
} catch {
|
|
121810
|
+
return null;
|
|
121811
|
+
}
|
|
121812
|
+
}
|
|
121813
|
+
/**
|
|
121814
|
+
* Vault fingerprint AND workspace key in one string, so `poll` stays a single
|
|
121815
|
+
* comparison and neither half can change without being noticed.
|
|
121816
|
+
*
|
|
121817
|
+
* A watch with NO workspace reader appends nothing, so its fingerprint stays
|
|
121818
|
+
* exactly the string it was before 0.3.6 (`"<mtime>:<size>"` / `"absent"`).
|
|
121819
|
+
* That is not cosmetic: the fingerprint is an observable of this class, and a
|
|
121820
|
+
* vault-only watch must be indistinguishable from the one that shipped.
|
|
121821
|
+
*/
|
|
121822
|
+
composeFingerprint() {
|
|
121823
|
+
const vault = fingerprintVault(this.stat(this.vaultPath));
|
|
121824
|
+
if (!this.readWorkspaceKey) return vault;
|
|
121825
|
+
return `${vault}|ws:${this.workspaceKey ?? "unbound"}`;
|
|
121507
121826
|
}
|
|
121508
121827
|
/** The fingerprint this watch currently considers current. */
|
|
121509
121828
|
currentFingerprint() {
|
|
121510
121829
|
return this.fingerprint;
|
|
121511
121830
|
}
|
|
121831
|
+
/** The workspace key this watch last read from the config. */
|
|
121832
|
+
currentWorkspaceKey() {
|
|
121833
|
+
return this.workspaceKey;
|
|
121834
|
+
}
|
|
121512
121835
|
/**
|
|
121513
121836
|
* One throttled `stat`. Returns the change when the vault file differs from
|
|
121514
121837
|
* the fingerprint on record — and ADOPTS it in the same step, so a reload the
|
|
@@ -121521,7 +121844,14 @@ var IdentityWatch = class {
|
|
|
121521
121844
|
const at = this.now();
|
|
121522
121845
|
if (this.lastCheckedAt !== null && at - this.lastCheckedAt < this.throttleMs) return null;
|
|
121523
121846
|
this.lastCheckedAt = at;
|
|
121524
|
-
|
|
121847
|
+
if (this.configPath) {
|
|
121848
|
+
const stamp = fingerprintVault(this.stat(this.configPath));
|
|
121849
|
+
if (stamp !== this.configStamp) {
|
|
121850
|
+
this.configStamp = stamp;
|
|
121851
|
+
this.workspaceKey = this.readWorkspaceKeySafely();
|
|
121852
|
+
}
|
|
121853
|
+
}
|
|
121854
|
+
const next = this.composeFingerprint();
|
|
121525
121855
|
if (next === this.fingerprint) return null;
|
|
121526
121856
|
const from14 = this.fingerprint;
|
|
121527
121857
|
this.fingerprint = next;
|
|
@@ -121537,7 +121867,7 @@ var IdentityWatch = class {
|
|
|
121537
121867
|
*/
|
|
121538
121868
|
readVault() {
|
|
121539
121869
|
try {
|
|
121540
|
-
return
|
|
121870
|
+
return readFileSync13(this.vaultPath, "utf-8");
|
|
121541
121871
|
} catch {
|
|
121542
121872
|
return null;
|
|
121543
121873
|
}
|
|
@@ -121545,6 +121875,7 @@ var IdentityWatch = class {
|
|
|
121545
121875
|
};
|
|
121546
121876
|
|
|
121547
121877
|
// src/mcp-server/index.ts
|
|
121878
|
+
init_grant_keystore();
|
|
121548
121879
|
function buildDeclaredConnectorsSentence(catalog, unreadable) {
|
|
121549
121880
|
const read = catalog === void 0 ? { connectors: cachedCatalogOrEmpty(), unreadable: mirrorGap() } : { connectors: catalog, unreadable: unreadable ?? [] };
|
|
121550
121881
|
const gap = describeCatalogGap(read.unreadable);
|
|
@@ -121555,6 +121886,39 @@ function buildDeclaredConnectorsSentence(catalog, unreadable) {
|
|
|
121555
121886
|
return `Currently declared: ${declared}. Those are the operations picked out as most useful, NOT the limit: each provider's own surface is larger, and any operation it advertises is callable by its exact name. If the one you want is not listed, try it anyway \u2014 an operation that really does not exist comes back as a typed \`unknown_operation\` with the alternatives.` + (gap === "" ? "" : ` ${gap}`);
|
|
121556
121887
|
}
|
|
121557
121888
|
var currentCredentials = {};
|
|
121889
|
+
function vaultIsEncrypted() {
|
|
121890
|
+
return !plaintextVaultMode;
|
|
121891
|
+
}
|
|
121892
|
+
function ownedSigningAddresses() {
|
|
121893
|
+
const addresses = [];
|
|
121894
|
+
if (currentCredentials.eoaAddress) addresses.push(currentCredentials.eoaAddress);
|
|
121895
|
+
try {
|
|
121896
|
+
addresses.push(...listGrantAddresses({ workspaceId: readBoundWorkspace() }));
|
|
121897
|
+
} catch (err) {
|
|
121898
|
+
console.error(
|
|
121899
|
+
`\u26A0\uFE0F could not read the per-grant keystore \u2014 only the install EOA is selectable: ${err instanceof Error ? err.message : String(err)}`
|
|
121900
|
+
);
|
|
121901
|
+
}
|
|
121902
|
+
return addresses;
|
|
121903
|
+
}
|
|
121904
|
+
function isOwnedSigningAddress(address) {
|
|
121905
|
+
if (typeof address !== "string" || address.trim() === "") return false;
|
|
121906
|
+
const needle = address.toLowerCase();
|
|
121907
|
+
return ownedSigningAddresses().some((a) => a.toLowerCase() === needle);
|
|
121908
|
+
}
|
|
121909
|
+
function signingKeyForAddress(address, passphrase) {
|
|
121910
|
+
return resolveGrantSigningKey({
|
|
121911
|
+
address,
|
|
121912
|
+
vaultJson: currentCredentials.walletKeystoreJson,
|
|
121913
|
+
vaultAddress: currentCredentials.eoaAddress,
|
|
121914
|
+
// ⛔ THE CALLER'S PASSPHRASE WINS. `start_session` resolves the key DURING
|
|
121915
|
+
// `runStartSession`, before `mergeStartSessionCredentials` has put the
|
|
121916
|
+
// passphrase into `currentCredentials` — so reading only the field would
|
|
121917
|
+
// make every encrypted-vault unlock fail with "a passphrase is required"
|
|
121918
|
+
// on the very call that was handed one.
|
|
121919
|
+
passphrase: passphrase ?? currentCredentials.passphrase
|
|
121920
|
+
});
|
|
121921
|
+
}
|
|
121558
121922
|
var plaintextVaultMode = false;
|
|
121559
121923
|
function refreshPlaintextVaultMode() {
|
|
121560
121924
|
const vault = currentCredentials.walletKeystoreJson;
|
|
@@ -122576,7 +122940,16 @@ async function initializeCredentials(walletKeystoreJson, eoaAddress, config) {
|
|
|
122576
122940
|
currentCredentials.signerAddress = eoaAddress;
|
|
122577
122941
|
currentCredentials.eoaAddress = eoaAddress;
|
|
122578
122942
|
currentCredentials.apiKey = config.apiKey;
|
|
122579
|
-
identityWatch = new IdentityWatch({
|
|
122943
|
+
identityWatch = new IdentityWatch({
|
|
122944
|
+
vaultPath: VAULT_PATH,
|
|
122945
|
+
source: config.apiKeySource ?? null,
|
|
122946
|
+
// The workspace half (0.3.6): a `login`/`init --claim` that re-binds this
|
|
122947
|
+
// install to a DIFFERENT workspace changes the recorded key, and the same
|
|
122948
|
+
// reload that follows a swapped `wallet.json` drops the state cached for
|
|
122949
|
+
// the old one. See ./identity-reload.ts.
|
|
122950
|
+
configPath: CONFIG_PATH,
|
|
122951
|
+
readWorkspaceKey: readBoundWorkspace
|
|
122952
|
+
});
|
|
122580
122953
|
mcpEventLogger = new McpEventLogger(
|
|
122581
122954
|
() => getSDK(),
|
|
122582
122955
|
() => currentCredentials.apiKey,
|
|
@@ -122662,10 +123035,8 @@ async function createKernelClientFromVault() {
|
|
|
122662
123035
|
throw new Error("No passphrase or keystore available");
|
|
122663
123036
|
}
|
|
122664
123037
|
if (!cliConfig) throw new Error("CLI config not initialized");
|
|
122665
|
-
|
|
122666
|
-
|
|
122667
|
-
currentCredentials.walletKeystoreJson,
|
|
122668
|
-
currentCredentials.passphrase
|
|
123038
|
+
let privateKey = signingKeyForAddress(
|
|
123039
|
+
currentCredentials.signerAddress || currentCredentials.eoaAddress || ""
|
|
122669
123040
|
);
|
|
122670
123041
|
console.error(`${ts()} Wallet key loaded`);
|
|
122671
123042
|
try {
|
|
@@ -122806,12 +123177,17 @@ async function tryResolvePendingApproval(probe) {
|
|
|
122806
123177
|
if (probe) probe.fetched = true;
|
|
122807
123178
|
const approvedWallet = findCurrentApprovedWallet(
|
|
122808
123179
|
freshWallets,
|
|
122809
|
-
|
|
123180
|
+
// Post-rotation the row belongs to the ephemeral key this request minted,
|
|
123181
|
+
// not to the install EOA — match on every address we hold a key for.
|
|
123182
|
+
ownedSigningAddresses(),
|
|
122810
123183
|
currentCredentials.pendingWalletId
|
|
122811
123184
|
);
|
|
122812
123185
|
if (!approvedWallet) return false;
|
|
122813
123186
|
console.error("\u2705 [approval-wait] Backend says approved \u2014 syncing");
|
|
122814
123187
|
currentCredentials.authorizationStatus = "approved";
|
|
123188
|
+
if (typeof approvedWallet.address === "string" && approvedWallet.address.trim() !== "") {
|
|
123189
|
+
currentCredentials.signerAddress = approvedWallet.address;
|
|
123190
|
+
}
|
|
122815
123191
|
currentCredentials.walletAddress = approvedWallet.walletAddress || approvedWallet.kernelAccountAddress;
|
|
122816
123192
|
currentCredentials.virtualWalletId = String(approvedWallet.id);
|
|
122817
123193
|
currentCredentials.paymentManagerAddress = approvedWallet.paymentManagerAddress;
|
|
@@ -122938,6 +123314,12 @@ async function applyStartSessionUnlock(passphrase, resetRequested) {
|
|
|
122938
123314
|
// this process loaded at boot verifies nothing: a grant approved since boot
|
|
122939
123315
|
// would be invisible to exactly the check that exists to find it.
|
|
122940
123316
|
fetchWallets: () => fetchVirtualWalletsFromBackend(currentCredentials.apiKey, true),
|
|
123317
|
+
// ROTATION (0.3.6). Grant selection must span every address this install
|
|
123318
|
+
// holds a key for, and the key that signs must be the SELECTED row's —
|
|
123319
|
+
// not whatever `wallet.json` happens to contain. Both are injected so
|
|
123320
|
+
// `start-session.ts` keeps no dependency on the keystore's fs layer.
|
|
123321
|
+
candidateAddresses: ownedSigningAddresses(),
|
|
123322
|
+
resolveSigningKey: (address) => signingKeyForAddress(address, passphrase),
|
|
122941
123323
|
readPolicyActiveOnchain: buildPolicyActiveOnchainReader(sdk)
|
|
122942
123324
|
}
|
|
122943
123325
|
);
|
|
@@ -123351,11 +123733,15 @@ server.tool(
|
|
|
123351
123733
|
};
|
|
123352
123734
|
}
|
|
123353
123735
|
const { virtualWalletsManagers: virtualWalletsManagers2 } = await getSDK();
|
|
123354
|
-
const result = await
|
|
123736
|
+
const result = await submitRotatedAccessRequest(
|
|
123355
123737
|
virtualWalletsManagers2,
|
|
123356
123738
|
currentCredentials.apiKey,
|
|
123357
|
-
|
|
123358
|
-
|
|
123739
|
+
policyId,
|
|
123740
|
+
createGrantKeyIssuer({
|
|
123741
|
+
encrypted: vaultIsEncrypted(),
|
|
123742
|
+
passphrase: currentCredentials.passphrase,
|
|
123743
|
+
workspaceId: readBoundWorkspace()
|
|
123744
|
+
})
|
|
123359
123745
|
);
|
|
123360
123746
|
currentCredentials.authorizationStatus = "pending";
|
|
123361
123747
|
currentCredentials.pendingWalletId = result.id ? String(result.id) : void 0;
|
|
@@ -123413,7 +123799,7 @@ server.tool(
|
|
|
123413
123799
|
server.tool(
|
|
123414
123800
|
{
|
|
123415
123801
|
name: "getWalletStatus",
|
|
123416
|
-
description: "Get wallet status: auth, balance, policy, allowlist, WHICH WORKSPACE this server is acting in, and the last 10 transactions (amount, merchant, timestamp, status). `workspace` is `{companyName, employeeName}` \u2014 the Ametyst workspace every task, memory document and payment from this server lands in; both fields are null when the profile could not be read, which means UNKNOWN, never 'no workspace'. A transaction `amount` is a EUR display string like `\u20AC0.0126`, or the em dash `\u2014` when the row carries no usable amount (none recorded, or a value that is not a base-units integer) \u2014 `\u2014` means UNKNOWN, never zero. The unformatted base-units value is on `amountRaw`, which is null for exactly those rows.",
|
|
123802
|
+
description: "Get wallet status: auth, balance, policy, allowlist, WHICH GRANT AND KEY THIS SESSION SIGNS WITH, WHICH WORKSPACE this server is acting in, and the last 10 transactions (amount, merchant, timestamp, status). `signerAddress` is the address of the approved grant this session signs with \u2014 since every access request now mints its OWN ephemeral session key, it is NOT the same as `eoaAddress` (the install wallet) except for a grant approved before rotation; `activeVirtualWalletId` / `activePolicyId` name that grant's row. `workspace` is `{companyName, employeeName}` \u2014 the Ametyst workspace every task, memory document and payment from this server lands in; both fields are null when the profile could not be read, which means UNKNOWN, never 'no workspace'. A transaction `amount` is a EUR display string like `\u20AC0.0126`, or the em dash `\u2014` when the row carries no usable amount (none recorded, or a value that is not a base-units integer) \u2014 `\u2014` means UNKNOWN, never zero. The unformatted base-units value is on `amountRaw`, which is null for exactly those rows.",
|
|
123417
123803
|
inputs: []
|
|
123418
123804
|
},
|
|
123419
123805
|
async () => {
|
|
@@ -123445,7 +123831,7 @@ server.tool(
|
|
|
123445
123831
|
fetchVirtualWalletsFromBackend(currentCredentials.apiKey, !refreshedFromBackend),
|
|
123446
123832
|
fetchPoliciesFromBackend(currentCredentials.apiKey, true)
|
|
123447
123833
|
]);
|
|
123448
|
-
const freshWallet =
|
|
123834
|
+
const freshWallet = selectNewestApprovedWalletAmong(freshWallets, ownedSigningAddresses());
|
|
123449
123835
|
const freshWalletIsActiveGrant = String(freshWallet?.id) === String(currentCredentials.virtualWalletId);
|
|
123450
123836
|
if (freshWallet && freshWalletIsActiveGrant) {
|
|
123451
123837
|
if (freshWallet.policyAssociated != null) {
|
|
@@ -123466,6 +123852,14 @@ server.tool(
|
|
|
123466
123852
|
authorized: currentCredentials.authorizationStatus === "approved",
|
|
123467
123853
|
status: currentCredentials.authorizationStatus || "none",
|
|
123468
123854
|
eoaAddress: currentCredentials.eoaAddress || null,
|
|
123855
|
+
// WHICH KEY IS SIGNING. Since 0.3.6 every grant gets its OWN ephemeral
|
|
123856
|
+
// session key, so `signerAddress` is no longer a synonym for
|
|
123857
|
+
// `eoaAddress`: it is the address of the approved row this session
|
|
123858
|
+
// adopted, and `eoaAddress` is the install identity in `wallet.json`.
|
|
123859
|
+
// They agree only for a pre-rotation grant. Reporting both is what makes
|
|
123860
|
+
// "which key signed this" readable from a payload instead of from a
|
|
123861
|
+
// nonce-key autopsy — the same lesson as `activeVirtualWalletId` below.
|
|
123862
|
+
signerAddress: currentCredentials.signerAddress || currentCredentials.eoaAddress || null,
|
|
123469
123863
|
walletAddress: currentCredentials.walletAddress || null,
|
|
123470
123864
|
kernelClientActive: !!currentCredentials.virtualWalletKernelAccountClient,
|
|
123471
123865
|
// WHICH GRANT THIS SESSION SIGNS WITH. The 2026-09-01 incident — three
|
|
@@ -123489,9 +123883,7 @@ server.tool(
|
|
|
123489
123883
|
}
|
|
123490
123884
|
}
|
|
123491
123885
|
if (walletsCache && currentCredentials.eoaAddress) {
|
|
123492
|
-
const wallet =
|
|
123493
|
-
(w) => w.address?.toLowerCase() === currentCredentials.eoaAddress?.toLowerCase()
|
|
123494
|
-
);
|
|
123886
|
+
const wallet = selectNewestApprovedWalletAmong(walletsCache, ownedSigningAddresses()) ?? walletsCache.find((w) => isOwnedSigningAddress(w.address));
|
|
123495
123887
|
if (wallet) {
|
|
123496
123888
|
const now = Math.floor(Date.now() / 1e3);
|
|
123497
123889
|
const validUntil = wallet.policyValidUntil;
|
|
@@ -124015,7 +124407,7 @@ async function startLiveDashboard(entity, dir) {
|
|
|
124015
124407
|
clearInterval(watcher);
|
|
124016
124408
|
return;
|
|
124017
124409
|
}
|
|
124018
|
-
if (!
|
|
124410
|
+
if (!existsSync13(dir)) {
|
|
124019
124411
|
clearInterval(watcher);
|
|
124020
124412
|
liveDashboards.delete(slug);
|
|
124021
124413
|
try {
|
|
@@ -124962,10 +125354,13 @@ server.tool(
|
|
|
124962
125354
|
}
|
|
124963
125355
|
const approvedWallet = findCurrentApprovedWallet(
|
|
124964
125356
|
wallets,
|
|
124965
|
-
|
|
125357
|
+
ownedSigningAddresses(),
|
|
124966
125358
|
currentCredentials.pendingWalletId
|
|
124967
125359
|
);
|
|
124968
125360
|
if (approvedWallet && currentCredentials.authorizationStatus !== "approved") {
|
|
125361
|
+
if (typeof approvedWallet.address === "string" && approvedWallet.address.trim() !== "") {
|
|
125362
|
+
currentCredentials.signerAddress = approvedWallet.address;
|
|
125363
|
+
}
|
|
124969
125364
|
currentCredentials.walletAddress = approvedWallet.walletAddress || approvedWallet.kernelAccountAddress;
|
|
124970
125365
|
currentCredentials.virtualWalletId = String(approvedWallet.id);
|
|
124971
125366
|
currentCredentials.paymentManagerAddress = approvedWallet.paymentManagerAddress;
|
|
@@ -125854,7 +126249,7 @@ async function reconcileApprovalStateOnConnect() {
|
|
|
125854
126249
|
}
|
|
125855
126250
|
try {
|
|
125856
126251
|
const wallets = await fetchVirtualWalletsFromBackend(currentCredentials.apiKey, true);
|
|
125857
|
-
const approved =
|
|
126252
|
+
const approved = selectNewestApprovedWalletAmong(wallets, ownedSigningAddresses());
|
|
125858
126253
|
if (!approved) return;
|
|
125859
126254
|
if (typeof approved.id === "number" && hasProcessedApprovalEventId(approved.id)) return;
|
|
125860
126255
|
console.error(
|
|
@@ -125869,9 +126264,7 @@ async function reconcileApprovalStateOnConnect() {
|
|
|
125869
126264
|
policyOnchainPermissions: void 0
|
|
125870
126265
|
});
|
|
125871
126266
|
if (walletsCache) {
|
|
125872
|
-
const idx = walletsCache.findIndex(
|
|
125873
|
-
(w) => w.address?.toLowerCase() === currentCredentials.eoaAddress?.toLowerCase()
|
|
125874
|
-
);
|
|
126267
|
+
const idx = walletsCache.findIndex((w) => isOwnedSigningAddress(w.address));
|
|
125875
126268
|
if (idx >= 0) walletsCache[idx] = { ...walletsCache[idx], status: "approved" };
|
|
125876
126269
|
}
|
|
125877
126270
|
if (typeof approved.id === "number") rememberProcessedApprovalEventId(approved.id);
|
|
@@ -125904,8 +126297,8 @@ async function setupWebSocketListener() {
|
|
|
125904
126297
|
return;
|
|
125905
126298
|
}
|
|
125906
126299
|
const statusMatch = event.status === "approved" || event.status === "ok";
|
|
125907
|
-
const addressMatch = event.address
|
|
125908
|
-
console.error(`\u{1F50D} [WebSocket] statusMatch=${statusMatch} addressMatch=${addressMatch} eoaAddress=${currentCredentials.eoaAddress}`);
|
|
126300
|
+
const addressMatch = isOwnedSigningAddress(event.address);
|
|
126301
|
+
console.error(`\u{1F50D} [WebSocket] statusMatch=${statusMatch} addressMatch=${addressMatch} eoaAddress=${currentCredentials.eoaAddress} signerAddress=${currentCredentials.signerAddress}`);
|
|
125909
126302
|
if (statusMatch && addressMatch) {
|
|
125910
126303
|
try {
|
|
125911
126304
|
await onAuthorizationApproved({
|
|
@@ -125917,9 +126310,7 @@ async function setupWebSocketListener() {
|
|
|
125917
126310
|
policyOnchainPermissions: void 0
|
|
125918
126311
|
});
|
|
125919
126312
|
if (walletsCache) {
|
|
125920
|
-
const idx = walletsCache.findIndex(
|
|
125921
|
-
(w) => w.address?.toLowerCase() === currentCredentials.eoaAddress?.toLowerCase()
|
|
125922
|
-
);
|
|
126313
|
+
const idx = walletsCache.findIndex((w) => isOwnedSigningAddress(w.address));
|
|
125923
126314
|
if (idx >= 0) {
|
|
125924
126315
|
walletsCache[idx] = { ...walletsCache[idx], status: "approved" };
|
|
125925
126316
|
}
|
|
@@ -126192,15 +126583,15 @@ function killPreviousServeInstances(deps) {
|
|
|
126192
126583
|
|
|
126193
126584
|
// src/commands/autosync-skills.ts
|
|
126194
126585
|
init_esm_shims();
|
|
126195
|
-
import { existsSync as
|
|
126586
|
+
import { existsSync as existsSync15 } from "fs";
|
|
126196
126587
|
import { homedir as homedir12 } from "os";
|
|
126197
|
-
import { join as
|
|
126588
|
+
import { join as join19 } from "path";
|
|
126198
126589
|
|
|
126199
126590
|
// src/compounds/sync-skills.ts
|
|
126200
126591
|
init_esm_shims();
|
|
126201
126592
|
init_paths();
|
|
126202
|
-
import { existsSync as
|
|
126203
|
-
import { join as
|
|
126593
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync9, readdirSync as readdirSync5, readFileSync as readFileSync14, rmSync, writeFileSync as writeFileSync10 } from "fs";
|
|
126594
|
+
import { join as join18 } from "path";
|
|
126204
126595
|
var MANAGED_MARKER = "<!-- ametyst-managed: sync-skills -->";
|
|
126205
126596
|
var GITIGNORE_HEADER = "# ametyst-managed: sync-skills \u2014 pointer skills generated for this account; never commit them.";
|
|
126206
126597
|
var GITIGNORE_HEADER_2 = "# Maintained by `ametyst serve` on every boot. Real skills without the managed marker are not listed.";
|
|
@@ -126244,22 +126635,22 @@ ${taskRunSection(slug, kind)}`;
|
|
|
126244
126635
|
}
|
|
126245
126636
|
function isManaged(file) {
|
|
126246
126637
|
try {
|
|
126247
|
-
return
|
|
126638
|
+
return readFileSync14(file, "utf8").includes(MANAGED_MARKER);
|
|
126248
126639
|
} catch {
|
|
126249
126640
|
return false;
|
|
126250
126641
|
}
|
|
126251
126642
|
}
|
|
126252
126643
|
function listManagedDirs(root2) {
|
|
126253
|
-
return
|
|
126644
|
+
return readdirSync5(root2, { withFileTypes: true }).filter((e) => e.isDirectory() && isManaged(join18(root2, e.name, "SKILL.md"))).map((e) => e.name).sort();
|
|
126254
126645
|
}
|
|
126255
126646
|
function buildGitignoreContent(managedSlugs) {
|
|
126256
126647
|
return [GITIGNORE_HEADER, GITIGNORE_HEADER_2, ".gitignore", ...managedSlugs.map((s) => `/${s}/`)].join("\n") + "\n";
|
|
126257
126648
|
}
|
|
126258
126649
|
function maintainGitignore(root2) {
|
|
126259
|
-
const file =
|
|
126650
|
+
const file = join18(root2, ".gitignore");
|
|
126260
126651
|
const managed = listManagedDirs(root2);
|
|
126261
|
-
if (
|
|
126262
|
-
const current =
|
|
126652
|
+
if (existsSync14(file)) {
|
|
126653
|
+
const current = readFileSync14(file, "utf8");
|
|
126263
126654
|
const firstLine = current.split(/\r?\n/, 1)[0];
|
|
126264
126655
|
if (firstLine !== GITIGNORE_HEADER) {
|
|
126265
126656
|
console.error(
|
|
@@ -126317,26 +126708,26 @@ async function syncSkills(opts = {}) {
|
|
|
126317
126708
|
if (!desired.has(dir)) desired.set(dir, item);
|
|
126318
126709
|
}
|
|
126319
126710
|
const root2 = skillsRoot(target, global2);
|
|
126320
|
-
|
|
126711
|
+
mkdirSync9(root2, { recursive: true });
|
|
126321
126712
|
let written = 0;
|
|
126322
126713
|
const skipped = [];
|
|
126323
126714
|
for (const [dir, item] of desired) {
|
|
126324
|
-
const file =
|
|
126325
|
-
if (
|
|
126715
|
+
const file = join18(dir, "SKILL.md");
|
|
126716
|
+
if (existsSync14(file) && !isManaged(file)) {
|
|
126326
126717
|
skipped.push(item.slug);
|
|
126327
126718
|
console.warn(`\u26A0\uFE0F sync-skills: skipping "${item.slug}" \u2014 an unmanaged skill already exists at ${file}`);
|
|
126328
126719
|
continue;
|
|
126329
126720
|
}
|
|
126330
|
-
|
|
126721
|
+
mkdirSync9(dir, { recursive: true });
|
|
126331
126722
|
writeFileSync10(file, buildStubContent(item));
|
|
126332
126723
|
written++;
|
|
126333
126724
|
}
|
|
126334
126725
|
let pruned = 0;
|
|
126335
|
-
for (const entry of
|
|
126726
|
+
for (const entry of readdirSync5(root2, { withFileTypes: true })) {
|
|
126336
126727
|
if (!entry.isDirectory()) continue;
|
|
126337
|
-
const dir =
|
|
126728
|
+
const dir = join18(root2, entry.name);
|
|
126338
126729
|
if (desired.has(dir)) continue;
|
|
126339
|
-
if (!isManaged(
|
|
126730
|
+
if (!isManaged(join18(dir, "SKILL.md"))) continue;
|
|
126340
126731
|
rmSync(dir, { recursive: true, force: true });
|
|
126341
126732
|
pruned++;
|
|
126342
126733
|
}
|
|
@@ -126350,12 +126741,12 @@ function isSkillsAutosyncEnabled(env = process.env) {
|
|
|
126350
126741
|
return !(raw === "0" || raw === "false" || raw === "off" || raw === "no");
|
|
126351
126742
|
}
|
|
126352
126743
|
function detectPresentTargets(deps = {}) {
|
|
126353
|
-
const exists = deps.existsSync ??
|
|
126744
|
+
const exists = deps.existsSync ?? existsSync15;
|
|
126354
126745
|
const cwd = deps.cwd ?? (() => process.cwd());
|
|
126355
126746
|
const home = deps.homedir ?? homedir12;
|
|
126356
126747
|
const targets = [];
|
|
126357
|
-
if (exists(
|
|
126358
|
-
if (exists(
|
|
126748
|
+
if (exists(join19(cwd(), ".claude")) || exists(join19(home(), ".claude"))) targets.push("claude");
|
|
126749
|
+
if (exists(join19(cwd(), ".codex")) || exists(join19(home(), ".codex", "config.toml"))) targets.push("codex");
|
|
126359
126750
|
return targets;
|
|
126360
126751
|
}
|
|
126361
126752
|
function resolveAutosyncTargets(clientName, deps = {}) {
|
|
@@ -127045,14 +127436,14 @@ init_esm_shims();
|
|
|
127045
127436
|
// src/loops/materialize.ts
|
|
127046
127437
|
init_esm_shims();
|
|
127047
127438
|
init_paths();
|
|
127048
|
-
import { mkdirSync as
|
|
127049
|
-
import { join as
|
|
127439
|
+
import { mkdirSync as mkdirSync10, writeFileSync as writeFileSync11 } from "fs";
|
|
127440
|
+
import { join as join20 } from "path";
|
|
127050
127441
|
var MIRRORED_DEFINITION_FILES = ["SKILL.md", "VISION.md", "README.md"];
|
|
127051
127442
|
function materialize(loop2, fireId, stateDocs = []) {
|
|
127052
127443
|
const dir = loopFireDir(loop2.slug, fireId);
|
|
127053
|
-
|
|
127444
|
+
mkdirSync10(dir, { recursive: true, mode: 448 });
|
|
127054
127445
|
for (const doc of stateDocs) {
|
|
127055
|
-
writeFileSync11(
|
|
127446
|
+
writeFileSync11(join20(dir, doc.filename), doc.body, { mode: 384 });
|
|
127056
127447
|
}
|
|
127057
127448
|
const files = {
|
|
127058
127449
|
"SKILL.md": loop2.markdownBody ?? "",
|
|
@@ -127067,10 +127458,10 @@ function materialize(loop2, fireId, stateDocs = []) {
|
|
|
127067
127458
|
files["dashboard.manifest.json"] = loop2.dashboardManifest;
|
|
127068
127459
|
}
|
|
127069
127460
|
for (const [name, body] of Object.entries(files)) {
|
|
127070
|
-
writeFileSync11(
|
|
127461
|
+
writeFileSync11(join20(dir, name), body, { mode: 384 });
|
|
127071
127462
|
}
|
|
127072
127463
|
writeFileSync11(
|
|
127073
|
-
|
|
127464
|
+
join20(dir, "STATUS.md"),
|
|
127074
127465
|
`# STATUS \u2014 ${loop2.slug}
|
|
127075
127466
|
|
|
127076
127467
|
loop_id: ${loop2.id}
|
|
@@ -127086,11 +127477,11 @@ queue: not started
|
|
|
127086
127477
|
function mirrorDefinitionFiles(slug, files) {
|
|
127087
127478
|
try {
|
|
127088
127479
|
const root2 = loopDir(slug);
|
|
127089
|
-
|
|
127480
|
+
mkdirSync10(root2, { recursive: true, mode: 448 });
|
|
127090
127481
|
for (const name of [...MIRRORED_DEFINITION_FILES, "dashboard.html", "dashboard.manifest.json"]) {
|
|
127091
127482
|
const body = files[name];
|
|
127092
127483
|
if (body === void 0) continue;
|
|
127093
|
-
writeFileSync11(
|
|
127484
|
+
writeFileSync11(join20(root2, name), body, { mode: 384 });
|
|
127094
127485
|
}
|
|
127095
127486
|
} catch {
|
|
127096
127487
|
}
|
|
@@ -127216,15 +127607,15 @@ function resolveMaxBudgetUsd(opts, env = process.env) {
|
|
|
127216
127607
|
// src/loops/heartbeat.ts
|
|
127217
127608
|
init_esm_shims();
|
|
127218
127609
|
import * as realFs2 from "fs";
|
|
127219
|
-
import { join as
|
|
127610
|
+
import { join as join21 } from "path";
|
|
127220
127611
|
function startHeartbeat(loopDir2, info, deps = {}) {
|
|
127221
127612
|
const fs = deps.fs ?? realFs2;
|
|
127222
127613
|
const now = deps.now ?? (() => /* @__PURE__ */ new Date());
|
|
127223
127614
|
const setI = deps.setInterval ?? globalThis.setInterval;
|
|
127224
127615
|
const clearI = deps.clearInterval ?? globalThis.clearInterval;
|
|
127225
127616
|
const intervalMs = deps.intervalMs ?? 6e4;
|
|
127226
|
-
const stateDir =
|
|
127227
|
-
const path2 =
|
|
127617
|
+
const stateDir = join21(loopDir2, ".state");
|
|
127618
|
+
const path2 = join21(stateDir, "fire.running");
|
|
127228
127619
|
try {
|
|
127229
127620
|
fs.mkdirSync(stateDir, { recursive: true, mode: 448 });
|
|
127230
127621
|
fs.writeFileSync(
|
|
@@ -127264,10 +127655,10 @@ ${info.sessionId ? `session=${info.sessionId}
|
|
|
127264
127655
|
init_esm_shims();
|
|
127265
127656
|
import * as realFs3 from "fs";
|
|
127266
127657
|
import { homedir as homedir13 } from "os";
|
|
127267
|
-
import { join as
|
|
127658
|
+
import { join as join22 } from "path";
|
|
127268
127659
|
function transcriptPathFor(cwd, sessionId2, home = homedir13()) {
|
|
127269
127660
|
const slug = cwd.replace(/\//g, "-");
|
|
127270
|
-
return
|
|
127661
|
+
return join22(home, ".claude", "projects", slug, `${sessionId2}.jsonl`);
|
|
127271
127662
|
}
|
|
127272
127663
|
function parseTranscriptStats(jsonl) {
|
|
127273
127664
|
const tokens = {
|
|
@@ -127338,9 +127729,9 @@ function recordFireAccounting(args) {
|
|
|
127338
127729
|
}
|
|
127339
127730
|
}
|
|
127340
127731
|
function appendFireLine(fs, loopDir2, rec) {
|
|
127341
|
-
const stateDir =
|
|
127732
|
+
const stateDir = join22(loopDir2, ".state");
|
|
127342
127733
|
fs.mkdirSync(stateDir, { recursive: true, mode: 448 });
|
|
127343
|
-
fs.appendFileSync(
|
|
127734
|
+
fs.appendFileSync(join22(stateDir, "fires.jsonl"), JSON.stringify(rec) + "\n", { mode: 384 });
|
|
127344
127735
|
}
|
|
127345
127736
|
function recordFailedLaunch(args) {
|
|
127346
127737
|
const fs = args.deps?.fs ?? realFs3;
|
|
@@ -127369,8 +127760,8 @@ function recordFailedLaunch(args) {
|
|
|
127369
127760
|
init_esm_shims();
|
|
127370
127761
|
import { spawn as spawn2 } from "child_process";
|
|
127371
127762
|
import { randomUUID as randomUUID5 } from "crypto";
|
|
127372
|
-
import { existsSync as
|
|
127373
|
-
import { dirname as dirname8, join as
|
|
127763
|
+
import { existsSync as existsSync16, mkdirSync as mkdirSync11, readFileSync as readFileSync15, rmSync as rmSync2, writeFileSync as writeFileSync12 } from "fs";
|
|
127764
|
+
import { dirname as dirname8, join as join24 } from "path";
|
|
127374
127765
|
|
|
127375
127766
|
// src/loops/claude-binary.ts
|
|
127376
127767
|
init_esm_shims();
|
|
@@ -127422,7 +127813,7 @@ function ensureClaudeBinary(deps = {}) {
|
|
|
127422
127813
|
// src/loops/concurrency.ts
|
|
127423
127814
|
init_esm_shims();
|
|
127424
127815
|
import * as realFs4 from "fs";
|
|
127425
|
-
import { join as
|
|
127816
|
+
import { join as join23 } from "path";
|
|
127426
127817
|
var DEFAULT_MAX_CONCURRENT_FIRES = 6;
|
|
127427
127818
|
var DEFAULT_HEARTBEAT_STALE_MS = 10 * 6e4;
|
|
127428
127819
|
function pidIsAlive(pid) {
|
|
@@ -127446,14 +127837,14 @@ function liveFires(loopRoot, deps = {}) {
|
|
|
127446
127837
|
const staleMs = deps.staleMs ?? DEFAULT_HEARTBEAT_STALE_MS;
|
|
127447
127838
|
let entries;
|
|
127448
127839
|
try {
|
|
127449
|
-
entries = fs.readdirSync(
|
|
127840
|
+
entries = fs.readdirSync(join23(loopRoot, "fires"));
|
|
127450
127841
|
} catch {
|
|
127451
127842
|
return [];
|
|
127452
127843
|
}
|
|
127453
127844
|
const out = [];
|
|
127454
127845
|
for (const entry of entries) {
|
|
127455
127846
|
try {
|
|
127456
|
-
const beat =
|
|
127847
|
+
const beat = join23(loopRoot, "fires", String(entry), ".state", "fire.running");
|
|
127457
127848
|
const st = fs.statSync(beat);
|
|
127458
127849
|
const heartbeatAgeMs = now() - Number(st.mtimeMs);
|
|
127459
127850
|
if (!(heartbeatAgeMs <= staleMs)) continue;
|
|
@@ -127481,7 +127872,7 @@ var SHIPBACK_SIGNAL_TIMEOUT_MS = 8e3;
|
|
|
127481
127872
|
function preserveRefusedConstraints(slug, fireId, body) {
|
|
127482
127873
|
try {
|
|
127483
127874
|
const path2 = refusedConstraintsPath(slug, fireId);
|
|
127484
|
-
|
|
127875
|
+
mkdirSync11(dirname8(path2), { recursive: true, mode: 448 });
|
|
127485
127876
|
writeFileSync12(path2, body, { mode: 384 });
|
|
127486
127877
|
return { path: path2 };
|
|
127487
127878
|
} catch (err) {
|
|
@@ -127560,8 +127951,8 @@ async function runLoop(loopId, opts = {}) {
|
|
|
127560
127951
|
console.log(
|
|
127561
127952
|
`Loop ${loop2.slug}: ${est.steps} steps (${est.paidSteps} paid), est \u20AC${est.estCostEur ?? "?"} \u2014 ${capLabel}`
|
|
127562
127953
|
);
|
|
127563
|
-
const statusPath =
|
|
127564
|
-
const statusBefore =
|
|
127954
|
+
const statusPath = join24(dir, "STATUS.md");
|
|
127955
|
+
const statusBefore = existsSync16(statusPath) ? readFileSync15(statusPath, "utf-8") : "";
|
|
127565
127956
|
writeFileSync12(
|
|
127566
127957
|
statusPath,
|
|
127567
127958
|
statusBefore.replace(
|
|
@@ -127620,8 +128011,8 @@ blast_radius: steps=${est.steps} paid=${est.paidSteps} estEur=${est.estCostEur ?
|
|
|
127620
128011
|
}
|
|
127621
128012
|
async function shipConstraints() {
|
|
127622
128013
|
try {
|
|
127623
|
-
const constraintsPath =
|
|
127624
|
-
const materializedConstraints =
|
|
128014
|
+
const constraintsPath = join24(dir, "CONSTRAINTS.md");
|
|
128015
|
+
const materializedConstraints = existsSync16(constraintsPath) ? readFileSync15(constraintsPath, "utf-8") : void 0;
|
|
127625
128016
|
if (materializedConstraints === void 0) return;
|
|
127626
128017
|
const boot = loop2.constraintsMd ?? "";
|
|
127627
128018
|
for (let attempt = 1; attempt <= 2; attempt++) {
|
|
@@ -127696,7 +128087,7 @@ blast_radius: steps=${est.steps} paid=${est.paidSteps} estEur=${est.estCostEur ?
|
|
|
127696
128087
|
void Promise.race([shipBackConstraints().then(() => "shipped"), deadline]).then((outcome) => {
|
|
127697
128088
|
if (outcome === "timeout") {
|
|
127698
128089
|
console.error(
|
|
127699
|
-
`Loop ${loop2.slug}: the ship-back did not finish within ${SHIPBACK_SIGNAL_TIMEOUT_MS}ms of the signal \u2014 exiting anyway so the shutdown is not held open. Nothing was discarded: this fire's rules remain at ${
|
|
128090
|
+
`Loop ${loop2.slug}: the ship-back did not finish within ${SHIPBACK_SIGNAL_TIMEOUT_MS}ms of the signal \u2014 exiting anyway so the shutdown is not held open. Nothing was discarded: this fire's rules remain at ${join24(dir, "CONSTRAINTS.md")}, and any state doc that did not get shipped remains beside it in ${dir}. The next fire can recover them by hand from there \u2014 nothing in the CLI reads a prior fire's folder automatically. \u26D4 Constraints are shipped FIRST, so the state docs are the likelier victim of this deadline \u2014 though a constraints ship-back that alone exceeds it truncates the rules too.`
|
|
127700
128091
|
);
|
|
127701
128092
|
}
|
|
127702
128093
|
}).finally(() => process.exit(130));
|
|
@@ -127732,7 +128123,7 @@ blast_radius: steps=${est.steps} paid=${est.paidSteps} estEur=${est.estCostEur ?
|
|
|
127732
128123
|
startedAtEpochMs
|
|
127733
128124
|
});
|
|
127734
128125
|
}
|
|
127735
|
-
const statusAfter =
|
|
128126
|
+
const statusAfter = existsSync16(statusPath) ? readFileSync15(statusPath, "utf-8") : "";
|
|
127736
128127
|
const clean2 = exitCode === 0 && /(vision\s*done|queue\s*drained|status:\s*done)/i.test(statusAfter) && !/(brake|crash|crashed|errored)/i.test(statusAfter);
|
|
127737
128128
|
await shipBackConstraints();
|
|
127738
128129
|
process.off("SIGINT", onSignal);
|
|
@@ -127770,9 +128161,9 @@ async function showLoop(loopId) {
|
|
|
127770
128161
|
// src/loops/schedule.ts
|
|
127771
128162
|
init_esm_shims();
|
|
127772
128163
|
init_paths();
|
|
127773
|
-
import { writeFileSync as writeFileSync13, mkdirSync as
|
|
128164
|
+
import { writeFileSync as writeFileSync13, mkdirSync as mkdirSync12, rmSync as rmSync3, existsSync as existsSync17, readdirSync as readdirSync6, readFileSync as readFileSync16, accessSync as accessSync2, constants as constants2 } from "fs";
|
|
127774
128165
|
import { execFileSync as execFileSync4 } from "child_process";
|
|
127775
|
-
import { join as
|
|
128166
|
+
import { join as join25, dirname as dirname9 } from "path";
|
|
127776
128167
|
import { homedir as homedir14 } from "os";
|
|
127777
128168
|
var LOOP_KIND = {
|
|
127778
128169
|
labelPrefix: "xyz.ametyst.loop.",
|
|
@@ -127783,7 +128174,7 @@ var LOOP_KIND = {
|
|
|
127783
128174
|
return args;
|
|
127784
128175
|
},
|
|
127785
128176
|
stateDir(slug, cwd) {
|
|
127786
|
-
return
|
|
128177
|
+
return join25(cwd, `.ametyst${ENV_SUFFIX}`, "loops", safeSlug2(slug, this.noun), ".state");
|
|
127787
128178
|
}
|
|
127788
128179
|
};
|
|
127789
128180
|
var TASK_KIND = {
|
|
@@ -127795,7 +128186,7 @@ var TASK_KIND = {
|
|
|
127795
128186
|
return args;
|
|
127796
128187
|
},
|
|
127797
128188
|
stateDir(slug, cwd) {
|
|
127798
|
-
return
|
|
128189
|
+
return join25(cwd, `.ametyst${ENV_SUFFIX}`, "loops", safeSlug2(slug, this.noun), ".state");
|
|
127799
128190
|
}
|
|
127800
128191
|
};
|
|
127801
128192
|
var COMPOUND_KIND = {
|
|
@@ -127807,7 +128198,7 @@ var COMPOUND_KIND = {
|
|
|
127807
128198
|
return args;
|
|
127808
128199
|
},
|
|
127809
128200
|
stateDir(slug, cwd) {
|
|
127810
|
-
return
|
|
128201
|
+
return join25(cwd, `.ametyst${ENV_SUFFIX}`, "compounds", safeSlug2(slug, this.noun), ".state");
|
|
127811
128202
|
}
|
|
127812
128203
|
};
|
|
127813
128204
|
function safeSlug2(slug, noun) {
|
|
@@ -127834,7 +128225,7 @@ function parseAt(at) {
|
|
|
127834
128225
|
return { hour, minute };
|
|
127835
128226
|
}
|
|
127836
128227
|
function plistPath(kind, home, slug) {
|
|
127837
|
-
return
|
|
128228
|
+
return join25(home, "Library", "LaunchAgents", `${label(kind, slug)}.plist`);
|
|
127838
128229
|
}
|
|
127839
128230
|
function launchctl(args) {
|
|
127840
128231
|
try {
|
|
@@ -127850,7 +128241,7 @@ function isLoaded(lbl) {
|
|
|
127850
128241
|
}
|
|
127851
128242
|
function readInteractiveDefaultModel(home) {
|
|
127852
128243
|
try {
|
|
127853
|
-
const raw =
|
|
128244
|
+
const raw = readFileSync16(join25(home, ".claude", "settings.json"), "utf-8");
|
|
127854
128245
|
const model = JSON.parse(String(raw)).model;
|
|
127855
128246
|
return typeof model === "string" && model.trim() ? model.trim() : void 0;
|
|
127856
128247
|
} catch {
|
|
@@ -127952,9 +128343,9 @@ ${progArgs}
|
|
|
127952
128343
|
${envEntries}
|
|
127953
128344
|
</dict>
|
|
127954
128345
|
<key>StandardOutPath</key>
|
|
127955
|
-
<string>${escapeXml(
|
|
128346
|
+
<string>${escapeXml(join25(stateDir, LAUNCHD_OUT))}</string>
|
|
127956
128347
|
<key>StandardErrorPath</key>
|
|
127957
|
-
<string>${escapeXml(
|
|
128348
|
+
<string>${escapeXml(join25(stateDir, LAUNCHD_ERR))}</string>
|
|
127958
128349
|
<key>AbandonProcessGroup</key>
|
|
127959
128350
|
<true/>
|
|
127960
128351
|
${scheduleBlock}
|
|
@@ -128020,8 +128411,8 @@ function schedule(kind, slug, opts = {}) {
|
|
|
128020
128411
|
const path2 = plistPath(kind, home, slug);
|
|
128021
128412
|
const stateDir2 = kind.stateDir(slug, cwd);
|
|
128022
128413
|
assertWritable(cwd, "working directory");
|
|
128023
|
-
|
|
128024
|
-
|
|
128414
|
+
mkdirSync12(dirname9(path2), { recursive: true });
|
|
128415
|
+
mkdirSync12(stateDir2, { recursive: true });
|
|
128025
128416
|
assertWritable(stateDir2, "log directory");
|
|
128026
128417
|
writeFileSync13(path2, plistXml(lbl, args, scheduleBlock, cwd, jobEnv, stateDir2), { mode: 384 });
|
|
128027
128418
|
if (isLoaded(lbl)) {
|
|
@@ -128044,7 +128435,7 @@ launchctl said: ${loaded.output.trim()}` : "")
|
|
|
128044
128435
|
}
|
|
128045
128436
|
const stateDir = kind.stateDir(slug, cwd);
|
|
128046
128437
|
assertWritable(cwd, "working directory");
|
|
128047
|
-
|
|
128438
|
+
mkdirSync12(stateDir, { recursive: true });
|
|
128048
128439
|
assertWritable(stateDir, "log directory");
|
|
128049
128440
|
const cronEnv = { PATH: envPath };
|
|
128050
128441
|
if (model) cronEnv.ANTHROPIC_MODEL = model;
|
|
@@ -128077,9 +128468,9 @@ function list(kind, opts = {}) {
|
|
|
128077
128468
|
const home = opts.home ?? homedir14();
|
|
128078
128469
|
const prefix = kind.labelPrefix;
|
|
128079
128470
|
if (platform === "darwin") {
|
|
128080
|
-
const dir =
|
|
128081
|
-
if (!
|
|
128082
|
-
return
|
|
128471
|
+
const dir = join25(home, "Library", "LaunchAgents");
|
|
128472
|
+
if (!existsSync17(dir)) return [];
|
|
128473
|
+
return readdirSync6(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".plist")).map((f) => f.slice(prefix.length, -".plist".length));
|
|
128083
128474
|
}
|
|
128084
128475
|
return readCrontab().split("\n").filter((l) => l.includes(`# ${prefix}`)).map((l) => l.trimEnd().slice(l.trimEnd().lastIndexOf(prefix) + prefix.length));
|
|
128085
128476
|
}
|
|
@@ -128139,13 +128530,13 @@ function listEntries(kind, opts = {}) {
|
|
|
128139
128530
|
const home = opts.home ?? homedir14();
|
|
128140
128531
|
const prefix = kind.labelPrefix;
|
|
128141
128532
|
if (platform === "darwin") {
|
|
128142
|
-
const dir =
|
|
128143
|
-
if (!
|
|
128144
|
-
return
|
|
128533
|
+
const dir = join25(home, "Library", "LaunchAgents");
|
|
128534
|
+
if (!existsSync17(dir)) return [];
|
|
128535
|
+
return readdirSync6(dir).filter((f) => f.startsWith(prefix) && f.endsWith(".plist")).map((f) => {
|
|
128145
128536
|
const slug = f.slice(prefix.length, -".plist".length);
|
|
128146
128537
|
let envKeys = [];
|
|
128147
128538
|
try {
|
|
128148
|
-
envKeys = plistEnvKeys(String(
|
|
128539
|
+
envKeys = plistEnvKeys(String(readFileSync16(join25(dir, f), "utf-8")));
|
|
128149
128540
|
} catch {
|
|
128150
128541
|
envKeys = [];
|
|
128151
128542
|
}
|