@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/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
- } else {}
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 readFile = (path, options) => {
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
- readFile(resolvedConfigFilepath, {
4333
+ readFile2(resolvedConfigFilepath, {
4334
4334
  ignoreCache: init.ignoreCache
4335
4335
  }).then(parseIni).then(getConfigData).catch(swallowError$1),
4336
- readFile(resolvedFilepath, {
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 = {}) => readFile(init.configFilepath ?? getConfigFilepath()).then(parseIni).then(getSsoSessionData).catch(swallowError);
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 = 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(parseNumber(value));
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(parseNumber(value));
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 parseNumber = (value) => {
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(parseNumber(value));
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(parseNumber(value));
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(parseNumber(value));
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(parseNumber(value));
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 = "/home/hasna/workspace/hasna/opensource/open-accounts/node_modules/@aws-sdk/core/dist-cjs/submodules/client";
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(now) {
14157
- const longDate = iso8601(now).replace(/[\-:]/g, "");
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 now = Date.now();
19959
- if (this.lastPurgeTime + S3ExpressIdentityCache.EXPIRED_CREDENTIAL_PURGE_INTERVAL_MS > now) {
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() < now) {
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 writeFile(tokenFilepath, tokenString);
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 now = Date.now();
32367
+ const now2 = Date.now();
32368
32368
  const expiryTime = new Date(accessToken.expiresAt).getTime();
32369
- const timeUntilExpiry = expiryTime - now;
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 existsSync10, readFileSync as readFileSync6 } from "node:fs";
36801
- import { homedir as homedir5 } from "node:os";
36802
- import { dirname as dirname5, join as join11 } from "node:path";
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 homedir2 } from "node:os";
41325
- import { join as join2 } from "node:path";
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 existsSync2, mkdirSync as mkdirSync2, readFileSync, writeFileSync } from "node:fs";
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 (existsSync(path) && lstatSync(path).isSymbolicLink()) {
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 (!existsSync(parent))
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(existsSync(absFile) ? absFile : parent);
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 join(homedir(), ".hasna", "accounts");
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 join(accountsHome(), "accounts.json");
42103
+ return join2(accountsHome(), "accounts.json");
41426
42104
  }
41427
42105
  function profilesDir() {
41428
- return join(accountsHome(), "profiles");
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 (!existsSync2(path))
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(join(path, ".."), { recursive: true });
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: join2(homedir2(), ".claude"),
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: join2(homedir2(), ".codex"),
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: join2(homedir2(), ".takumi"),
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: join2(homedir2(), ".gemini"),
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: join2(homedir2(), ".config", "opencode"),
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: join2(homedir2(), ".cursor"),
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: join2(homedir2(), ".pi"),
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: join2(homedir2(), ".hermes"),
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: join2(homedir2(), ".kimi-code"),
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: join2(homedir2(), ".grok"),
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 homedir3 } from "node:os";
41769
- import { isAbsolute, join as join4, resolve as resolve2 } from "node:path";
41770
- import { existsSync as existsSync4, mkdirSync as mkdirSync3, rmSync } from "node:fs";
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 existsSync3, readFileSync as readFileSync2 } from "node:fs";
41774
- import { dirname as dirname2, join as join3 } from "node:path";
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 = [join3(dir, tool.accountFile)];
42456
+ const candidates = [join4(dir, tool.accountFile)];
41779
42457
  if (dir === tool.defaultDir)
41780
- candidates.push(join3(dirname2(dir), tool.accountFile));
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 (!existsSync3(file))
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 = homedir3();
42492
+ out = homedir4();
41815
42493
  else if (out.startsWith("~/"))
41816
- out = join4(homedir3(), out.slice(2));
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) : join4(profilesDir(), toolId, name);
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 && existsSync4(profile.dir)) {
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 existsSync5, lstatSync as lstatSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync3, statSync, unlinkSync, writeFileSync as writeFileSync2 } from "node:fs";
41992
- import { dirname as dirname4, join as join6 } from "node:path";
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 homedir4 } from "node:os";
41996
- import { dirname as dirname3, join as join5 } from "node:path";
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 : homedir4();
42682
+ return testBase && testBase.trim() ? testBase : homedir5();
42005
42683
  }
