@hasna/accounts 0.1.7 → 0.1.9
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 +1 -1
- package/dist/cli.js +899 -120
- package/dist/lib/agents.d.ts +33 -3
- package/dist/lib/agents.d.ts.map +1 -1
- package/dist/mcp.js +1 -1
- package/package.json +2 -1
package/dist/cli.js
CHANGED
|
@@ -992,7 +992,7 @@ Expecting one of '${allowedValues.join("', '")}'`);
|
|
|
992
992
|
this._exitCallback = (err) => {
|
|
993
993
|
if (err.code !== "commander.executeSubCommandAsync") {
|
|
994
994
|
throw err;
|
|
995
|
-
}
|
|
995
|
+
}
|
|
996
996
|
};
|
|
997
997
|
}
|
|
998
998
|
return this;
|
|
@@ -4307,7 +4307,7 @@ var require_config = __commonJS((exports) => {
|
|
|
4307
4307
|
};
|
|
4308
4308
|
var filePromises = {};
|
|
4309
4309
|
var fileIntercept = {};
|
|
4310
|
-
var
|
|
4310
|
+
var readFile2 = (path, options) => {
|
|
4311
4311
|
if (fileIntercept[path] !== undefined) {
|
|
4312
4312
|
return fileIntercept[path];
|
|
4313
4313
|
}
|
|
@@ -4330,10 +4330,10 @@ var require_config = __commonJS((exports) => {
|
|
|
4330
4330
|
resolvedConfigFilepath = node_path.join(homeDir, configFilepath.slice(2));
|
|
4331
4331
|
}
|
|
4332
4332
|
const parsedFiles = await Promise.all([
|
|
4333
|
-
|
|
4333
|
+
readFile2(resolvedConfigFilepath, {
|
|
4334
4334
|
ignoreCache: init.ignoreCache
|
|
4335
4335
|
}).then(parseIni).then(getConfigData).catch(swallowError$1),
|
|
4336
|
-
|
|
4336
|
+
readFile2(resolvedFilepath, {
|
|
4337
4337
|
ignoreCache: init.ignoreCache
|
|
4338
4338
|
}).then(parseIni).catch(swallowError$1)
|
|
4339
4339
|
]);
|
|
@@ -4344,7 +4344,7 @@ var require_config = __commonJS((exports) => {
|
|
|
4344
4344
|
};
|
|
4345
4345
|
var getSsoSessionData = (data) => Object.entries(data).filter(([key]) => key.startsWith(types2.IniSectionType.SSO_SESSION + CONFIG_PREFIX_SEPARATOR)).reduce((acc, [key, value]) => ({ ...acc, [key.substring(key.indexOf(CONFIG_PREFIX_SEPARATOR) + 1)]: value }), {});
|
|
4346
4346
|
var swallowError = () => ({});
|
|
4347
|
-
var loadSsoSessionData = async (init = {}) =>
|
|
4347
|
+
var loadSsoSessionData = async (init = {}) => readFile2(init.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
|
|
4348
4348
|
var mergeConfigFiles = (...files) => {
|
|
4349
4349
|
const merged = {};
|
|
4350
4350
|
for (const file of files) {
|
|
@@ -4711,7 +4711,7 @@ var require_config = __commonJS((exports) => {
|
|
|
4711
4711
|
exports.nodeFipsConfigSelectors = nodeFipsConfigSelectors;
|
|
4712
4712
|
exports.numberSelector = numberSelector;
|
|
4713
4713
|
exports.parseKnownFiles = parseKnownFiles;
|
|
4714
|
-
exports.readFile =
|
|
4714
|
+
exports.readFile = readFile2;
|
|
4715
4715
|
exports.resolveCustomEndpointsConfig = resolveCustomEndpointsConfig;
|
|
4716
4716
|
exports.resolveDefaultsModeConfig = resolveDefaultsModeConfig;
|
|
4717
4717
|
exports.resolveEndpointsConfig = resolveEndpointsConfig;
|
|
@@ -5760,19 +5760,19 @@ var require_serde = __commonJS((exports) => {
|
|
|
5760
5760
|
};
|
|
5761
5761
|
var strictParseDouble = (value) => {
|
|
5762
5762
|
if (typeof value == "string") {
|
|
5763
|
-
return expectNumber(
|
|
5763
|
+
return expectNumber(parseNumber2(value));
|
|
5764
5764
|
}
|
|
5765
5765
|
return expectNumber(value);
|
|
5766
5766
|
};
|
|
5767
5767
|
var strictParseFloat = strictParseDouble;
|
|
5768
5768
|
var strictParseFloat32 = (value) => {
|
|
5769
5769
|
if (typeof value == "string") {
|
|
5770
|
-
return expectFloat32(
|
|
5770
|
+
return expectFloat32(parseNumber2(value));
|
|
5771
5771
|
}
|
|
5772
5772
|
return expectFloat32(value);
|
|
5773
5773
|
};
|
|
5774
5774
|
var NUMBER_REGEX = /(-?(?:0|[1-9]\d*)(?:\.\d+)?(?:[eE][+-]?\d+)?)|(-?Infinity)|(NaN)/g;
|
|
5775
|
-
var
|
|
5775
|
+
var parseNumber2 = (value) => {
|
|
5776
5776
|
const matches = value.match(NUMBER_REGEX);
|
|
5777
5777
|
if (matches === null || matches[0].length !== value.length) {
|
|
5778
5778
|
throw new TypeError(`Expected real number, got implicit NaN`);
|
|
@@ -5807,26 +5807,26 @@ var require_serde = __commonJS((exports) => {
|
|
|
5807
5807
|
};
|
|
5808
5808
|
var strictParseLong = (value) => {
|
|
5809
5809
|
if (typeof value === "string") {
|
|
5810
|
-
return expectLong(
|
|
5810
|
+
return expectLong(parseNumber2(value));
|
|
5811
5811
|
}
|
|
5812
5812
|
return expectLong(value);
|
|
5813
5813
|
};
|
|
5814
5814
|
var strictParseInt = strictParseLong;
|
|
5815
5815
|
var strictParseInt32 = (value) => {
|
|
5816
5816
|
if (typeof value === "string") {
|
|
5817
|
-
return expectInt32(
|
|
5817
|
+
return expectInt32(parseNumber2(value));
|
|
5818
5818
|
}
|
|
5819
5819
|
return expectInt32(value);
|
|
5820
5820
|
};
|
|
5821
5821
|
var strictParseShort = (value) => {
|
|
5822
5822
|
if (typeof value === "string") {
|
|
5823
|
-
return expectShort(
|
|
5823
|
+
return expectShort(parseNumber2(value));
|
|
5824
5824
|
}
|
|
5825
5825
|
return expectShort(value);
|
|
5826
5826
|
};
|
|
5827
5827
|
var strictParseByte = (value) => {
|
|
5828
5828
|
if (typeof value === "string") {
|
|
5829
|
-
return expectByte(
|
|
5829
|
+
return expectByte(parseNumber2(value));
|
|
5830
5830
|
}
|
|
5831
5831
|
return expectByte(value);
|
|
5832
5832
|
};
|
|
@@ -12259,7 +12259,7 @@ var require_es5 = __commonJS((exports, module) => {
|
|
|
12259
12259
|
|
|
12260
12260
|
// node_modules/@aws-sdk/core/dist-cjs/submodules/client/index.js
|
|
12261
12261
|
var require_client2 = __commonJS((exports) => {
|
|
12262
|
-
var __dirname = "/
|
|
12262
|
+
var __dirname = "/tmp/open-accounts-events-release-384975/node_modules/@aws-sdk/core/dist-cjs/submodules/client";
|
|
12263
12263
|
var retry = require_retry();
|
|
12264
12264
|
var protocols = require_protocols();
|
|
12265
12265
|
var lambdaInvokeStore = require_invoke_store();
|
|
@@ -14153,8 +14153,8 @@ ${serde.toHex(hashedRequest)}`;
|
|
|
14153
14153
|
throw new Error("Resolved credential object is not valid");
|
|
14154
14154
|
}
|
|
14155
14155
|
}
|
|
14156
|
-
formatDate(
|
|
14157
|
-
const longDate = iso8601(
|
|
14156
|
+
formatDate(now2) {
|
|
14157
|
+
const longDate = iso8601(now2).replace(/[\-:]/g, "");
|
|
14158
14158
|
return {
|
|
14159
14159
|
longDate,
|
|
14160
14160
|
shortDate: longDate.slice(0, 8)
|
|
@@ -19955,8 +19955,8 @@ var require_s3 = __commonJS((exports) => {
|
|
|
19955
19955
|
delete this.data[key];
|
|
19956
19956
|
}
|
|
19957
19957
|
async purgeExpired() {
|
|
19958
|
-
const
|
|
19959
|
-
if (this.lastPurgeTime + S3ExpressIdentityCache.EXPIRED_CREDENTIAL_PURGE_INTERVAL_MS >
|
|
19958
|
+
const now2 = Date.now();
|
|
19959
|
+
if (this.lastPurgeTime + S3ExpressIdentityCache.EXPIRED_CREDENTIAL_PURGE_INTERVAL_MS > now2) {
|
|
19960
19960
|
return;
|
|
19961
19961
|
}
|
|
19962
19962
|
for (const key in this.data) {
|
|
@@ -19964,7 +19964,7 @@ var require_s3 = __commonJS((exports) => {
|
|
|
19964
19964
|
if (!entry.isRefreshing) {
|
|
19965
19965
|
const credential = await entry.identity;
|
|
19966
19966
|
if (credential.expiration) {
|
|
19967
|
-
if (credential.expiration.getTime() <
|
|
19967
|
+
if (credential.expiration.getTime() < now2) {
|
|
19968
19968
|
delete this.data[key];
|
|
19969
19969
|
}
|
|
19970
19970
|
}
|
|
@@ -30963,11 +30963,11 @@ var require_dist_cjs15 = __commonJS((exports) => {
|
|
|
30963
30963
|
throw new config.TokenProviderError(`Value not present for '${key}' in SSO Token${forRefresh ? ". Cannot refresh" : ""}. ${REFRESH_MESSAGE}`, false);
|
|
30964
30964
|
}
|
|
30965
30965
|
};
|
|
30966
|
-
var { writeFile } = node_fs.promises;
|
|
30966
|
+
var { writeFile: writeFile2 } = node_fs.promises;
|
|
30967
30967
|
var writeSSOTokenToFile = (id, ssoToken) => {
|
|
30968
30968
|
const tokenFilepath = config.getSSOTokenFilepath(id);
|
|
30969
30969
|
const tokenString = JSON.stringify(ssoToken, null, 2);
|
|
30970
|
-
return
|
|
30970
|
+
return writeFile2(tokenFilepath, tokenString);
|
|
30971
30971
|
};
|
|
30972
30972
|
var lastRefreshAttemptTime = new Date(0);
|
|
30973
30973
|
var fromSso = (init = {}) => async ({ callerClientConfig } = {}) => {
|
|
@@ -32364,9 +32364,9 @@ var require_dist_cjs17 = __commonJS((exports) => {
|
|
|
32364
32364
|
throw new config.CredentialsProviderError(`Failed to load a token for session ${this.loginSession}, please re-authenticate using aws login`, { tryNextLink: false, logger: this.logger });
|
|
32365
32365
|
}
|
|
32366
32366
|
const accessToken = token.accessToken;
|
|
32367
|
-
const
|
|
32367
|
+
const now2 = Date.now();
|
|
32368
32368
|
const expiryTime = new Date(accessToken.expiresAt).getTime();
|
|
32369
|
-
const timeUntilExpiry = expiryTime -
|
|
32369
|
+
const timeUntilExpiry = expiryTime - now2;
|
|
32370
32370
|
if (timeUntilExpiry <= LoginCredentialsFetcher.REFRESH_THRESHOLD) {
|
|
32371
32371
|
return this.refresh(token);
|
|
32372
32372
|
}
|
|
@@ -36797,9 +36797,9 @@ var require_dist_cjs22 = __commonJS((exports) => {
|
|
|
36797
36797
|
|
|
36798
36798
|
// src/cli.ts
|
|
36799
36799
|
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
36800
|
-
import { existsSync as
|
|
36801
|
-
import { homedir as
|
|
36802
|
-
import { dirname as dirname5, join as
|
|
36800
|
+
import { existsSync as existsSync12, readFileSync as readFileSync7 } from "node:fs";
|
|
36801
|
+
import { homedir as homedir6 } from "node:os";
|
|
36802
|
+
import { dirname as dirname5, join as join12 } from "node:path";
|
|
36803
36803
|
import { fileURLToPath } from "node:url";
|
|
36804
36804
|
|
|
36805
36805
|
// node_modules/commander/esm.mjs
|
|
@@ -36818,6 +36818,684 @@ var {
|
|
|
36818
36818
|
Help
|
|
36819
36819
|
} = import__.default;
|
|
36820
36820
|
|
|
36821
|
+
// node_modules/@hasna/events/dist/commander.js
|
|
36822
|
+
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
36823
|
+
import { existsSync } from "fs";
|
|
36824
|
+
import { homedir } from "os";
|
|
36825
|
+
import { join } from "path";
|
|
36826
|
+
import { createHmac, timingSafeEqual } from "crypto";
|
|
36827
|
+
import { randomUUID } from "crypto";
|
|
36828
|
+
import { spawn } from "child_process";
|
|
36829
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
36830
|
+
function getPathValue(input, path) {
|
|
36831
|
+
return path.split(".").reduce((value, part) => {
|
|
36832
|
+
if (value && typeof value === "object" && part in value) {
|
|
36833
|
+
return value[part];
|
|
36834
|
+
}
|
|
36835
|
+
return;
|
|
36836
|
+
}, input);
|
|
36837
|
+
}
|
|
36838
|
+
function wildcardToRegExp(pattern) {
|
|
36839
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
36840
|
+
return new RegExp(`^${escaped}$`);
|
|
36841
|
+
}
|
|
36842
|
+
function matchString(value, matcher) {
|
|
36843
|
+
if (matcher === undefined)
|
|
36844
|
+
return true;
|
|
36845
|
+
if (value === undefined)
|
|
36846
|
+
return false;
|
|
36847
|
+
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
36848
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
36849
|
+
}
|
|
36850
|
+
function matchRecord(input, matcher) {
|
|
36851
|
+
if (!matcher)
|
|
36852
|
+
return true;
|
|
36853
|
+
return Object.entries(matcher).every(([path, expected]) => {
|
|
36854
|
+
const actual = getPathValue(input, path);
|
|
36855
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
36856
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
36857
|
+
}
|
|
36858
|
+
return actual === expected;
|
|
36859
|
+
});
|
|
36860
|
+
}
|
|
36861
|
+
function eventMatchesFilter(event, filter) {
|
|
36862
|
+
return matchString(event.source, filter.source) && matchString(event.type, filter.type) && matchString(event.subject, filter.subject) && matchString(event.severity, filter.severity) && matchRecord(event.data, filter.data) && matchRecord(event.metadata, filter.metadata);
|
|
36863
|
+
}
|
|
36864
|
+
function channelMatchesEvent(channel, event) {
|
|
36865
|
+
if (!channel.enabled)
|
|
36866
|
+
return false;
|
|
36867
|
+
if (!channel.filters || channel.filters.length === 0)
|
|
36868
|
+
return true;
|
|
36869
|
+
return channel.filters.some((filter) => eventMatchesFilter(event, filter));
|
|
36870
|
+
}
|
|
36871
|
+
var HASNA_EVENTS_DIR_ENV = "HASNA_EVENTS_DIR";
|
|
36872
|
+
var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
36873
|
+
function getEventsDataDir(override) {
|
|
36874
|
+
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
36875
|
+
}
|
|
36876
|
+
|
|
36877
|
+
class JsonEventsStore {
|
|
36878
|
+
dataDir;
|
|
36879
|
+
channelsPath;
|
|
36880
|
+
eventsPath;
|
|
36881
|
+
deliveriesPath;
|
|
36882
|
+
constructor(dataDir = getEventsDataDir()) {
|
|
36883
|
+
this.dataDir = dataDir;
|
|
36884
|
+
this.channelsPath = join(dataDir, "channels.json");
|
|
36885
|
+
this.eventsPath = join(dataDir, "events.json");
|
|
36886
|
+
this.deliveriesPath = join(dataDir, "deliveries.json");
|
|
36887
|
+
}
|
|
36888
|
+
async init() {
|
|
36889
|
+
await mkdir(this.dataDir, { recursive: true, mode: 448 });
|
|
36890
|
+
await chmod(this.dataDir, 448).catch(() => {
|
|
36891
|
+
return;
|
|
36892
|
+
});
|
|
36893
|
+
await this.ensureArrayFile(this.channelsPath);
|
|
36894
|
+
await this.ensureArrayFile(this.eventsPath);
|
|
36895
|
+
await this.ensureArrayFile(this.deliveriesPath);
|
|
36896
|
+
}
|
|
36897
|
+
async addChannel(channel) {
|
|
36898
|
+
await this.init();
|
|
36899
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
36900
|
+
const index = channels.findIndex((item) => item.id === channel.id);
|
|
36901
|
+
if (index >= 0) {
|
|
36902
|
+
channels[index] = { ...channel, createdAt: channels[index].createdAt, updatedAt: new Date().toISOString() };
|
|
36903
|
+
} else {
|
|
36904
|
+
channels.push(channel);
|
|
36905
|
+
}
|
|
36906
|
+
await this.writeJson(this.channelsPath, channels);
|
|
36907
|
+
return index >= 0 ? channels[index] : channel;
|
|
36908
|
+
}
|
|
36909
|
+
async listChannels() {
|
|
36910
|
+
await this.init();
|
|
36911
|
+
return this.readJson(this.channelsPath, []);
|
|
36912
|
+
}
|
|
36913
|
+
async getChannel(id) {
|
|
36914
|
+
const channels = await this.listChannels();
|
|
36915
|
+
return channels.find((channel) => channel.id === id);
|
|
36916
|
+
}
|
|
36917
|
+
async removeChannel(id) {
|
|
36918
|
+
await this.init();
|
|
36919
|
+
const channels = await this.readJson(this.channelsPath, []);
|
|
36920
|
+
const next = channels.filter((channel) => channel.id !== id);
|
|
36921
|
+
await this.writeJson(this.channelsPath, next);
|
|
36922
|
+
return next.length !== channels.length;
|
|
36923
|
+
}
|
|
36924
|
+
async appendEvent(event) {
|
|
36925
|
+
await this.init();
|
|
36926
|
+
const events = await this.readJson(this.eventsPath, []);
|
|
36927
|
+
events.push(event);
|
|
36928
|
+
await this.writeJson(this.eventsPath, events);
|
|
36929
|
+
return event;
|
|
36930
|
+
}
|
|
36931
|
+
async listEvents() {
|
|
36932
|
+
await this.init();
|
|
36933
|
+
return this.readJson(this.eventsPath, []);
|
|
36934
|
+
}
|
|
36935
|
+
async findEventByIdentity(identity) {
|
|
36936
|
+
const events = await this.listEvents();
|
|
36937
|
+
return events.find((event) => identity.id !== undefined && event.id === identity.id || identity.dedupeKey !== undefined && event.dedupeKey === identity.dedupeKey);
|
|
36938
|
+
}
|
|
36939
|
+
async appendDelivery(result) {
|
|
36940
|
+
await this.init();
|
|
36941
|
+
const deliveries = await this.readJson(this.deliveriesPath, []);
|
|
36942
|
+
deliveries.push(result);
|
|
36943
|
+
await this.writeJson(this.deliveriesPath, deliveries);
|
|
36944
|
+
return result;
|
|
36945
|
+
}
|
|
36946
|
+
async listDeliveries() {
|
|
36947
|
+
await this.init();
|
|
36948
|
+
return this.readJson(this.deliveriesPath, []);
|
|
36949
|
+
}
|
|
36950
|
+
async exportData() {
|
|
36951
|
+
return {
|
|
36952
|
+
channels: await this.listChannels(),
|
|
36953
|
+
events: await this.listEvents(),
|
|
36954
|
+
deliveries: await this.listDeliveries()
|
|
36955
|
+
};
|
|
36956
|
+
}
|
|
36957
|
+
async ensureArrayFile(path) {
|
|
36958
|
+
if (!existsSync(path)) {
|
|
36959
|
+
await writeFile(path, `[]
|
|
36960
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
36961
|
+
}
|
|
36962
|
+
await chmod(path, 384).catch(() => {
|
|
36963
|
+
return;
|
|
36964
|
+
});
|
|
36965
|
+
}
|
|
36966
|
+
async readJson(path, fallback) {
|
|
36967
|
+
try {
|
|
36968
|
+
const raw = await readFile(path, "utf-8");
|
|
36969
|
+
if (!raw.trim())
|
|
36970
|
+
return fallback;
|
|
36971
|
+
return JSON.parse(raw);
|
|
36972
|
+
} catch (error) {
|
|
36973
|
+
if (error.code === "ENOENT")
|
|
36974
|
+
return fallback;
|
|
36975
|
+
throw error;
|
|
36976
|
+
}
|
|
36977
|
+
}
|
|
36978
|
+
async writeJson(path, value) {
|
|
36979
|
+
const tempPath = `${path}.${process.pid}.${Date.now()}.tmp`;
|
|
36980
|
+
await writeFile(tempPath, `${JSON.stringify(value, null, 2)}
|
|
36981
|
+
`, { encoding: "utf-8", mode: 384 });
|
|
36982
|
+
await rename(tempPath, path);
|
|
36983
|
+
await chmod(path, 384).catch(() => {
|
|
36984
|
+
return;
|
|
36985
|
+
});
|
|
36986
|
+
}
|
|
36987
|
+
}
|
|
36988
|
+
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
36989
|
+
function buildSignatureBase(timestamp, body) {
|
|
36990
|
+
return `${timestamp}.${body}`;
|
|
36991
|
+
}
|
|
36992
|
+
function signPayload(secret, timestamp, body) {
|
|
36993
|
+
const digest = createHmac("sha256", secret).update(buildSignatureBase(timestamp, body)).digest("hex");
|
|
36994
|
+
return `sha256=${digest}`;
|
|
36995
|
+
}
|
|
36996
|
+
function now() {
|
|
36997
|
+
return new Date().toISOString();
|
|
36998
|
+
}
|
|
36999
|
+
function truncate(value, max = 4096) {
|
|
37000
|
+
return value.length > max ? `${value.slice(0, max)}...` : value;
|
|
37001
|
+
}
|
|
37002
|
+
function buildWebhookRequest(event, channel) {
|
|
37003
|
+
if (!channel.webhook)
|
|
37004
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
37005
|
+
const body = JSON.stringify(event);
|
|
37006
|
+
const timestamp = event.time;
|
|
37007
|
+
const headers = {
|
|
37008
|
+
"Content-Type": "application/json",
|
|
37009
|
+
"User-Agent": "@hasna/events",
|
|
37010
|
+
"X-Hasna-Event-Id": event.id,
|
|
37011
|
+
"X-Hasna-Event-Type": event.type,
|
|
37012
|
+
"X-Hasna-Timestamp": timestamp,
|
|
37013
|
+
...channel.webhook.headers
|
|
37014
|
+
};
|
|
37015
|
+
if (channel.webhook.secret) {
|
|
37016
|
+
headers["X-Hasna-Signature"] = signPayload(channel.webhook.secret, timestamp, body);
|
|
37017
|
+
}
|
|
37018
|
+
return { body, headers };
|
|
37019
|
+
}
|
|
37020
|
+
async function dispatchWebhook(event, channel, options = {}) {
|
|
37021
|
+
if (!channel.webhook)
|
|
37022
|
+
throw new Error(`Channel ${channel.id} has no webhook config`);
|
|
37023
|
+
const startedAt = now();
|
|
37024
|
+
const { body, headers } = buildWebhookRequest(event, channel);
|
|
37025
|
+
const controller = new AbortController;
|
|
37026
|
+
const timeout = setTimeout(() => controller.abort(), channel.webhook.timeoutMs ?? 15000);
|
|
37027
|
+
try {
|
|
37028
|
+
const response = await (options.fetchImpl ?? fetch)(channel.webhook.url, {
|
|
37029
|
+
method: "POST",
|
|
37030
|
+
headers,
|
|
37031
|
+
body,
|
|
37032
|
+
signal: controller.signal
|
|
37033
|
+
});
|
|
37034
|
+
const responseBody = truncate(await response.text());
|
|
37035
|
+
return {
|
|
37036
|
+
attempt: 1,
|
|
37037
|
+
status: response.ok ? "success" : "failed",
|
|
37038
|
+
startedAt,
|
|
37039
|
+
completedAt: now(),
|
|
37040
|
+
responseStatus: response.status,
|
|
37041
|
+
responseBody,
|
|
37042
|
+
error: response.ok ? undefined : `Webhook returned HTTP ${response.status}`
|
|
37043
|
+
};
|
|
37044
|
+
} catch (error) {
|
|
37045
|
+
return {
|
|
37046
|
+
attempt: 1,
|
|
37047
|
+
status: "failed",
|
|
37048
|
+
startedAt,
|
|
37049
|
+
completedAt: now(),
|
|
37050
|
+
error: error instanceof Error ? error.message : String(error)
|
|
37051
|
+
};
|
|
37052
|
+
} finally {
|
|
37053
|
+
clearTimeout(timeout);
|
|
37054
|
+
}
|
|
37055
|
+
}
|
|
37056
|
+
async function dispatchCommand(event, channel) {
|
|
37057
|
+
if (!channel.command)
|
|
37058
|
+
throw new Error(`Channel ${channel.id} has no command config`);
|
|
37059
|
+
const startedAt = now();
|
|
37060
|
+
const eventJson = JSON.stringify(event);
|
|
37061
|
+
const env = {
|
|
37062
|
+
...process.env,
|
|
37063
|
+
...channel.command.env,
|
|
37064
|
+
HASNA_CHANNEL_ID: channel.id,
|
|
37065
|
+
HASNA_EVENT_ID: event.id,
|
|
37066
|
+
HASNA_EVENT_TYPE: event.type,
|
|
37067
|
+
HASNA_EVENT_SOURCE: event.source,
|
|
37068
|
+
HASNA_EVENT_SUBJECT: event.subject ?? "",
|
|
37069
|
+
HASNA_EVENT_SEVERITY: event.severity,
|
|
37070
|
+
HASNA_EVENT_TIME: event.time,
|
|
37071
|
+
HASNA_EVENT_DEDUPE_KEY: event.dedupeKey ?? "",
|
|
37072
|
+
HASNA_EVENT_SCHEMA_VERSION: event.schemaVersion,
|
|
37073
|
+
HASNA_EVENT_JSON: eventJson
|
|
37074
|
+
};
|
|
37075
|
+
return new Promise((resolve) => {
|
|
37076
|
+
const child = spawn(channel.command.command, channel.command.args ?? [], {
|
|
37077
|
+
cwd: channel.command.cwd,
|
|
37078
|
+
env,
|
|
37079
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
37080
|
+
});
|
|
37081
|
+
let stdout = "";
|
|
37082
|
+
let stderr = "";
|
|
37083
|
+
const timeout = setTimeout(() => child.kill("SIGTERM"), channel.command.timeoutMs ?? 15000);
|
|
37084
|
+
child.stdin.end(eventJson);
|
|
37085
|
+
child.stdout.on("data", (chunk) => {
|
|
37086
|
+
stdout += chunk.toString();
|
|
37087
|
+
});
|
|
37088
|
+
child.stderr.on("data", (chunk) => {
|
|
37089
|
+
stderr += chunk.toString();
|
|
37090
|
+
});
|
|
37091
|
+
child.on("error", (error) => {
|
|
37092
|
+
clearTimeout(timeout);
|
|
37093
|
+
resolve({
|
|
37094
|
+
attempt: 1,
|
|
37095
|
+
status: "failed",
|
|
37096
|
+
startedAt,
|
|
37097
|
+
completedAt: now(),
|
|
37098
|
+
stdout: truncate(stdout),
|
|
37099
|
+
stderr: truncate(stderr),
|
|
37100
|
+
error: error.message
|
|
37101
|
+
});
|
|
37102
|
+
});
|
|
37103
|
+
child.on("close", (code, signal) => {
|
|
37104
|
+
clearTimeout(timeout);
|
|
37105
|
+
const success = code === 0;
|
|
37106
|
+
resolve({
|
|
37107
|
+
attempt: 1,
|
|
37108
|
+
status: success ? "success" : "failed",
|
|
37109
|
+
startedAt,
|
|
37110
|
+
completedAt: now(),
|
|
37111
|
+
stdout: truncate(stdout),
|
|
37112
|
+
stderr: truncate(stderr),
|
|
37113
|
+
error: success ? undefined : `Command exited with ${signal ? `signal ${signal}` : `code ${code}`}`
|
|
37114
|
+
});
|
|
37115
|
+
});
|
|
37116
|
+
});
|
|
37117
|
+
}
|
|
37118
|
+
async function dispatchChannel(event, channel, options = {}) {
|
|
37119
|
+
if (channel.transport === "webhook")
|
|
37120
|
+
return dispatchWebhook(event, channel, options);
|
|
37121
|
+
if (channel.transport === "command")
|
|
37122
|
+
return dispatchCommand(event, channel);
|
|
37123
|
+
return {
|
|
37124
|
+
attempt: 1,
|
|
37125
|
+
status: "skipped",
|
|
37126
|
+
startedAt: now(),
|
|
37127
|
+
completedAt: now(),
|
|
37128
|
+
error: `Unsupported transport: ${channel.transport}`
|
|
37129
|
+
};
|
|
37130
|
+
}
|
|
37131
|
+
function createDeliveryResult(event, channel, attempts) {
|
|
37132
|
+
const status = attempts.some((attempt) => attempt.status === "success") ? "success" : attempts.every((attempt) => attempt.status === "skipped") ? "skipped" : "failed";
|
|
37133
|
+
return {
|
|
37134
|
+
id: randomUUID(),
|
|
37135
|
+
eventId: event.id,
|
|
37136
|
+
channelId: channel.id,
|
|
37137
|
+
transport: channel.transport,
|
|
37138
|
+
status,
|
|
37139
|
+
attempts,
|
|
37140
|
+
createdAt: attempts[0]?.startedAt ?? now(),
|
|
37141
|
+
completedAt: attempts.at(-1)?.completedAt ?? now()
|
|
37142
|
+
};
|
|
37143
|
+
}
|
|
37144
|
+
function createEvent(input) {
|
|
37145
|
+
return {
|
|
37146
|
+
id: input.id ?? randomUUID2(),
|
|
37147
|
+
source: input.source,
|
|
37148
|
+
type: input.type,
|
|
37149
|
+
time: normalizeTime(input.time),
|
|
37150
|
+
subject: input.subject,
|
|
37151
|
+
severity: input.severity ?? "info",
|
|
37152
|
+
data: input.data ?? {},
|
|
37153
|
+
message: input.message,
|
|
37154
|
+
dedupeKey: input.dedupeKey,
|
|
37155
|
+
schemaVersion: input.schemaVersion ?? "1.0",
|
|
37156
|
+
metadata: input.metadata ?? {}
|
|
37157
|
+
};
|
|
37158
|
+
}
|
|
37159
|
+
|
|
37160
|
+
class EventsClient {
|
|
37161
|
+
store;
|
|
37162
|
+
redactors;
|
|
37163
|
+
transportOptions;
|
|
37164
|
+
constructor(options = {}) {
|
|
37165
|
+
this.store = options.store ?? new JsonEventsStore(options.dataDir);
|
|
37166
|
+
this.redactors = options.redactors ?? [];
|
|
37167
|
+
this.transportOptions = { fetchImpl: options.fetchImpl };
|
|
37168
|
+
}
|
|
37169
|
+
async addChannel(input) {
|
|
37170
|
+
const timestamp = new Date().toISOString();
|
|
37171
|
+
return this.store.addChannel({
|
|
37172
|
+
...input,
|
|
37173
|
+
createdAt: input.createdAt ?? timestamp,
|
|
37174
|
+
updatedAt: input.updatedAt ?? timestamp
|
|
37175
|
+
});
|
|
37176
|
+
}
|
|
37177
|
+
async listChannels() {
|
|
37178
|
+
return this.store.listChannels();
|
|
37179
|
+
}
|
|
37180
|
+
async removeChannel(id) {
|
|
37181
|
+
return this.store.removeChannel(id);
|
|
37182
|
+
}
|
|
37183
|
+
async emit(input, options = {}) {
|
|
37184
|
+
const event = options.redactSensitiveData === false ? createEvent(input) : redactSensitiveKeys(createEvent(input));
|
|
37185
|
+
if (options.dedupe !== false) {
|
|
37186
|
+
const existing = await this.store.findEventByIdentity({ id: input.id, dedupeKey: event.dedupeKey });
|
|
37187
|
+
if (existing) {
|
|
37188
|
+
return { event: existing, deliveries: [], deduped: true };
|
|
37189
|
+
}
|
|
37190
|
+
}
|
|
37191
|
+
await this.store.appendEvent(event);
|
|
37192
|
+
const deliveries = options.deliver === false ? [] : await this.deliver(event);
|
|
37193
|
+
return { event, deliveries, deduped: false };
|
|
37194
|
+
}
|
|
37195
|
+
async listEvents() {
|
|
37196
|
+
return this.store.listEvents();
|
|
37197
|
+
}
|
|
37198
|
+
async listDeliveries() {
|
|
37199
|
+
return this.store.listDeliveries();
|
|
37200
|
+
}
|
|
37201
|
+
async deliver(event) {
|
|
37202
|
+
const channels = await this.store.listChannels();
|
|
37203
|
+
const selected = channels.filter((channel) => channelMatchesEvent(channel, event));
|
|
37204
|
+
const deliveries = [];
|
|
37205
|
+
for (const channel of selected) {
|
|
37206
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
37207
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
37208
|
+
await this.store.appendDelivery(result);
|
|
37209
|
+
deliveries.push(result);
|
|
37210
|
+
}
|
|
37211
|
+
return deliveries;
|
|
37212
|
+
}
|
|
37213
|
+
async testChannel(id, input = {}) {
|
|
37214
|
+
const channel = await this.store.getChannel(id);
|
|
37215
|
+
if (!channel)
|
|
37216
|
+
throw new Error(`Channel not found: ${id}`);
|
|
37217
|
+
const event = createEvent({
|
|
37218
|
+
source: input.source ?? "hasna.events",
|
|
37219
|
+
type: input.type ?? "events.test",
|
|
37220
|
+
subject: input.subject ?? id,
|
|
37221
|
+
severity: input.severity ?? "info",
|
|
37222
|
+
data: input.data ?? { test: true },
|
|
37223
|
+
message: input.message ?? "Hasna events test delivery",
|
|
37224
|
+
dedupeKey: input.dedupeKey,
|
|
37225
|
+
schemaVersion: input.schemaVersion,
|
|
37226
|
+
metadata: input.metadata,
|
|
37227
|
+
time: input.time,
|
|
37228
|
+
id: input.id
|
|
37229
|
+
});
|
|
37230
|
+
const eventForChannel = await this.applyRedaction(event, channel);
|
|
37231
|
+
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
37232
|
+
await this.store.appendDelivery(result);
|
|
37233
|
+
return result;
|
|
37234
|
+
}
|
|
37235
|
+
async replay(options = {}) {
|
|
37236
|
+
const events = (await this.store.listEvents()).filter((event) => {
|
|
37237
|
+
if (options.eventId && event.id !== options.eventId)
|
|
37238
|
+
return false;
|
|
37239
|
+
if (options.source && event.source !== options.source)
|
|
37240
|
+
return false;
|
|
37241
|
+
if (options.type && event.type !== options.type)
|
|
37242
|
+
return false;
|
|
37243
|
+
return true;
|
|
37244
|
+
});
|
|
37245
|
+
if (options.dryRun)
|
|
37246
|
+
return { events, deliveries: [] };
|
|
37247
|
+
const deliveries = [];
|
|
37248
|
+
for (const event of events) {
|
|
37249
|
+
deliveries.push(...await this.deliver(event));
|
|
37250
|
+
}
|
|
37251
|
+
return { events, deliveries };
|
|
37252
|
+
}
|
|
37253
|
+
async applyRedaction(event, channel) {
|
|
37254
|
+
let next = redactPaths(event, channel.redact?.paths ?? [], channel.redact?.replacement ?? "[REDACTED]");
|
|
37255
|
+
for (const redactor of this.redactors) {
|
|
37256
|
+
next = await redactor(next, channel);
|
|
37257
|
+
}
|
|
37258
|
+
return next;
|
|
37259
|
+
}
|
|
37260
|
+
async deliverWithRetry(event, channel) {
|
|
37261
|
+
const policy = normalizeRetryPolicy(channel.retry);
|
|
37262
|
+
const attempts = [];
|
|
37263
|
+
for (let index = 0;index < policy.maxAttempts; index += 1) {
|
|
37264
|
+
const attempt = await dispatchChannel(event, channel, this.transportOptions);
|
|
37265
|
+
attempt.attempt = index + 1;
|
|
37266
|
+
if (attempt.status === "failed" && index + 1 < policy.maxAttempts) {
|
|
37267
|
+
attempt.nextBackoffMs = Math.round(policy.backoffMs * policy.multiplier ** index);
|
|
37268
|
+
}
|
|
37269
|
+
attempts.push(attempt);
|
|
37270
|
+
if (attempt.status !== "failed")
|
|
37271
|
+
break;
|
|
37272
|
+
if (attempt.nextBackoffMs)
|
|
37273
|
+
await Bun.sleep(attempt.nextBackoffMs);
|
|
37274
|
+
}
|
|
37275
|
+
return createDeliveryResult(event, channel, attempts);
|
|
37276
|
+
}
|
|
37277
|
+
}
|
|
37278
|
+
function redactPaths(event, paths, replacement = "[REDACTED]") {
|
|
37279
|
+
if (paths.length === 0)
|
|
37280
|
+
return event;
|
|
37281
|
+
const copy = structuredClone(event);
|
|
37282
|
+
for (const path of paths) {
|
|
37283
|
+
setPath(copy, path, replacement);
|
|
37284
|
+
}
|
|
37285
|
+
return copy;
|
|
37286
|
+
}
|
|
37287
|
+
function sanitizeChannelForOutput(channel) {
|
|
37288
|
+
const copy = structuredClone(channel);
|
|
37289
|
+
if (copy.webhook?.secret)
|
|
37290
|
+
copy.webhook.secret = "[REDACTED]";
|
|
37291
|
+
if (copy.command?.env) {
|
|
37292
|
+
copy.command.env = Object.fromEntries(Object.entries(copy.command.env).map(([key, value]) => [key, shouldRedactKey(key) ? "[REDACTED]" : value]));
|
|
37293
|
+
}
|
|
37294
|
+
return copy;
|
|
37295
|
+
}
|
|
37296
|
+
function sanitizeChannelsForOutput(channels) {
|
|
37297
|
+
return channels.map(sanitizeChannelForOutput);
|
|
37298
|
+
}
|
|
37299
|
+
function redactSensitiveKeys(event, replacement = "[REDACTED]") {
|
|
37300
|
+
return redactValue(event, replacement);
|
|
37301
|
+
}
|
|
37302
|
+
function shouldRedactKey(key) {
|
|
37303
|
+
return /secret|token|password|api[_-]?key|authorization/i.test(key);
|
|
37304
|
+
}
|
|
37305
|
+
function redactValue(value, replacement) {
|
|
37306
|
+
if (Array.isArray(value))
|
|
37307
|
+
return value.map((item) => redactValue(item, replacement));
|
|
37308
|
+
if (!value || typeof value !== "object")
|
|
37309
|
+
return value;
|
|
37310
|
+
return Object.fromEntries(Object.entries(value).map(([key, item]) => [
|
|
37311
|
+
key,
|
|
37312
|
+
shouldRedactKey(key) ? replacement : redactValue(item, replacement)
|
|
37313
|
+
]));
|
|
37314
|
+
}
|
|
37315
|
+
function setPath(input, path, replacement) {
|
|
37316
|
+
const parts = path.split(".");
|
|
37317
|
+
let cursor = input;
|
|
37318
|
+
for (const part of parts.slice(0, -1)) {
|
|
37319
|
+
const next = cursor[part];
|
|
37320
|
+
if (!next || typeof next !== "object")
|
|
37321
|
+
return;
|
|
37322
|
+
cursor = next;
|
|
37323
|
+
}
|
|
37324
|
+
const last = parts.at(-1);
|
|
37325
|
+
if (last && last in cursor)
|
|
37326
|
+
cursor[last] = replacement;
|
|
37327
|
+
}
|
|
37328
|
+
function normalizeTime(value) {
|
|
37329
|
+
if (!value)
|
|
37330
|
+
return new Date().toISOString();
|
|
37331
|
+
return value instanceof Date ? value.toISOString() : value;
|
|
37332
|
+
}
|
|
37333
|
+
function normalizeRetryPolicy(policy) {
|
|
37334
|
+
return {
|
|
37335
|
+
maxAttempts: Math.max(1, policy?.maxAttempts ?? 1),
|
|
37336
|
+
backoffMs: Math.max(0, policy?.backoffMs ?? 250),
|
|
37337
|
+
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
37338
|
+
};
|
|
37339
|
+
}
|
|
37340
|
+
function parseJsonObject(value, fallback) {
|
|
37341
|
+
if (!value)
|
|
37342
|
+
return fallback;
|
|
37343
|
+
const parsed = JSON.parse(value);
|
|
37344
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
37345
|
+
throw new Error("Expected a JSON object");
|
|
37346
|
+
}
|
|
37347
|
+
return parsed;
|
|
37348
|
+
}
|
|
37349
|
+
function parseHeaders(values) {
|
|
37350
|
+
if (!values?.length)
|
|
37351
|
+
return;
|
|
37352
|
+
const headers = {};
|
|
37353
|
+
for (const value of values) {
|
|
37354
|
+
const separator = value.indexOf("=");
|
|
37355
|
+
if (separator === -1)
|
|
37356
|
+
throw new Error(`Invalid header, expected name=value: ${value}`);
|
|
37357
|
+
headers[value.slice(0, separator)] = value.slice(separator + 1);
|
|
37358
|
+
}
|
|
37359
|
+
return headers;
|
|
37360
|
+
}
|
|
37361
|
+
function parseFilter(options) {
|
|
37362
|
+
const filter2 = {};
|
|
37363
|
+
if (options.source)
|
|
37364
|
+
filter2.source = options.source;
|
|
37365
|
+
if (options.type)
|
|
37366
|
+
filter2.type = options.type;
|
|
37367
|
+
if (options.subject)
|
|
37368
|
+
filter2.subject = options.subject;
|
|
37369
|
+
if (options.severity)
|
|
37370
|
+
filter2.severity = options.severity;
|
|
37371
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
37372
|
+
}
|
|
37373
|
+
function createClient(options) {
|
|
37374
|
+
if (options.createClient)
|
|
37375
|
+
return options.createClient();
|
|
37376
|
+
return new EventsClient({ store: new JsonEventsStore(options.dataDir) });
|
|
37377
|
+
}
|
|
37378
|
+
function print(value, json, text) {
|
|
37379
|
+
if (json)
|
|
37380
|
+
console.log(JSON.stringify(value, null, 2));
|
|
37381
|
+
else
|
|
37382
|
+
console.log(text);
|
|
37383
|
+
}
|
|
37384
|
+
function registerWebhookCommands(program2, options) {
|
|
37385
|
+
const webhooks = program2.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
37386
|
+
webhooks.command("add").description("Add or replace a webhook or command subscription").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "Subscription/channel identifier").option("--transport <kind>", "Transport kind: webhook or command", "webhook").option("--name <name>", "Display name").option("--type <pattern>", "Event type filter, e.g. todos.task.*").option("--source <pattern>", "Event source filter").option("--subject <pattern>", "Event subject filter").option("--severity <pattern>", "Event severity filter").option("--secret <secret>", "Webhook HMAC secret").option("--header <name=value...>", "Webhook header", collectValues, []).option("--arg <arg...>", "Command argument", collectValues, []).option("--timeout-ms <ms>", "Transport timeout in milliseconds", parseNumber).option("--retry-attempts <n>", "Maximum delivery attempts", parseNumber).option("--retry-backoff-ms <ms>", "Initial retry backoff in milliseconds", parseNumber).option("--redact <path...>", "Event field path to redact before delivery", collectValues, []).option("--disabled", "Create channel disabled", false).option("-j, --json", "Print JSON output", false).action(async (target, actionOptions) => {
|
|
37387
|
+
const timestamp = new Date().toISOString();
|
|
37388
|
+
const channel = {
|
|
37389
|
+
id: actionOptions.id,
|
|
37390
|
+
name: actionOptions.name,
|
|
37391
|
+
enabled: !actionOptions.disabled,
|
|
37392
|
+
transport: actionOptions.transport,
|
|
37393
|
+
filters: parseFilter(actionOptions),
|
|
37394
|
+
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
37395
|
+
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
37396
|
+
createdAt: timestamp,
|
|
37397
|
+
updatedAt: timestamp
|
|
37398
|
+
};
|
|
37399
|
+
if (actionOptions.transport === "webhook") {
|
|
37400
|
+
channel.webhook = { url: target, secret: actionOptions.secret, headers: parseHeaders(actionOptions.header), timeoutMs: actionOptions.timeoutMs };
|
|
37401
|
+
} else if (actionOptions.transport === "command") {
|
|
37402
|
+
channel.command = { command: target, args: actionOptions.arg ?? [], timeoutMs: actionOptions.timeoutMs };
|
|
37403
|
+
} else {
|
|
37404
|
+
throw new Error(`Transport ${actionOptions.transport} is reserved for future use and cannot be added yet`);
|
|
37405
|
+
}
|
|
37406
|
+
const saved = await createClient(options).addChannel(channel);
|
|
37407
|
+
print(sanitizeChannelForOutput(saved), Boolean(actionOptions.json), `Added ${saved.transport} channel ${saved.id}`);
|
|
37408
|
+
});
|
|
37409
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
37410
|
+
const channels = await createClient(options).listChannels();
|
|
37411
|
+
if (actionOptions.json) {
|
|
37412
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
37413
|
+
return;
|
|
37414
|
+
}
|
|
37415
|
+
if (!channels.length) {
|
|
37416
|
+
console.log("No channels configured.");
|
|
37417
|
+
return;
|
|
37418
|
+
}
|
|
37419
|
+
for (const channel of channels) {
|
|
37420
|
+
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
37421
|
+
}
|
|
37422
|
+
});
|
|
37423
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
37424
|
+
const removed = await createClient(options).removeChannel(id);
|
|
37425
|
+
print({ removed }, Boolean(actionOptions.json), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
37426
|
+
});
|
|
37427
|
+
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events test delivery").option("--data <json>", "Event data JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions) => {
|
|
37428
|
+
const result = await createClient(options).testChannel(id, {
|
|
37429
|
+
source: options.source,
|
|
37430
|
+
type: actionOptions.type,
|
|
37431
|
+
subject: actionOptions.subject ?? id,
|
|
37432
|
+
message: actionOptions.message,
|
|
37433
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
37434
|
+
});
|
|
37435
|
+
print(result, Boolean(actionOptions.json), `${result.status}: ${result.channelId}`);
|
|
37436
|
+
});
|
|
37437
|
+
return webhooks;
|
|
37438
|
+
}
|
|
37439
|
+
function registerEventCommands(program2, options) {
|
|
37440
|
+
const events = program2.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
37441
|
+
events.command("emit").description("Emit an event from this app").argument("<type>", "Event type").option("--source <source>", "Event source override").option("--subject <subject>", "Event subject").option("--severity <severity>", "Event severity", "info").option("--message <message>", "Event message").option("--dedupe-key <key>", "Dedupe key").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("--no-deliver", "Record without delivering").option("--no-dedupe", "Allow duplicate id/dedupeKey events").option("-j, --json", "Print JSON output", false).action(async (type, actionOptions) => {
|
|
37442
|
+
const result = await createClient(options).emit({
|
|
37443
|
+
source: actionOptions.source ?? options.source,
|
|
37444
|
+
type,
|
|
37445
|
+
subject: actionOptions.subject,
|
|
37446
|
+
severity: actionOptions.severity,
|
|
37447
|
+
message: actionOptions.message,
|
|
37448
|
+
dedupeKey: actionOptions.dedupeKey,
|
|
37449
|
+
data: parseJsonObject(actionOptions.data, {}),
|
|
37450
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
37451
|
+
}, { deliver: actionOptions.deliver, dedupe: actionOptions.dedupe });
|
|
37452
|
+
print(result, Boolean(actionOptions.json), `${result.deduped ? "Deduped" : "Emitted"} ${result.event.id} to ${result.deliveries.length} channel(s)`);
|
|
37453
|
+
});
|
|
37454
|
+
events.command("list").description("List recorded events").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--limit <n>", "Limit results", parseNumber).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
37455
|
+
let rows = await createClient(options).listEvents();
|
|
37456
|
+
if (actionOptions.source)
|
|
37457
|
+
rows = rows.filter((event) => event.source === actionOptions.source);
|
|
37458
|
+
if (actionOptions.type)
|
|
37459
|
+
rows = rows.filter((event) => event.type === actionOptions.type);
|
|
37460
|
+
if (actionOptions.limit)
|
|
37461
|
+
rows = rows.slice(-actionOptions.limit);
|
|
37462
|
+
if (actionOptions.json) {
|
|
37463
|
+
console.log(JSON.stringify(rows, null, 2));
|
|
37464
|
+
return;
|
|
37465
|
+
}
|
|
37466
|
+
if (!rows.length) {
|
|
37467
|
+
console.log("No events recorded.");
|
|
37468
|
+
return;
|
|
37469
|
+
}
|
|
37470
|
+
for (const event of rows)
|
|
37471
|
+
console.log(`${event.time} ${event.id} ${event.source} ${event.type} ${event.severity}`);
|
|
37472
|
+
});
|
|
37473
|
+
events.command("replay").description("Replay recorded events").option("--id <id>", "Replay one event id").option("--source <source>", "Filter by source").option("--type <type>", "Filter by type").option("--dry-run", "Preview without delivery", false).option("-j, --json", "Print JSON output", false).action(async (actionOptions) => {
|
|
37474
|
+
const result = await createClient(options).replay({
|
|
37475
|
+
eventId: actionOptions.id,
|
|
37476
|
+
source: actionOptions.source,
|
|
37477
|
+
type: actionOptions.type,
|
|
37478
|
+
dryRun: actionOptions.dryRun
|
|
37479
|
+
});
|
|
37480
|
+
print(result, Boolean(actionOptions.json), `Replayed ${result.events.length} event(s), ${result.deliveries.length} delivery result(s)`);
|
|
37481
|
+
});
|
|
37482
|
+
return events;
|
|
37483
|
+
}
|
|
37484
|
+
function registerEventsCommands(program2, options) {
|
|
37485
|
+
registerWebhookCommands(program2, options);
|
|
37486
|
+
registerEventCommands(program2, options);
|
|
37487
|
+
}
|
|
37488
|
+
function parseNumber(value) {
|
|
37489
|
+
const parsed = Number(value);
|
|
37490
|
+
if (!Number.isFinite(parsed))
|
|
37491
|
+
throw new Error(`Expected a number, got ${value}`);
|
|
37492
|
+
return parsed;
|
|
37493
|
+
}
|
|
37494
|
+
function collectValues(value, previous) {
|
|
37495
|
+
previous.push(value);
|
|
37496
|
+
return previous;
|
|
37497
|
+
}
|
|
37498
|
+
|
|
36821
37499
|
// node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
36822
37500
|
var ANSI_BACKGROUND_OFFSET = 10;
|
|
36823
37501
|
var wrapAnsi16 = (offset = 0) => (code) => `\x1B[${code + offset}m`;
|
|
@@ -41321,20 +41999,20 @@ class AccountsError extends Error {
|
|
|
41321
41999
|
}
|
|
41322
42000
|
|
|
41323
42001
|
// src/lib/tools.ts
|
|
41324
|
-
import { homedir as
|
|
41325
|
-
import { join as
|
|
42002
|
+
import { homedir as homedir3 } from "node:os";
|
|
42003
|
+
import { join as join3 } from "node:path";
|
|
41326
42004
|
|
|
41327
42005
|
// src/storage.ts
|
|
41328
|
-
import { homedir } from "node:os";
|
|
42006
|
+
import { homedir as homedir2 } from "node:os";
|
|
41329
42007
|
import { hostname } from "node:os";
|
|
41330
|
-
import { join } from "node:path";
|
|
41331
|
-
import { existsSync as
|
|
42008
|
+
import { join as join2 } from "node:path";
|
|
42009
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
|
|
41332
42010
|
|
|
41333
42011
|
// src/lib/safe-path.ts
|
|
41334
|
-
import { existsSync, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
42012
|
+
import { existsSync as existsSync2, lstatSync, mkdirSync, realpathSync } from "node:fs";
|
|
41335
42013
|
import { dirname, resolve } from "node:path";
|
|
41336
42014
|
function throwIfSymlink(path, label) {
|
|
41337
|
-
if (
|
|
42015
|
+
if (existsSync2(path) && lstatSync(path).isSymbolicLink()) {
|
|
41338
42016
|
throw new AccountsError(`${label}: ${path}`);
|
|
41339
42017
|
}
|
|
41340
42018
|
}
|
|
@@ -41370,11 +42048,11 @@ function assertDirChainSafe(targetFile, mustStayUnder) {
|
|
|
41370
42048
|
function assertSafeWritePath(filePath, opts) {
|
|
41371
42049
|
const absFile = resolve(filePath);
|
|
41372
42050
|
const parent = dirname(absFile);
|
|
41373
|
-
if (!
|
|
42051
|
+
if (!existsSync2(parent))
|
|
41374
42052
|
mkdirSync(parent, { recursive: true });
|
|
41375
42053
|
throwIfSymlink(absFile, "refusing to write through symlink");
|
|
41376
42054
|
assertDirChainSafe(absFile, opts?.mustStayUnder);
|
|
41377
|
-
const resolved = realpathSync(
|
|
42055
|
+
const resolved = realpathSync(existsSync2(absFile) ? absFile : parent);
|
|
41378
42056
|
if (opts?.mustStayUnder) {
|
|
41379
42057
|
const base = realpathSync(resolve(opts.mustStayUnder));
|
|
41380
42058
|
if (resolved !== base && !resolved.startsWith(base + "/")) {
|
|
@@ -41416,21 +42094,21 @@ function accountsHome() {
|
|
|
41416
42094
|
const override = process.env.ACCOUNTS_HOME;
|
|
41417
42095
|
if (override && override.trim())
|
|
41418
42096
|
return validateEnvPath(override, "ACCOUNTS_HOME");
|
|
41419
|
-
return
|
|
42097
|
+
return join2(homedir2(), ".hasna", "accounts");
|
|
41420
42098
|
}
|
|
41421
42099
|
function storePath() {
|
|
41422
42100
|
const override = process.env.ACCOUNTS_STORE_PATH;
|
|
41423
42101
|
if (override && override.trim())
|
|
41424
42102
|
return validateEnvPath(override, "ACCOUNTS_STORE_PATH");
|
|
41425
|
-
return
|
|
42103
|
+
return join2(accountsHome(), "accounts.json");
|
|
41426
42104
|
}
|
|
41427
42105
|
function profilesDir() {
|
|
41428
|
-
return
|
|
42106
|
+
return join2(accountsHome(), "profiles");
|
|
41429
42107
|
}
|
|
41430
42108
|
var EMPTY_STORE = { version: 1, current: {}, applied: {}, profiles: [], tools: [] };
|
|
41431
42109
|
function loadStore() {
|
|
41432
42110
|
const path = storePath();
|
|
41433
|
-
if (!
|
|
42111
|
+
if (!existsSync3(path))
|
|
41434
42112
|
return structuredClone(EMPTY_STORE);
|
|
41435
42113
|
let raw;
|
|
41436
42114
|
try {
|
|
@@ -41468,7 +42146,7 @@ function loadStore() {
|
|
|
41468
42146
|
function saveStore(store) {
|
|
41469
42147
|
const path = storePath();
|
|
41470
42148
|
assertSafeWritePath(path, { mustStayUnder: accountsHome() });
|
|
41471
|
-
mkdirSync2(
|
|
42149
|
+
mkdirSync2(join2(path, ".."), { recursive: true });
|
|
41472
42150
|
writeFileSync(path, JSON.stringify(store, null, 2) + `
|
|
41473
42151
|
`, { mode: 384 });
|
|
41474
42152
|
}
|
|
@@ -41616,7 +42294,7 @@ var BUILTIN_TOOLS = [
|
|
|
41616
42294
|
id: "claude",
|
|
41617
42295
|
label: "Claude Code",
|
|
41618
42296
|
envVar: "CLAUDE_CONFIG_DIR",
|
|
41619
|
-
defaultDir:
|
|
42297
|
+
defaultDir: join3(homedir3(), ".claude"),
|
|
41620
42298
|
bin: "claude",
|
|
41621
42299
|
loginHint: "run /login inside Claude, then /exit when done",
|
|
41622
42300
|
resumeArgs: ["--continue"],
|
|
@@ -41627,7 +42305,7 @@ var BUILTIN_TOOLS = [
|
|
|
41627
42305
|
id: "codex",
|
|
41628
42306
|
label: "Codex CLI",
|
|
41629
42307
|
envVar: "CODEX_HOME",
|
|
41630
|
-
defaultDir:
|
|
42308
|
+
defaultDir: join3(homedir3(), ".codex"),
|
|
41631
42309
|
bin: "codex",
|
|
41632
42310
|
loginArgs: ["login"],
|
|
41633
42311
|
loginHint: "complete the Codex login flow for this CODEX_HOME",
|
|
@@ -41637,7 +42315,7 @@ var BUILTIN_TOOLS = [
|
|
|
41637
42315
|
id: "takumi",
|
|
41638
42316
|
label: "Takumi",
|
|
41639
42317
|
envVar: "TAKUMI_CONFIG_DIR",
|
|
41640
|
-
defaultDir:
|
|
42318
|
+
defaultDir: join3(homedir3(), ".takumi"),
|
|
41641
42319
|
bin: "takumi",
|
|
41642
42320
|
loginHint: "complete Takumi auth in this TAKUMI_CONFIG_DIR",
|
|
41643
42321
|
resumeArgs: ["--continue"],
|
|
@@ -41648,7 +42326,7 @@ var BUILTIN_TOOLS = [
|
|
|
41648
42326
|
id: "gemini",
|
|
41649
42327
|
label: "Gemini CLI",
|
|
41650
42328
|
envVar: "GEMINI_CONFIG_DIR",
|
|
41651
|
-
defaultDir:
|
|
42329
|
+
defaultDir: join3(homedir3(), ".gemini"),
|
|
41652
42330
|
bin: "gemini",
|
|
41653
42331
|
loginHint: "complete Gemini auth in this GEMINI_CONFIG_DIR"
|
|
41654
42332
|
},
|
|
@@ -41660,7 +42338,7 @@ var BUILTIN_TOOLS = [
|
|
|
41660
42338
|
XDG_CONFIG_HOME: "{profileDir}/xdg-config",
|
|
41661
42339
|
XDG_DATA_HOME: "{profileDir}/xdg-data"
|
|
41662
42340
|
},
|
|
41663
|
-
defaultDir:
|
|
42341
|
+
defaultDir: join3(homedir3(), ".config", "opencode"),
|
|
41664
42342
|
bin: "opencode",
|
|
41665
42343
|
loginArgs: ["auth", "login"],
|
|
41666
42344
|
loginHint: "complete opencode auth login for this isolated config/data root",
|
|
@@ -41670,7 +42348,7 @@ var BUILTIN_TOOLS = [
|
|
|
41670
42348
|
id: "cursor",
|
|
41671
42349
|
label: "Cursor Agent",
|
|
41672
42350
|
envVar: "CURSOR_CONFIG_DIR",
|
|
41673
|
-
defaultDir:
|
|
42351
|
+
defaultDir: join3(homedir3(), ".cursor"),
|
|
41674
42352
|
bin: "cursor-agent",
|
|
41675
42353
|
loginArgs: ["login"],
|
|
41676
42354
|
loginHint: "complete cursor-agent login for this CURSOR_CONFIG_DIR"
|
|
@@ -41679,7 +42357,7 @@ var BUILTIN_TOOLS = [
|
|
|
41679
42357
|
id: "pi",
|
|
41680
42358
|
label: "Pi Coding Agent",
|
|
41681
42359
|
envVar: "PI_CODING_AGENT_HOME",
|
|
41682
|
-
defaultDir:
|
|
42360
|
+
defaultDir: join3(homedir3(), ".pi"),
|
|
41683
42361
|
bin: "pi",
|
|
41684
42362
|
loginHint: "complete Pi coding agent auth in this PI_CODING_AGENT_HOME"
|
|
41685
42363
|
},
|
|
@@ -41687,7 +42365,7 @@ var BUILTIN_TOOLS = [
|
|
|
41687
42365
|
id: "hermes",
|
|
41688
42366
|
label: "Hermes",
|
|
41689
42367
|
envVar: "HERMES_HOME",
|
|
41690
|
-
defaultDir:
|
|
42368
|
+
defaultDir: join3(homedir3(), ".hermes"),
|
|
41691
42369
|
bin: "hermes",
|
|
41692
42370
|
loginHint: "complete Hermes auth in this HERMES_HOME"
|
|
41693
42371
|
},
|
|
@@ -41695,7 +42373,7 @@ var BUILTIN_TOOLS = [
|
|
|
41695
42373
|
id: "kimi",
|
|
41696
42374
|
label: "Kimi Code",
|
|
41697
42375
|
envVar: "KIMI_CODE_HOME",
|
|
41698
|
-
defaultDir:
|
|
42376
|
+
defaultDir: join3(homedir3(), ".kimi-code"),
|
|
41699
42377
|
bin: "kimi",
|
|
41700
42378
|
loginArgs: ["login"],
|
|
41701
42379
|
loginHint: "complete kimi login for this KIMI_CODE_HOME"
|
|
@@ -41704,7 +42382,7 @@ var BUILTIN_TOOLS = [
|
|
|
41704
42382
|
id: "grok",
|
|
41705
42383
|
label: "Grok Build",
|
|
41706
42384
|
envVar: "HOME",
|
|
41707
|
-
defaultDir:
|
|
42385
|
+
defaultDir: join3(homedir3(), ".grok"),
|
|
41708
42386
|
bin: "grok",
|
|
41709
42387
|
loginArgs: ["login"],
|
|
41710
42388
|
loginHint: "complete grok login in this process-scoped HOME; prefer launch/shell over exporting HOME globally"
|
|
@@ -41765,19 +42443,19 @@ function removeCustomTool(id) {
|
|
|
41765
42443
|
}
|
|
41766
42444
|
|
|
41767
42445
|
// src/lib/profiles.ts
|
|
41768
|
-
import { homedir as
|
|
41769
|
-
import { isAbsolute, join as
|
|
41770
|
-
import { existsSync as
|
|
42446
|
+
import { homedir as homedir4 } from "node:os";
|
|
42447
|
+
import { isAbsolute, join as join5, resolve as resolve2 } from "node:path";
|
|
42448
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync3, rmSync } from "node:fs";
|
|
41771
42449
|
|
|
41772
42450
|
// src/lib/detect.ts
|
|
41773
|
-
import { existsSync as
|
|
41774
|
-
import { dirname as dirname2, join as
|
|
42451
|
+
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "node:fs";
|
|
42452
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
41775
42453
|
function detectEmail(dir, tool) {
|
|
41776
42454
|
if (!tool.accountFile || !tool.emailPath)
|
|
41777
42455
|
return;
|
|
41778
|
-
const candidates = [
|
|
42456
|
+
const candidates = [join4(dir, tool.accountFile)];
|
|
41779
42457
|
if (dir === tool.defaultDir)
|
|
41780
|
-
candidates.push(
|
|
42458
|
+
candidates.push(join4(dirname2(dir), tool.accountFile));
|
|
41781
42459
|
for (const file of candidates) {
|
|
41782
42460
|
const email = readEmail(file, tool.emailPath);
|
|
41783
42461
|
if (email)
|
|
@@ -41786,7 +42464,7 @@ function detectEmail(dir, tool) {
|
|
|
41786
42464
|
return;
|
|
41787
42465
|
}
|
|
41788
42466
|
function readEmail(file, path) {
|
|
41789
|
-
if (!
|
|
42467
|
+
if (!existsSync4(file))
|
|
41790
42468
|
return;
|
|
41791
42469
|
let cursor;
|
|
41792
42470
|
try {
|
|
@@ -41811,9 +42489,9 @@ function nowIso() {
|
|
|
41811
42489
|
function expandPath(p) {
|
|
41812
42490
|
let out = p;
|
|
41813
42491
|
if (out === "~")
|
|
41814
|
-
out =
|
|
42492
|
+
out = homedir4();
|
|
41815
42493
|
else if (out.startsWith("~/"))
|
|
41816
|
-
out =
|
|
42494
|
+
out = join5(homedir4(), out.slice(2));
|
|
41817
42495
|
return isAbsolute(out) ? out : resolve2(process.cwd(), out);
|
|
41818
42496
|
}
|
|
41819
42497
|
function listProfiles(toolId) {
|
|
@@ -41847,7 +42525,7 @@ function addProfile(opts) {
|
|
|
41847
42525
|
if (store.profiles.some((p) => p.name === name && p.tool === toolId)) {
|
|
41848
42526
|
throw new AccountsError(`a ${toolId} profile named "${name}" already exists`);
|
|
41849
42527
|
}
|
|
41850
|
-
const dir = opts.dir ? expandPath(opts.dir) :
|
|
42528
|
+
const dir = opts.dir ? expandPath(opts.dir) : join5(profilesDir(), toolId, name);
|
|
41851
42529
|
if (store.profiles.some((p) => p.dir === dir)) {
|
|
41852
42530
|
throw new AccountsError(`a profile already uses config dir ${dir}`);
|
|
41853
42531
|
}
|
|
@@ -41889,7 +42567,7 @@ function removeProfile(name, opts = {}) {
|
|
|
41889
42567
|
if (options.purge) {
|
|
41890
42568
|
const managed = profile.dir.startsWith(profilesDir());
|
|
41891
42569
|
const isDefault = profile.dir === getTool(profile.tool).defaultDir;
|
|
41892
|
-
if (managed && !isDefault &&
|
|
42570
|
+
if (managed && !isDefault && existsSync5(profile.dir)) {
|
|
41893
42571
|
rmSync(profile.dir, { recursive: true, force: true });
|
|
41894
42572
|
purged = true;
|
|
41895
42573
|
} else {
|
|
@@ -41988,12 +42666,12 @@ function currentProfile(toolId) {
|
|
|
41988
42666
|
}
|
|
41989
42667
|
|
|
41990
42668
|
// src/lib/claude-auth.ts
|
|
41991
|
-
import { copyFileSync, existsSync as
|
|
41992
|
-
import { dirname as dirname4, join as
|
|
42669
|
+
import { copyFileSync, existsSync as existsSync6, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
42670
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
41993
42671
|
|
|
41994
42672
|
// src/lib/claude-layout.ts
|
|
41995
|
-
import { homedir as
|
|
41996
|
-
import { dirname as dirname3, join as
|
|
42673
|
+
import { homedir as homedir5 } from "node:os";
|
|
42674
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
41997
42675
|
var CLAUDE_KEYCHAIN_SERVICE = "Claude Code-credentials";
|
|
41998
42676
|
var ACCOUNTS_AUTH_DIR = ".accounts-auth";
|
|
41999
42677
|
var OAUTH_SNAPSHOT = "oauth-account.json";
|
|
@@ -42001,36 +42679,36 @@ var CREDENTIALS_SNAPSHOT = "credentials.json";
|
|
|
42001
42679
|
var KEYCHAIN_SNAPSHOT = "keychain.json";
|
|
42002
42680
|
function liveClaudeBase() {
|
|
42003
42681
|
const testBase = process.env.ACCOUNTS_TEST_LIVE_DIR;
|
|
42004
|
-
return testBase && testBase.trim() ? testBase :
|
|
42682
|
+
return testBase && testBase.trim() ? testBase : homedir5();
|
|
42005
42683
|
}
|
|
42006
42684
|
function liveClaudePaths() {
|
|
42007
42685
|
const base = liveClaudeBase();
|
|
42008
|
-
const configDir =
|
|
42686
|
+
const configDir = join6(base, ".claude");
|
|
42009
42687
|
return {
|
|
42010
42688
|
configDir,
|
|
42011
|
-
homeJson:
|
|
42012
|
-
credentialsFile:
|
|
42689
|
+
homeJson: join6(base, ".claude.json"),
|
|
42690
|
+
credentialsFile: join6(configDir, ".credentials.json")
|
|
42013
42691
|
};
|
|
42014
42692
|
}
|
|
42015
42693
|
function profileAccountJsonPaths(profileDir, tool) {
|
|
42016
42694
|
if (!tool.accountFile)
|
|
42017
42695
|
return [];
|
|
42018
|
-
const paths = [
|
|
42696
|
+
const paths = [join6(profileDir, tool.accountFile)];
|
|
42019
42697
|
if (profileDir === tool.defaultDir)
|
|
42020
|
-
paths.push(
|
|
42698
|
+
paths.push(join6(dirname3(profileDir), tool.accountFile));
|
|
42021
42699
|
return paths;
|
|
42022
42700
|
}
|
|
42023
42701
|
function profileAuthDir(profileDir) {
|
|
42024
|
-
return
|
|
42702
|
+
return join6(profileDir, ACCOUNTS_AUTH_DIR);
|
|
42025
42703
|
}
|
|
42026
42704
|
function profileOAuthSnapshot(profileDir) {
|
|
42027
|
-
return
|
|
42705
|
+
return join6(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
|
|
42028
42706
|
}
|
|
42029
42707
|
function profileCredentialsSnapshot(profileDir) {
|
|
42030
|
-
return
|
|
42708
|
+
return join6(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
|
|
42031
42709
|
}
|
|
42032
42710
|
function profileKeychainSnapshot(profileDir) {
|
|
42033
|
-
return
|
|
42711
|
+
return join6(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
|
|
42034
42712
|
}
|
|
42035
42713
|
|
|
42036
42714
|
// src/lib/keychain.ts
|
|
@@ -42095,7 +42773,7 @@ function writeClaudeKeychain(cred) {
|
|
|
42095
42773
|
|
|
42096
42774
|
// src/lib/claude-auth.ts
|
|
42097
42775
|
function readJsonFile(path) {
|
|
42098
|
-
if (!
|
|
42776
|
+
if (!existsSync6(path))
|
|
42099
42777
|
return;
|
|
42100
42778
|
try {
|
|
42101
42779
|
return JSON.parse(readFileSync3(path, "utf8"));
|
|
@@ -42122,7 +42800,7 @@ function findOAuthSource(paths) {
|
|
|
42122
42800
|
return;
|
|
42123
42801
|
}
|
|
42124
42802
|
function snapshotIsStale(sourcePath, snapshotPath) {
|
|
42125
|
-
if (!
|
|
42803
|
+
if (!existsSync6(snapshotPath))
|
|
42126
42804
|
return true;
|
|
42127
42805
|
try {
|
|
42128
42806
|
return statSync(sourcePath).mtimeMs > statSync(snapshotPath).mtimeMs;
|
|
@@ -42161,13 +42839,13 @@ function liveOAuthEmail() {
|
|
|
42161
42839
|
}
|
|
42162
42840
|
function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
42163
42841
|
const authDir = profileAuthDir(profileDir);
|
|
42164
|
-
assertSafeWritePath(
|
|
42842
|
+
assertSafeWritePath(join7(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
42165
42843
|
mkdirSync4(authDir, { recursive: true });
|
|
42166
42844
|
const live = liveClaudePaths();
|
|
42167
42845
|
const oauth = readOAuthFromPaths([live.homeJson]);
|
|
42168
42846
|
if (oauth)
|
|
42169
42847
|
writeJsonFile(profileOAuthSnapshot(profileDir), { oauthAccount: oauth }, profileDir);
|
|
42170
|
-
if (
|
|
42848
|
+
if (existsSync6(live.credentialsFile)) {
|
|
42171
42849
|
const dest = profileCredentialsSnapshot(profileDir);
|
|
42172
42850
|
assertSafeWritePath(dest, { mustStayUnder: profileDir });
|
|
42173
42851
|
copyFileSync(live.credentialsFile, dest);
|
|
@@ -42180,16 +42858,16 @@ function snapshotLiveAuthToProfile(profileDir, _tool) {
|
|
|
42180
42858
|
}
|
|
42181
42859
|
function ensureProfileAuthSnapshot(profileDir, tool, opts = {}) {
|
|
42182
42860
|
const authDir = profileAuthDir(profileDir);
|
|
42183
|
-
assertSafeWritePath(
|
|
42861
|
+
assertSafeWritePath(join7(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
|
|
42184
42862
|
mkdirSync4(authDir, { recursive: true });
|
|
42185
42863
|
const oauthSource = findOAuthSource(profileAccountJsonPaths(profileDir, tool));
|
|
42186
42864
|
const oauthSnap = profileOAuthSnapshot(profileDir);
|
|
42187
42865
|
if (oauthSource && (opts.overwrite || snapshotIsStale(oauthSource.path, oauthSnap))) {
|
|
42188
42866
|
writeJsonFile(oauthSnap, { oauthAccount: oauthSource.oauth }, profileDir);
|
|
42189
42867
|
}
|
|
42190
|
-
const credFile =
|
|
42868
|
+
const credFile = join7(profileDir, ".credentials.json");
|
|
42191
42869
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
42192
|
-
if (
|
|
42870
|
+
if (existsSync6(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
|
|
42193
42871
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
42194
42872
|
copyFileSync(credFile, credSnap);
|
|
42195
42873
|
}
|
|
@@ -42214,12 +42892,12 @@ function restoreClaudeAuthFromProfile(profileDir, tool, profileName) {
|
|
|
42214
42892
|
assertSafeWritePath(live.homeJson, { mustStayUnder: liveRoot });
|
|
42215
42893
|
mergeOAuthInto([live.homeJson], oauth, false, liveRoot);
|
|
42216
42894
|
const credSnap = profileCredentialsSnapshot(profileDir);
|
|
42217
|
-
if (
|
|
42895
|
+
if (existsSync6(credSnap)) {
|
|
42218
42896
|
assertSafeWritePath(live.credentialsFile, { mustStayUnder: liveRoot });
|
|
42219
42897
|
assertSafeWritePath(credSnap, { mustStayUnder: profileDir });
|
|
42220
42898
|
copyFileSync(credSnap, live.credentialsFile);
|
|
42221
42899
|
writeFileSync2(live.credentialsFile, readFileSync3(live.credentialsFile), { mode: 384 });
|
|
42222
|
-
} else if (
|
|
42900
|
+
} else if (existsSync6(live.credentialsFile)) {
|
|
42223
42901
|
if (!lstatSync2(live.credentialsFile).isSymbolicLink())
|
|
42224
42902
|
unlinkSync(live.credentialsFile);
|
|
42225
42903
|
}
|
|
@@ -42241,14 +42919,14 @@ function restoreClaudeAuthFromProfile(profileDir, tool, profileName) {
|
|
|
42241
42919
|
}
|
|
42242
42920
|
}
|
|
42243
42921
|
function hasAuthSnapshot(profileDir) {
|
|
42244
|
-
return
|
|
42922
|
+
return existsSync6(profileOAuthSnapshot(profileDir)) || existsSync6(profileCredentialsSnapshot(profileDir)) || existsSync6(profileKeychainSnapshot(profileDir));
|
|
42245
42923
|
}
|
|
42246
42924
|
|
|
42247
42925
|
// src/lib/apply-lock.ts
|
|
42248
|
-
import { closeSync, existsSync as
|
|
42249
|
-
import { join as
|
|
42926
|
+
import { closeSync, existsSync as existsSync7, mkdirSync as mkdirSync5, openSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
|
|
42927
|
+
import { join as join8 } from "node:path";
|
|
42250
42928
|
function lockPath() {
|
|
42251
|
-
return
|
|
42929
|
+
return join8(accountsHome(), ".apply.lock");
|
|
42252
42930
|
}
|
|
42253
42931
|
function withApplyLock(fn) {
|
|
42254
42932
|
const home = accountsHome();
|
|
@@ -42273,7 +42951,7 @@ function withApplyLock(fn) {
|
|
|
42273
42951
|
if (fd !== undefined) {
|
|
42274
42952
|
closeSync(fd);
|
|
42275
42953
|
try {
|
|
42276
|
-
if (
|
|
42954
|
+
if (existsSync7(path))
|
|
42277
42955
|
unlinkSync2(path);
|
|
42278
42956
|
} catch {}
|
|
42279
42957
|
}
|
|
@@ -42319,6 +42997,7 @@ function applyProfileUnlocked(name, toolId) {
|
|
|
42319
42997
|
|
|
42320
42998
|
// src/lib/agents.ts
|
|
42321
42999
|
import { spawnSync } from "node:child_process";
|
|
43000
|
+
import { existsSync as existsSync8, readFileSync as readFileSync4 } from "node:fs";
|
|
42322
43001
|
import { platform as platform2 } from "node:os";
|
|
42323
43002
|
function extractJsonArray(raw) {
|
|
42324
43003
|
const text = raw.replace(/\r/g, "");
|
|
@@ -42378,11 +43057,79 @@ function runClaudeAgentsJson(profile, timeoutMs = 20000) {
|
|
|
42378
43057
|
}
|
|
42379
43058
|
return { ok: true, raw: res.stdout ?? "" };
|
|
42380
43059
|
}
|
|
43060
|
+
function isToolSessionCommand(command, bin) {
|
|
43061
|
+
const argv = command.trim().split(/\s+/);
|
|
43062
|
+
if (argv.length === 0)
|
|
43063
|
+
return false;
|
|
43064
|
+
const base = (p) => p.split("/").pop() ?? p;
|
|
43065
|
+
let head = argv[0] ?? "";
|
|
43066
|
+
let rest = argv.slice(1);
|
|
43067
|
+
if ((base(head) === "node" || base(head) === "bun") && rest[0]) {
|
|
43068
|
+
head = rest[0];
|
|
43069
|
+
rest = rest.slice(1);
|
|
43070
|
+
}
|
|
43071
|
+
const isVersionedBuild = head.includes(`/${bin}/versions/`);
|
|
43072
|
+
if (base(head) !== bin && !isVersionedBuild)
|
|
43073
|
+
return false;
|
|
43074
|
+
if (rest[0] === "agents")
|
|
43075
|
+
return false;
|
|
43076
|
+
const joined = rest.join(" ");
|
|
43077
|
+
if (/--bg-pty-host|--bg-spare/.test(joined))
|
|
43078
|
+
return false;
|
|
43079
|
+
if (rest[0] === "daemon")
|
|
43080
|
+
return false;
|
|
43081
|
+
return true;
|
|
43082
|
+
}
|
|
43083
|
+
function scanToolProcesses(toolId = "claude") {
|
|
43084
|
+
const tool = getTool(toolId);
|
|
43085
|
+
const bin = tool.bin ?? toolId;
|
|
43086
|
+
const res = spawnSync("ps", ["-axo", "pid=,ppid=,args="], {
|
|
43087
|
+
encoding: "utf8",
|
|
43088
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
43089
|
+
});
|
|
43090
|
+
if (res.status !== 0 || !res.stdout)
|
|
43091
|
+
return [];
|
|
43092
|
+
const out = [];
|
|
43093
|
+
for (const line of res.stdout.split(`
|
|
43094
|
+
`)) {
|
|
43095
|
+
const m = line.match(/^\s*(\d+)\s+(\d+)\s+(.+)$/);
|
|
43096
|
+
if (!m)
|
|
43097
|
+
continue;
|
|
43098
|
+
const pid = Number(m[1]);
|
|
43099
|
+
const ppid = Number(m[2]);
|
|
43100
|
+
const command = m[3].trim();
|
|
43101
|
+
if (pid === process.pid)
|
|
43102
|
+
continue;
|
|
43103
|
+
if (!isToolSessionCommand(command, bin))
|
|
43104
|
+
continue;
|
|
43105
|
+
const configDir = readProcessEnvVar(pid, tool.envVar);
|
|
43106
|
+
out.push({ pid, ppid, command, ...configDir ? { configDir } : {} });
|
|
43107
|
+
}
|
|
43108
|
+
return out;
|
|
43109
|
+
}
|
|
43110
|
+
function readProcessEnvVar(pid, envVar) {
|
|
43111
|
+
try {
|
|
43112
|
+
const environ = readFileSync4(`/proc/${pid}/environ`, "utf8");
|
|
43113
|
+
for (const kv of environ.split("\x00")) {
|
|
43114
|
+
if (kv.startsWith(`${envVar}=`))
|
|
43115
|
+
return kv.slice(envVar.length + 1) || undefined;
|
|
43116
|
+
}
|
|
43117
|
+
} catch {}
|
|
43118
|
+
return;
|
|
43119
|
+
}
|
|
42381
43120
|
function listAgentsAcrossProfiles(opts = {}) {
|
|
42382
43121
|
const toolId = opts.tool ?? "claude";
|
|
42383
43122
|
const runner = opts.runner ?? runClaudeAgentsJson;
|
|
42384
|
-
const
|
|
42385
|
-
|
|
43123
|
+
const tool = getTool(toolId);
|
|
43124
|
+
const registered = listProfiles(toolId);
|
|
43125
|
+
const entries = [...registered];
|
|
43126
|
+
const defaultDir = opts.defaultDir ?? tool.defaultDir;
|
|
43127
|
+
if (defaultDir && !registered.some((p) => p.dir === defaultDir) && existsSync8(defaultDir)) {
|
|
43128
|
+
entries.unshift({ name: "(default)", tool: toolId, dir: defaultDir });
|
|
43129
|
+
}
|
|
43130
|
+
const wanted = entries.filter((p) => !opts.profile || p.name === opts.profile || opts.profile === "default" && p.name === "(default)");
|
|
43131
|
+
const reported = new Set;
|
|
43132
|
+
const results = wanted.map((profile) => {
|
|
42386
43133
|
const base = {
|
|
42387
43134
|
profile: profile.name,
|
|
42388
43135
|
tool: profile.tool,
|
|
@@ -42396,25 +43143,50 @@ function listAgentsAcrossProfiles(opts = {}) {
|
|
|
42396
43143
|
const parsed = extractJsonArray(result.raw);
|
|
42397
43144
|
if (!parsed)
|
|
42398
43145
|
return { ...base, error: "could not parse agents output" };
|
|
43146
|
+
for (const a of parsed) {
|
|
43147
|
+
if (typeof a.pid === "number")
|
|
43148
|
+
reported.add(a.pid);
|
|
43149
|
+
}
|
|
42399
43150
|
const agents = parsed.filter((a) => !opts.backgroundOnly || a.kind === "background");
|
|
42400
43151
|
return { ...base, agents };
|
|
42401
43152
|
});
|
|
43153
|
+
if (!opts.profile) {
|
|
43154
|
+
const scanner = opts.processScanner ?? scanToolProcesses;
|
|
43155
|
+
const scanned = scanner();
|
|
43156
|
+
if (scanned.length > 0) {
|
|
43157
|
+
const untracked = scanned.filter((p) => !reported.has(p.pid) && !reported.has(p.ppid) && !scanned.some((q) => q.ppid === p.pid && reported.has(q.pid)));
|
|
43158
|
+
if (untracked.length > 0) {
|
|
43159
|
+
results.push({
|
|
43160
|
+
profile: "(untracked)",
|
|
43161
|
+
tool: toolId,
|
|
43162
|
+
dir: "",
|
|
43163
|
+
agents: untracked.map((p) => ({
|
|
43164
|
+
kind: "process",
|
|
43165
|
+
pid: p.pid,
|
|
43166
|
+
command: p.command,
|
|
43167
|
+
...p.configDir ? { configDir: p.configDir } : {}
|
|
43168
|
+
}))
|
|
43169
|
+
});
|
|
43170
|
+
}
|
|
43171
|
+
}
|
|
43172
|
+
}
|
|
43173
|
+
return results;
|
|
42402
43174
|
}
|
|
42403
43175
|
|
|
42404
43176
|
// src/lib/import-profile.ts
|
|
42405
|
-
import { cpSync, existsSync as
|
|
42406
|
-
import { join as
|
|
43177
|
+
import { cpSync, existsSync as existsSync9 } from "node:fs";
|
|
43178
|
+
import { join as join9 } from "node:path";
|
|
42407
43179
|
function importProfile(opts) {
|
|
42408
43180
|
const toolId = opts.tool ?? DEFAULT_TOOL;
|
|
42409
43181
|
const tool = getTool(toolId);
|
|
42410
43182
|
const name = opts.name ?? "main";
|
|
42411
43183
|
const sourceDir = opts.dir ? expandPath(opts.dir) : tool.defaultDir;
|
|
42412
|
-
if (!
|
|
43184
|
+
if (!existsSync9(sourceDir)) {
|
|
42413
43185
|
throw new AccountsError(`config dir does not exist: ${sourceDir}`);
|
|
42414
43186
|
}
|
|
42415
43187
|
if (opts.copy) {
|
|
42416
|
-
const targetDir =
|
|
42417
|
-
if (
|
|
43188
|
+
const targetDir = join9(profilesDir(), toolId, name);
|
|
43189
|
+
if (existsSync9(targetDir)) {
|
|
42418
43190
|
throw new AccountsError(`managed copy target already exists: ${targetDir}`);
|
|
42419
43191
|
}
|
|
42420
43192
|
cpSync(sourceDir, targetDir, { recursive: true });
|
|
@@ -42498,8 +43270,8 @@ async function pickProfile(opts = {}) {
|
|
|
42498
43270
|
}
|
|
42499
43271
|
|
|
42500
43272
|
// src/lib/hook.ts
|
|
42501
|
-
import { existsSync as
|
|
42502
|
-
import { join as
|
|
43273
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync6, readFileSync as readFileSync5, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
|
|
43274
|
+
import { join as join10 } from "node:path";
|
|
42503
43275
|
var HOOK_FILE = "claude-hook.sh";
|
|
42504
43276
|
var MARKER = "# accounts-claude-hook";
|
|
42505
43277
|
var NAME_PATTERN = "^[a-z0-9][a-z0-9-]*$";
|
|
@@ -42507,7 +43279,7 @@ function shellQuotePath(path) {
|
|
|
42507
43279
|
return `'${path.replace(/'/g, `'\\''`)}'`;
|
|
42508
43280
|
}
|
|
42509
43281
|
function hookPath() {
|
|
42510
|
-
return
|
|
43282
|
+
return join10(accountsHome(), HOOK_FILE);
|
|
42511
43283
|
}
|
|
42512
43284
|
function hookScript() {
|
|
42513
43285
|
const quotedHook = shellQuotePath(hookPath());
|
|
@@ -42538,15 +43310,15 @@ claude() {
|
|
|
42538
43310
|
function installHook() {
|
|
42539
43311
|
const path = hookPath();
|
|
42540
43312
|
mkdirSync6(accountsHome(), { recursive: true });
|
|
42541
|
-
const created = !
|
|
43313
|
+
const created = !existsSync10(path);
|
|
42542
43314
|
writeFileSync4(path, hookScript(), { mode: 493 });
|
|
42543
43315
|
return { path, created };
|
|
42544
43316
|
}
|
|
42545
43317
|
function uninstallHook() {
|
|
42546
43318
|
const path = hookPath();
|
|
42547
|
-
if (!
|
|
43319
|
+
if (!existsSync10(path))
|
|
42548
43320
|
return false;
|
|
42549
|
-
const content =
|
|
43321
|
+
const content = readFileSync5(path, "utf8");
|
|
42550
43322
|
if (!content.includes(MARKER))
|
|
42551
43323
|
return false;
|
|
42552
43324
|
unlinkSync3(path);
|
|
@@ -42636,24 +43408,24 @@ function switchProfile(name, opts = {}) {
|
|
|
42636
43408
|
}
|
|
42637
43409
|
|
|
42638
43410
|
// src/lib/supervisor.ts
|
|
42639
|
-
import { spawn } from "node:child_process";
|
|
43411
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
42640
43412
|
import { createHash } from "node:crypto";
|
|
42641
|
-
import { existsSync as
|
|
43413
|
+
import { existsSync as existsSync11, mkdirSync as mkdirSync7, readFileSync as readFileSync6, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
|
|
42642
43414
|
import { createConnection, createServer } from "node:net";
|
|
42643
|
-
import { basename, join as
|
|
43415
|
+
import { basename, join as join11 } from "node:path";
|
|
42644
43416
|
var STATE_SUFFIX = ".json";
|
|
42645
43417
|
function supervisorDir() {
|
|
42646
|
-
return
|
|
43418
|
+
return join11(accountsHome(), "supervisors");
|
|
42647
43419
|
}
|
|
42648
43420
|
function supervisorStatePath(toolId) {
|
|
42649
|
-
return
|
|
43421
|
+
return join11(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
|
|
42650
43422
|
}
|
|
42651
43423
|
function supervisorSocketPath(toolId) {
|
|
42652
43424
|
if (process.platform === "win32") {
|
|
42653
43425
|
const hash = createHash("sha1").update(accountsHome()).digest("hex").slice(0, 12);
|
|
42654
43426
|
return `\\\\.\\pipe\\hasna-accounts-${hash}-${toolId}`;
|
|
42655
43427
|
}
|
|
42656
|
-
return
|
|
43428
|
+
return join11(supervisorDir(), `${toolId}.sock`);
|
|
42657
43429
|
}
|
|
42658
43430
|
function nowIso2() {
|
|
42659
43431
|
return new Date().toISOString();
|
|
@@ -42667,17 +43439,17 @@ function parseState(raw) {
|
|
|
42667
43439
|
}
|
|
42668
43440
|
function readSupervisorState(toolId) {
|
|
42669
43441
|
const path = supervisorStatePath(toolId);
|
|
42670
|
-
if (!
|
|
43442
|
+
if (!existsSync11(path))
|
|
42671
43443
|
return;
|
|
42672
43444
|
try {
|
|
42673
|
-
return parseState(
|
|
43445
|
+
return parseState(readFileSync6(path, "utf8"));
|
|
42674
43446
|
} catch {
|
|
42675
43447
|
return;
|
|
42676
43448
|
}
|
|
42677
43449
|
}
|
|
42678
43450
|
function listSupervisorStates() {
|
|
42679
43451
|
const dir = supervisorDir();
|
|
42680
|
-
if (!
|
|
43452
|
+
if (!existsSync11(dir))
|
|
42681
43453
|
return [];
|
|
42682
43454
|
return readdirSync(dir).filter((name) => name.endsWith(STATE_SUFFIX)).map((name) => basename(name, STATE_SUFFIX)).map((toolId) => readSupervisorState(toolId)).filter((state) => state !== undefined);
|
|
42683
43455
|
}
|
|
@@ -42900,7 +43672,7 @@ async function runSupervisedTool(initialProfile, tool, initialArgs = [], opts =
|
|
|
42900
43672
|
useProfile(profile.name, tool.id);
|
|
42901
43673
|
const env2 = profileEnv(profile, tool);
|
|
42902
43674
|
log(`accounts supervisor: starting ${tool.bin} for ${profile.name}`);
|
|
42903
|
-
const proc =
|
|
43675
|
+
const proc = spawn2(tool.bin, childArgs, {
|
|
42904
43676
|
stdio: opts.stdio ?? "inherit",
|
|
42905
43677
|
env: { ...process.env, ...env2, ACCOUNTS_SUPERVISOR: "1", ACCOUNTS_ACTIVE: profile.name },
|
|
42906
43678
|
detached: process.platform !== "win32"
|
|
@@ -43084,7 +43856,7 @@ program2.command("show").argument("<name>", "profile name").description("show fu
|
|
|
43084
43856
|
console.log(` tool: ${p.tool} (${getTool(p.tool).label})`);
|
|
43085
43857
|
console.log(` active: ${active ? source_default.green("yes") : source_default.dim("no")}`);
|
|
43086
43858
|
console.log(` applied: ${isApplied ? source_default.magenta("yes") : source_default.dim("no")}`);
|
|
43087
|
-
console.log(` config dir: ${p.dir}${
|
|
43859
|
+
console.log(` config dir: ${p.dir}${existsSync12(p.dir) ? "" : source_default.red(" [missing]")}`);
|
|
43088
43860
|
console.log(` email: ${p.email ?? source_default.dim("(none)")}`);
|
|
43089
43861
|
console.log(` created: ${p.createdAt}`);
|
|
43090
43862
|
if (p.lastUsedAt)
|
|
@@ -43222,7 +43994,7 @@ program2.command("switch").argument("<name>", "profile name").argument("[args...
|
|
|
43222
43994
|
}
|
|
43223
43995
|
}));
|
|
43224
43996
|
var hook = program2.command("hook").description("install a shell wrapper for claude");
|
|
43225
|
-
hook.command("install").description(`write ${
|
|
43997
|
+
hook.command("install").description(`write ${join12(accountsHome(), "claude-hook.sh")}`).action(action(() => {
|
|
43226
43998
|
const { path, created } = installHook();
|
|
43227
43999
|
console.log(source_default.green(created ? `✓ installed hook at ${path}` : `✓ updated hook at ${path}`));
|
|
43228
44000
|
console.log(source_default.dim(` add to ~/.zshrc: ${shellSnippet()}`));
|
|
@@ -43366,6 +44138,12 @@ program2.command("agents").description("list Claude Code agent sessions (interac
|
|
|
43366
44138
|
continue;
|
|
43367
44139
|
}
|
|
43368
44140
|
for (const a of r.agents) {
|
|
44141
|
+
if (a.kind === "process") {
|
|
44142
|
+
const cfg = typeof a.configDir === "string" ? source_default.dim(` cfg=${a.configDir}`) : "";
|
|
44143
|
+
const cmd = typeof a.command === "string" ? source_default.dim(` ${a.command.slice(0, 100)}`) : "";
|
|
44144
|
+
console.log(` ${source_default.yellow("process ")} pid ${a.pid}${cfg}${cmd}`);
|
|
44145
|
+
continue;
|
|
44146
|
+
}
|
|
43369
44147
|
const kind = a.kind === "background" ? source_default.magenta("background ") : source_default.dim("interactive");
|
|
43370
44148
|
const state = String(a.state ?? a.status ?? "");
|
|
43371
44149
|
const stateFmt = state === "working" || state === "busy" ? source_default.green(state) : source_default.dim(state);
|
|
@@ -43441,7 +44219,7 @@ tools.command("add").argument("<id>", "tool id, e.g. cursor").description("regis
|
|
|
43441
44219
|
label: opts.label,
|
|
43442
44220
|
envVar: opts.envVar,
|
|
43443
44221
|
bin: opts.bin,
|
|
43444
|
-
defaultDir: opts.defaultDir ? expandPath(opts.defaultDir) :
|
|
44222
|
+
defaultDir: opts.defaultDir ? expandPath(opts.defaultDir) : join12(homedir6(), `.${id}`),
|
|
43445
44223
|
...Object.keys(extraEnv).length > 0 ? { extraEnv } : {},
|
|
43446
44224
|
...opts.loginArg ? { loginArgs: opts.loginArg } : {},
|
|
43447
44225
|
...opts.resumeArg ? { resumeArgs: opts.resumeArg } : {},
|
|
@@ -43462,7 +44240,7 @@ program2.command("doctor").description("check the store and profile dirs for pro
|
|
|
43462
44240
|
const profiles = listProfiles();
|
|
43463
44241
|
let problems = 0;
|
|
43464
44242
|
for (const p of profiles) {
|
|
43465
|
-
const missing = !
|
|
44243
|
+
const missing = !existsSync12(p.dir);
|
|
43466
44244
|
const noEmail = !p.email;
|
|
43467
44245
|
if (missing) {
|
|
43468
44246
|
console.log(source_default.red(` ✗ ${p.name}: config dir missing (${p.dir})`));
|
|
@@ -43506,13 +44284,14 @@ ${problems} problem(s) found.`));
|
|
|
43506
44284
|
console.log(source_default.green(`
|
|
43507
44285
|
healthy.`));
|
|
43508
44286
|
}));
|
|
44287
|
+
registerEventsCommands(program2, { source: "accounts" });
|
|
43509
44288
|
program2.parseAsync(process.argv);
|
|
43510
44289
|
function getVersion() {
|
|
43511
44290
|
try {
|
|
43512
44291
|
const here = dirname5(fileURLToPath(import.meta.url));
|
|
43513
|
-
for (const candidate of [
|
|
43514
|
-
if (
|
|
43515
|
-
const pkg = JSON.parse(
|
|
44292
|
+
for (const candidate of [join12(here, "..", "package.json"), join12(here, "package.json")]) {
|
|
44293
|
+
if (existsSync12(candidate)) {
|
|
44294
|
+
const pkg = JSON.parse(readFileSync7(candidate, "utf8"));
|
|
43516
44295
|
if (pkg.version)
|
|
43517
44296
|
return pkg.version;
|
|
43518
44297
|
}
|