42006
42684
  function liveClaudePaths() {
42007
42685
  const base = liveClaudeBase();
42008
- const configDir = join5(base, ".claude");
42686
+ const configDir = join6(base, ".claude");
42009
42687
  return {
42010
42688
  configDir,
42011
- homeJson: join5(base, ".claude.json"),
42012
- credentialsFile: join5(configDir, ".credentials.json")
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 = [join5(profileDir, tool.accountFile)];
42696
+ const paths = [join6(profileDir, tool.accountFile)];
42019
42697
  if (profileDir === tool.defaultDir)
42020
- paths.push(join5(dirname3(profileDir), tool.accountFile));
42698
+ paths.push(join6(dirname3(profileDir), tool.accountFile));
42021
42699
  return paths;
42022
42700
  }
42023
42701
  function profileAuthDir(profileDir) {
42024
- return join5(profileDir, ACCOUNTS_AUTH_DIR);
42702
+ return join6(profileDir, ACCOUNTS_AUTH_DIR);
42025
42703
  }
42026
42704
  function profileOAuthSnapshot(profileDir) {
42027
- return join5(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
42705
+ return join6(profileAuthDir(profileDir), OAUTH_SNAPSHOT);
42028
42706
  }
42029
42707
  function profileCredentialsSnapshot(profileDir) {
42030
- return join5(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
42708
+ return join6(profileAuthDir(profileDir), CREDENTIALS_SNAPSHOT);
42031
42709
  }
42032
42710
  function profileKeychainSnapshot(profileDir) {
42033
- return join5(profileAuthDir(profileDir), KEYCHAIN_SNAPSHOT);
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 (!existsSync5(path))
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 (!existsSync5(snapshotPath))
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(join6(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
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 (existsSync5(live.credentialsFile)) {
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(join6(authDir, OAUTH_SNAPSHOT), { mustStayUnder: profileDir });
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 = join6(profileDir, ".credentials.json");
42868
+ const credFile = join7(profileDir, ".credentials.json");
42191
42869
  const credSnap = profileCredentialsSnapshot(profileDir);
42192
- if (existsSync5(credFile) && (opts.overwrite || snapshotIsStale(credFile, credSnap))) {
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 (existsSync5(credSnap)) {
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 (existsSync5(live.credentialsFile)) {
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 existsSync5(profileOAuthSnapshot(profileDir)) || existsSync5(profileCredentialsSnapshot(profileDir)) || existsSync5(profileKeychainSnapshot(profileDir));
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 existsSync6, mkdirSync as mkdirSync5, openSync, unlinkSync as unlinkSync2, writeFileSync as writeFileSync3 } from "node:fs";
42249
- import { join as join7 } from "node:path";
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 join7(accountsHome(), ".apply.lock");
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 (existsSync6(path))
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 profiles = listProfiles(toolId).filter((p) => !opts.profile || p.name === opts.profile);
42385
- return profiles.map((profile) => {
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 existsSync7 } from "node:fs";
42406
- import { join as join8 } from "node:path";
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 (!existsSync7(sourceDir)) {
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 = join8(profilesDir(), toolId, name);
42417
- if (existsSync7(targetDir)) {
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 existsSync8, mkdirSync as mkdirSync6, readFileSync as readFileSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync4 } from "node:fs";
42502
- import { join as join9 } from "node:path";
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 join9(accountsHome(), HOOK_FILE);
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 = !existsSync8(path);
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 (!existsSync8(path))
43319
+ if (!existsSync10(path))
42548
43320
  return false;
42549
- const content = readFileSync4(path, "utf8");
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 existsSync9, mkdirSync as mkdirSync7, readFileSync as readFileSync5, readdirSync, rmSync as rmSync2, writeFileSync as writeFileSync5 } from "node:fs";
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 join10 } from "node:path";
43415
+ import { basename, join as join11 } from "node:path";
42644
43416
  var STATE_SUFFIX = ".json";
42645
43417
  function supervisorDir() {
42646
- return join10(accountsHome(), "supervisors");
43418
+ return join11(accountsHome(), "supervisors");
42647
43419
  }
42648
43420
  function supervisorStatePath(toolId) {
42649
- return join10(supervisorDir(), `${toolId}${STATE_SUFFIX}`);
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 join10(supervisorDir(), `${toolId}.sock`);
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 (!existsSync9(path))
43442
+ if (!existsSync11(path))
42671
43443
  return;
42672
43444
  try {
42673
- return parseState(readFileSync5(path, "utf8"));
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 (!existsSync9(dir))
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 = spawn(tool.bin, childArgs, {
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}${existsSync10(p.dir) ? "" : source_default.red(" [missing]")}`);
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 ${join11(accountsHome(), "claude-hook.sh")}`).action(action(() => {
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) : join11(homedir5(), `.${id}`),
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 = !existsSync10(p.dir);
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 [join11(here, "..", "package.json"), join11(here, "package.json")]) {
43514
- if (existsSync10(candidate)) {
43515
- const pkg = JSON.parse(readFileSync6(candidate, "utf8"));
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
  }