@hasna/recordings 0.1.34 → 0.1.36
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 +17 -0
- package/dist/cli/index.js +328 -95
- package/dist/db/storage-config.d.ts +2 -1
- package/dist/db/storage-config.d.ts.map +1 -1
- package/dist/db/storage-sync.d.ts.map +1 -1
- package/dist/index.js +79 -57
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/mcp/index.js +85 -61
- package/dist/storage.js +79 -57
- package/dist/version.d.ts +1 -1
- package/package.json +5 -3
- package/scripts/install_macos_app.sh +2 -2
- package/scripts/release-guard.ts +129 -0
- package/src/native/Recordings/RecordingsTests/CLIRunnerTests.swift +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -2955,7 +2955,7 @@ var require_dist = __commonJS((exports) => {
|
|
|
2955
2955
|
function parse(stream, callback) {
|
|
2956
2956
|
const parser = new parser_1.Parser;
|
|
2957
2957
|
stream.on("data", (buffer) => parser.parse(buffer, callback));
|
|
2958
|
-
return new Promise((
|
|
2958
|
+
return new Promise((resolve2) => stream.on("end", () => resolve2()));
|
|
2959
2959
|
}
|
|
2960
2960
|
exports.parse = parse;
|
|
2961
2961
|
});
|
|
@@ -3634,12 +3634,12 @@ var require_client = __commonJS((exports, module) => {
|
|
|
3634
3634
|
this._connect(callback);
|
|
3635
3635
|
return;
|
|
3636
3636
|
}
|
|
3637
|
-
return new this._Promise((
|
|
3637
|
+
return new this._Promise((resolve2, reject) => {
|
|
3638
3638
|
this._connect((error) => {
|
|
3639
3639
|
if (error) {
|
|
3640
3640
|
reject(error);
|
|
3641
3641
|
} else {
|
|
3642
|
-
|
|
3642
|
+
resolve2(this);
|
|
3643
3643
|
}
|
|
3644
3644
|
});
|
|
3645
3645
|
});
|
|
@@ -3971,8 +3971,8 @@ var require_client = __commonJS((exports, module) => {
|
|
|
3971
3971
|
readTimeout = config.query_timeout || this.connectionParameters.query_timeout;
|
|
3972
3972
|
query = new Query(config, values, callback);
|
|
3973
3973
|
if (!query.callback) {
|
|
3974
|
-
result = new this._Promise((
|
|
3975
|
-
query.callback = (err, res) => err ? reject(err) :
|
|
3974
|
+
result = new this._Promise((resolve2, reject) => {
|
|
3975
|
+
query.callback = (err, res) => err ? reject(err) : resolve2(res);
|
|
3976
3976
|
}).catch((err) => {
|
|
3977
3977
|
Error.captureStackTrace(err);
|
|
3978
3978
|
throw err;
|
|
@@ -4047,8 +4047,8 @@ var require_client = __commonJS((exports, module) => {
|
|
|
4047
4047
|
if (cb) {
|
|
4048
4048
|
this.connection.once("end", cb);
|
|
4049
4049
|
} else {
|
|
4050
|
-
return new this._Promise((
|
|
4051
|
-
this.connection.once("end",
|
|
4050
|
+
return new this._Promise((resolve2) => {
|
|
4051
|
+
this.connection.once("end", resolve2);
|
|
4052
4052
|
});
|
|
4053
4053
|
}
|
|
4054
4054
|
}
|
|
@@ -4095,8 +4095,8 @@ var require_pg_pool = __commonJS((exports, module) => {
|
|
|
4095
4095
|
const cb = function(err, client) {
|
|
4096
4096
|
err ? rej(err) : res(client);
|
|
4097
4097
|
};
|
|
4098
|
-
const result = new Promise2(function(
|
|
4099
|
-
res =
|
|
4098
|
+
const result = new Promise2(function(resolve2, reject) {
|
|
4099
|
+
res = resolve2;
|
|
4100
4100
|
rej = reject;
|
|
4101
4101
|
}).catch((err) => {
|
|
4102
4102
|
Error.captureStackTrace(err);
|
|
@@ -4157,7 +4157,7 @@ var require_pg_pool = __commonJS((exports, module) => {
|
|
|
4157
4157
|
if (typeof Promise2.try === "function") {
|
|
4158
4158
|
return Promise2.try(f);
|
|
4159
4159
|
}
|
|
4160
|
-
return new Promise2((
|
|
4160
|
+
return new Promise2((resolve2) => resolve2(f()));
|
|
4161
4161
|
}
|
|
4162
4162
|
_isFull() {
|
|
4163
4163
|
return this._clients.length >= this.options.max;
|
|
@@ -4533,8 +4533,8 @@ var require_query2 = __commonJS((exports, module) => {
|
|
|
4533
4533
|
NativeQuery.prototype._getPromise = function() {
|
|
4534
4534
|
if (this._promise)
|
|
4535
4535
|
return this._promise;
|
|
4536
|
-
this._promise = new Promise(function(
|
|
4537
|
-
this._once("end",
|
|
4536
|
+
this._promise = new Promise(function(resolve2, reject) {
|
|
4537
|
+
this._once("end", resolve2);
|
|
4538
4538
|
this._once("error", reject);
|
|
4539
4539
|
}.bind(this));
|
|
4540
4540
|
return this._promise;
|
|
@@ -4708,12 +4708,12 @@ var require_client2 = __commonJS((exports, module) => {
|
|
|
4708
4708
|
this._connect(callback);
|
|
4709
4709
|
return;
|
|
4710
4710
|
}
|
|
4711
|
-
return new this._Promise((
|
|
4711
|
+
return new this._Promise((resolve2, reject) => {
|
|
4712
4712
|
this._connect((error) => {
|
|
4713
4713
|
if (error) {
|
|
4714
4714
|
reject(error);
|
|
4715
4715
|
} else {
|
|
4716
|
-
|
|
4716
|
+
resolve2(this);
|
|
4717
4717
|
}
|
|
4718
4718
|
});
|
|
4719
4719
|
});
|
|
@@ -4737,8 +4737,8 @@ var require_client2 = __commonJS((exports, module) => {
|
|
|
4737
4737
|
query = new NativeQuery(config, values, callback);
|
|
4738
4738
|
if (!query.callback) {
|
|
4739
4739
|
let resolveOut, rejectOut;
|
|
4740
|
-
result = new this._Promise((
|
|
4741
|
-
resolveOut =
|
|
4740
|
+
result = new this._Promise((resolve2, reject) => {
|
|
4741
|
+
resolveOut = resolve2;
|
|
4742
4742
|
rejectOut = reject;
|
|
4743
4743
|
}).catch((err) => {
|
|
4744
4744
|
Error.captureStackTrace(err);
|
|
@@ -4796,8 +4796,8 @@ var require_client2 = __commonJS((exports, module) => {
|
|
|
4796
4796
|
}
|
|
4797
4797
|
let result;
|
|
4798
4798
|
if (!cb) {
|
|
4799
|
-
result = new this._Promise(function(
|
|
4800
|
-
cb = (err) => err ? reject(err) :
|
|
4799
|
+
result = new this._Promise(function(resolve2, reject) {
|
|
4800
|
+
cb = (err) => err ? reject(err) : resolve2();
|
|
4801
4801
|
});
|
|
4802
4802
|
}
|
|
4803
4803
|
this.native.end(function() {
|
|
@@ -5098,7 +5098,7 @@ var init_pg_migrate = __esm(() => {
|
|
|
5098
5098
|
var require_package = __commonJS((exports, module) => {
|
|
5099
5099
|
module.exports = {
|
|
5100
5100
|
name: "@hasna/recordings",
|
|
5101
|
-
version: "0.1.
|
|
5101
|
+
version: "0.1.36",
|
|
5102
5102
|
type: "module",
|
|
5103
5103
|
description: "Speech-to-text recording tool with MCP and CLI \u2014 records, transcribes, and optionally enhances text using AI",
|
|
5104
5104
|
repository: {
|
|
@@ -5132,7 +5132,9 @@ var require_package = __commonJS((exports, module) => {
|
|
|
5132
5132
|
"test:coverage": "bun test --coverage",
|
|
5133
5133
|
"dev:cli": "bun run src/cli/index.ts",
|
|
5134
5134
|
"dev:mcp": "bun run src/mcp/index.ts",
|
|
5135
|
-
|
|
5135
|
+
"verify:release": "bun run scripts/release-guard.ts",
|
|
5136
|
+
prepack: "bun run build && bun run verify:release",
|
|
5137
|
+
prepublishOnly: "bun run typecheck && bun run test",
|
|
5136
5138
|
postinstall: "bash scripts/install_macos_app.sh --postinstall"
|
|
5137
5139
|
},
|
|
5138
5140
|
files: [
|
|
@@ -5148,7 +5150,7 @@ var require_package = __commonJS((exports, module) => {
|
|
|
5148
5150
|
"LICENSE"
|
|
5149
5151
|
],
|
|
5150
5152
|
dependencies: {
|
|
5151
|
-
"@hasna/events": "^0.1.
|
|
5153
|
+
"@hasna/events": "^0.1.11",
|
|
5152
5154
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
5153
5155
|
chalk: "^5.4.1",
|
|
5154
5156
|
commander: "^13.1.0",
|
|
@@ -5190,29 +5192,83 @@ function getPathValue(input, path) {
|
|
|
5190
5192
|
return;
|
|
5191
5193
|
}, input);
|
|
5192
5194
|
}
|
|
5193
|
-
function
|
|
5194
|
-
const
|
|
5195
|
-
|
|
5195
|
+
function getFieldValues(input, path) {
|
|
5196
|
+
const values = [];
|
|
5197
|
+
const push = (value) => {
|
|
5198
|
+
if (!values.some((item) => Object.is(item, value)))
|
|
5199
|
+
values.push(value);
|
|
5200
|
+
};
|
|
5201
|
+
if (path.includes(".") && path in input)
|
|
5202
|
+
push(input[path]);
|
|
5203
|
+
const nestedValue = getPathValue(input, path);
|
|
5204
|
+
if (nestedValue !== undefined || !path.includes("."))
|
|
5205
|
+
push(nestedValue);
|
|
5206
|
+
return values;
|
|
5196
5207
|
}
|
|
5197
|
-
function
|
|
5208
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
5209
|
+
let body = "";
|
|
5210
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
5211
|
+
const char = pattern[index];
|
|
5212
|
+
if (char === "*") {
|
|
5213
|
+
if (pattern[index + 1] === "*") {
|
|
5214
|
+
body += ".*";
|
|
5215
|
+
index += 1;
|
|
5216
|
+
} else {
|
|
5217
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
5218
|
+
}
|
|
5219
|
+
} else {
|
|
5220
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
5221
|
+
}
|
|
5222
|
+
}
|
|
5223
|
+
return new RegExp(`^${body}$`);
|
|
5224
|
+
}
|
|
5225
|
+
function matchString(value, matcher, options = {}) {
|
|
5198
5226
|
if (matcher === undefined)
|
|
5199
5227
|
return true;
|
|
5200
5228
|
if (value === undefined)
|
|
5201
5229
|
return false;
|
|
5202
5230
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
5203
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
5231
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
5204
5232
|
}
|
|
5205
5233
|
function matchRecord(input, matcher) {
|
|
5206
5234
|
if (!matcher)
|
|
5207
5235
|
return true;
|
|
5208
5236
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
5209
|
-
const
|
|
5210
|
-
|
|
5211
|
-
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
5212
|
-
}
|
|
5213
|
-
return actual === expected;
|
|
5237
|
+
const actualValues = getFieldValues(input, path);
|
|
5238
|
+
return matchField(actualValues, expected, path);
|
|
5214
5239
|
});
|
|
5215
5240
|
}
|
|
5241
|
+
function matchField(actualValues, expected, path) {
|
|
5242
|
+
if (isNegativeMatcher(expected)) {
|
|
5243
|
+
return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
|
|
5244
|
+
}
|
|
5245
|
+
return actualValues.some((actual) => matchPositiveField(actual, expected, path));
|
|
5246
|
+
}
|
|
5247
|
+
function matchPositiveField(actual, expected, path) {
|
|
5248
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
5249
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
5250
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
5251
|
+
}));
|
|
5252
|
+
}
|
|
5253
|
+
if (Array.isArray(actual)) {
|
|
5254
|
+
return actual.some((item) => item === expected);
|
|
5255
|
+
}
|
|
5256
|
+
return actual === expected;
|
|
5257
|
+
}
|
|
5258
|
+
function stringCandidates(actual) {
|
|
5259
|
+
if (actual === undefined)
|
|
5260
|
+
return [];
|
|
5261
|
+
if (Array.isArray(actual)) {
|
|
5262
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
5263
|
+
}
|
|
5264
|
+
return [String(actual)];
|
|
5265
|
+
}
|
|
5266
|
+
function isPrimitiveFieldValue(value) {
|
|
5267
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
5268
|
+
}
|
|
5269
|
+
function isNegativeMatcher(value) {
|
|
5270
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
5271
|
+
}
|
|
5216
5272
|
function eventMatchesFilter(event, filter) {
|
|
5217
5273
|
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);
|
|
5218
5274
|
}
|
|
@@ -5228,6 +5284,13 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
5228
5284
|
function getEventsDataDir(override) {
|
|
5229
5285
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
5230
5286
|
}
|
|
5287
|
+
function getActiveEventsDirEnv() {
|
|
5288
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
5289
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
5290
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
5291
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
5292
|
+
return null;
|
|
5293
|
+
}
|
|
5231
5294
|
|
|
5232
5295
|
class JsonEventsStore {
|
|
5233
5296
|
dataDir;
|
|
@@ -5340,6 +5403,52 @@ class JsonEventsStore {
|
|
|
5340
5403
|
});
|
|
5341
5404
|
}
|
|
5342
5405
|
}
|
|
5406
|
+
async function getEventsStatus(dataDir) {
|
|
5407
|
+
const store = new JsonEventsStore(dataDir);
|
|
5408
|
+
await store.init();
|
|
5409
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
5410
|
+
store.listChannels(),
|
|
5411
|
+
store.listEvents(),
|
|
5412
|
+
store.listDeliveries()
|
|
5413
|
+
]);
|
|
5414
|
+
const transports = channels.reduce((counts, channel) => {
|
|
5415
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
5416
|
+
return counts;
|
|
5417
|
+
}, {});
|
|
5418
|
+
return {
|
|
5419
|
+
service: "events",
|
|
5420
|
+
schemaVersion: "1.0",
|
|
5421
|
+
dataDir: store.dataDir,
|
|
5422
|
+
env: {
|
|
5423
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
5424
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
5425
|
+
active: getActiveEventsDirEnv()
|
|
5426
|
+
},
|
|
5427
|
+
files: {
|
|
5428
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
5429
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
5430
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
5431
|
+
},
|
|
5432
|
+
counts: {
|
|
5433
|
+
channels: channels.length,
|
|
5434
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
5435
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
5436
|
+
events: events.length,
|
|
5437
|
+
deliveries: deliveries.length
|
|
5438
|
+
},
|
|
5439
|
+
transports,
|
|
5440
|
+
safety: {
|
|
5441
|
+
includesEventPayloads: false,
|
|
5442
|
+
includesWebhookSecrets: false,
|
|
5443
|
+
listOutputsRedactSecrets: true,
|
|
5444
|
+
statusOutputIsMetadataOnly: true
|
|
5445
|
+
}
|
|
5446
|
+
};
|
|
5447
|
+
}
|
|
5448
|
+
function statusFile(dataDir, fileName, records) {
|
|
5449
|
+
const path = join(dataDir, fileName);
|
|
5450
|
+
return { path, exists: existsSync(path), records };
|
|
5451
|
+
}
|
|
5343
5452
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
5344
5453
|
function buildSignatureBase(timestamp, body) {
|
|
5345
5454
|
return `${timestamp}.${body}`;
|
|
@@ -5565,7 +5674,7 @@ class EventsClient {
|
|
|
5565
5674
|
}
|
|
5566
5675
|
return deliveries;
|
|
5567
5676
|
}
|
|
5568
|
-
async
|
|
5677
|
+
async matchChannel(id, input = {}) {
|
|
5569
5678
|
const channel = await this.store.getChannel(id);
|
|
5570
5679
|
if (!channel)
|
|
5571
5680
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -5582,6 +5691,34 @@ class EventsClient {
|
|
|
5582
5691
|
time: input.time,
|
|
5583
5692
|
id: input.id
|
|
5584
5693
|
});
|
|
5694
|
+
const matched = channelMatchesEvent(channel, event);
|
|
5695
|
+
return {
|
|
5696
|
+
channelId: channel.id,
|
|
5697
|
+
matched,
|
|
5698
|
+
event,
|
|
5699
|
+
filters: channel.filters,
|
|
5700
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
5701
|
+
};
|
|
5702
|
+
}
|
|
5703
|
+
async testChannel(id, input = {}, options = {}) {
|
|
5704
|
+
const channel = await this.store.getChannel(id);
|
|
5705
|
+
if (!channel)
|
|
5706
|
+
throw new Error(`Channel not found: ${id}`);
|
|
5707
|
+
const match = await this.matchChannel(id, input);
|
|
5708
|
+
const event = match.event;
|
|
5709
|
+
if (options.honorFilters && !match.matched) {
|
|
5710
|
+
const timestamp = new Date().toISOString();
|
|
5711
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
5712
|
+
attempt: 1,
|
|
5713
|
+
status: "skipped",
|
|
5714
|
+
startedAt: timestamp,
|
|
5715
|
+
completedAt: timestamp,
|
|
5716
|
+
error: match.reason
|
|
5717
|
+
}]);
|
|
5718
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
5719
|
+
await this.store.appendDelivery(result2);
|
|
5720
|
+
return result2;
|
|
5721
|
+
}
|
|
5585
5722
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
5586
5723
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
5587
5724
|
await this.store.appendDelivery(result);
|
|
@@ -5692,6 +5829,76 @@ function normalizeRetryPolicy(policy) {
|
|
|
5692
5829
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
5693
5830
|
};
|
|
5694
5831
|
}
|
|
5832
|
+
function parseFieldMatchers(values, label, typed = false) {
|
|
5833
|
+
if (!values?.length)
|
|
5834
|
+
return;
|
|
5835
|
+
const result = {};
|
|
5836
|
+
for (const value of values) {
|
|
5837
|
+
const parsed = parseMatcherExpression(value, label);
|
|
5838
|
+
const path = parsed.path;
|
|
5839
|
+
if (path in result)
|
|
5840
|
+
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
5841
|
+
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
5842
|
+
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
5843
|
+
}
|
|
5844
|
+
return result;
|
|
5845
|
+
}
|
|
5846
|
+
function parseFilterOptions(options) {
|
|
5847
|
+
const filter2 = {};
|
|
5848
|
+
if (options.source)
|
|
5849
|
+
filter2.source = options.source;
|
|
5850
|
+
if (options.type)
|
|
5851
|
+
filter2.type = options.type;
|
|
5852
|
+
if (options.subject)
|
|
5853
|
+
filter2.subject = options.subject;
|
|
5854
|
+
if (options.severity)
|
|
5855
|
+
filter2.severity = options.severity;
|
|
5856
|
+
const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
|
|
5857
|
+
const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
|
|
5858
|
+
if (Object.keys(data).length > 0)
|
|
5859
|
+
filter2.data = data;
|
|
5860
|
+
if (Object.keys(metadata).length > 0)
|
|
5861
|
+
filter2.metadata = metadata;
|
|
5862
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
5863
|
+
}
|
|
5864
|
+
function mergeMatchers(...records) {
|
|
5865
|
+
const result = {};
|
|
5866
|
+
for (const record of records) {
|
|
5867
|
+
if (!record)
|
|
5868
|
+
continue;
|
|
5869
|
+
for (const [path, value] of Object.entries(record)) {
|
|
5870
|
+
if (path in result)
|
|
5871
|
+
throw new Error(`Duplicate filter path: ${path}`);
|
|
5872
|
+
result[path] = value;
|
|
5873
|
+
}
|
|
5874
|
+
}
|
|
5875
|
+
return result;
|
|
5876
|
+
}
|
|
5877
|
+
function parseTypedMatcherValue(value, label) {
|
|
5878
|
+
const parsed = JSON.parse(value);
|
|
5879
|
+
if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
5880
|
+
return parsed;
|
|
5881
|
+
}
|
|
5882
|
+
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
5883
|
+
}
|
|
5884
|
+
function parseMatcherExpression(value, label) {
|
|
5885
|
+
const negativeSeparator = value.indexOf("!=");
|
|
5886
|
+
if (negativeSeparator > 0) {
|
|
5887
|
+
return {
|
|
5888
|
+
path: value.slice(0, negativeSeparator),
|
|
5889
|
+
rawValue: value.slice(negativeSeparator + 2),
|
|
5890
|
+
negated: true
|
|
5891
|
+
};
|
|
5892
|
+
}
|
|
5893
|
+
const separator = value.indexOf("=");
|
|
5894
|
+
if (separator <= 0)
|
|
5895
|
+
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
5896
|
+
return {
|
|
5897
|
+
path: value.slice(0, separator),
|
|
5898
|
+
rawValue: value.slice(separator + 1),
|
|
5899
|
+
negated: false
|
|
5900
|
+
};
|
|
5901
|
+
}
|
|
5695
5902
|
function parseJsonObject(value, fallback) {
|
|
5696
5903
|
if (!value)
|
|
5697
5904
|
return fallback;
|
|
@@ -5713,18 +5920,6 @@ function parseHeaders(values) {
|
|
|
5713
5920
|
}
|
|
5714
5921
|
return headers;
|
|
5715
5922
|
}
|
|
5716
|
-
function parseFilter(options) {
|
|
5717
|
-
const filter2 = {};
|
|
5718
|
-
if (options.source)
|
|
5719
|
-
filter2.source = options.source;
|
|
5720
|
-
if (options.type)
|
|
5721
|
-
filter2.type = options.type;
|
|
5722
|
-
if (options.subject)
|
|
5723
|
-
filter2.subject = options.subject;
|
|
5724
|
-
if (options.severity)
|
|
5725
|
-
filter2.severity = options.severity;
|
|
5726
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
5727
|
-
}
|
|
5728
5923
|
function createClient(options) {
|
|
5729
5924
|
if (options.createClient)
|
|
5730
5925
|
return options.createClient();
|
|
@@ -5744,14 +5939,14 @@ function wantsJson(actionOptions, command) {
|
|
|
5744
5939
|
}
|
|
5745
5940
|
function registerWebhookCommands(program, options) {
|
|
5746
5941
|
const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
5747
|
-
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, command) => {
|
|
5942
|
+
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("--data <path=value...>", "Event data field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--metadata <path=value...>", "Event metadata field filter; string values, path!=value negatives, array-member matching, dot paths, * segment wildcard, ** recursive wildcard", collectValues, []).option("--data-json <path=json...>", "Event data field filter with typed JSON value; path!=json negatives supported", collectValues, []).option("--metadata-json <path=json...>", "Event metadata field filter with typed JSON value; path!=json negatives supported", collectValues, []).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, command) => {
|
|
5748
5943
|
const timestamp = new Date().toISOString();
|
|
5749
5944
|
const channel = {
|
|
5750
5945
|
id: actionOptions.id,
|
|
5751
5946
|
name: actionOptions.name,
|
|
5752
5947
|
enabled: !actionOptions.disabled,
|
|
5753
5948
|
transport: actionOptions.transport,
|
|
5754
|
-
filters:
|
|
5949
|
+
filters: parseFilterOptions(actionOptions),
|
|
5755
5950
|
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
5756
5951
|
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
5757
5952
|
createdAt: timestamp,
|
|
@@ -5781,20 +5976,36 @@ function registerWebhookCommands(program, options) {
|
|
|
5781
5976
|
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
5782
5977
|
}
|
|
5783
5978
|
});
|
|
5979
|
+
webhooks.command("status").description("Show events webhook storage status").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
5980
|
+
const status = await getEventsStatus(options.dataDir);
|
|
5981
|
+
print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
|
|
5982
|
+
});
|
|
5784
5983
|
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
5785
5984
|
const removed = await createClient(options).removeChannel(id);
|
|
5786
5985
|
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
5787
5986
|
});
|
|
5788
|
-
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, command) => {
|
|
5987
|
+
webhooks.command("test").description("Send a test event to one subscription").argument("<id>", "Subscription/channel identifier").option("--source <source>", "Event source override").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("--metadata <json>", "Event metadata JSON object").option("--honor-filters", "Skip delivery when the sample event does not match channel filters", false).option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
5789
5988
|
const result = await createClient(options).testChannel(id, {
|
|
5790
|
-
source: options.source,
|
|
5989
|
+
source: actionOptions.source ?? options.source,
|
|
5791
5990
|
type: actionOptions.type,
|
|
5792
5991
|
subject: actionOptions.subject ?? id,
|
|
5793
5992
|
message: actionOptions.message,
|
|
5794
|
-
data: parseJsonObject(actionOptions.data, { test: true })
|
|
5795
|
-
|
|
5993
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
5994
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
5995
|
+
}, { honorFilters: actionOptions.honorFilters });
|
|
5796
5996
|
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
5797
5997
|
});
|
|
5998
|
+
webhooks.command("match").description("Check whether a sample event matches one subscription without delivering").argument("<id>", "Subscription/channel identifier").option("--source <source>", "Event source override").option("--type <type>", "Event type", "events.test").option("--subject <subject>", "Event subject").option("--message <message>", "Event message", "Hasna events match preview").option("--data <json>", "Event data JSON object").option("--metadata <json>", "Event metadata JSON object").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
5999
|
+
const result = await createClient(options).matchChannel(id, {
|
|
6000
|
+
source: actionOptions.source ?? options.source,
|
|
6001
|
+
type: actionOptions.type,
|
|
6002
|
+
subject: actionOptions.subject ?? id,
|
|
6003
|
+
message: actionOptions.message,
|
|
6004
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
6005
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
6006
|
+
});
|
|
6007
|
+
print(result, wantsJson(actionOptions, command), `${result.matched ? "matched" : "skipped"}: ${result.channelId}`);
|
|
6008
|
+
});
|
|
5798
6009
|
return webhooks;
|
|
5799
6010
|
}
|
|
5800
6011
|
function registerEventCommands(program, options) {
|
|
@@ -5861,12 +6072,12 @@ function collectValues(value, previous) {
|
|
|
5861
6072
|
import chalk2 from "chalk";
|
|
5862
6073
|
import { spawnSync } from "child_process";
|
|
5863
6074
|
import { existsSync as existsSync5, readFileSync as readFileSync3 } from "fs";
|
|
5864
|
-
import { dirname as
|
|
6075
|
+
import { dirname as dirname3, join as pathJoin } from "path";
|
|
5865
6076
|
import { fileURLToPath } from "url";
|
|
5866
6077
|
|
|
5867
6078
|
// src/lib/config.ts
|
|
5868
|
-
import { existsSync as existsSync2, readFileSync, mkdirSync,
|
|
5869
|
-
import { join as join2 } from "path";
|
|
6079
|
+
import { copyFileSync, existsSync as existsSync2, readFileSync, mkdirSync, readdirSync, statSync } from "fs";
|
|
6080
|
+
import { dirname, join as join2, resolve } from "path";
|
|
5870
6081
|
import { homedir as homedir2 } from "os";
|
|
5871
6082
|
var DEFAULT_CONFIG = {
|
|
5872
6083
|
openai_api_key: "",
|
|
@@ -5960,44 +6171,53 @@ function expandEnvBackedConfig(config) {
|
|
|
5960
6171
|
return expanded;
|
|
5961
6172
|
}
|
|
5962
6173
|
function findConfigFile() {
|
|
5963
|
-
|
|
5964
|
-
const root = "/";
|
|
5965
|
-
while (dir !== root) {
|
|
5966
|
-
const candidate = join2(dir, ".recordings", "config.json");
|
|
5967
|
-
if (existsSync2(candidate))
|
|
5968
|
-
return candidate;
|
|
5969
|
-
const parent = join2(dir, "..");
|
|
5970
|
-
if (parent === dir)
|
|
5971
|
-
break;
|
|
5972
|
-
dir = parent;
|
|
5973
|
-
}
|
|
5974
|
-
return null;
|
|
6174
|
+
return findProjectRecordingsPath("config.json");
|
|
5975
6175
|
}
|
|
5976
6176
|
function getDataDir() {
|
|
5977
|
-
|
|
5978
|
-
|
|
5979
|
-
|
|
5980
|
-
|
|
5981
|
-
if (existsSync2(candidate))
|
|
5982
|
-
return candidate;
|
|
5983
|
-
const parent = join2(dir, "..");
|
|
5984
|
-
if (parent === dir)
|
|
5985
|
-
break;
|
|
5986
|
-
dir = parent;
|
|
5987
|
-
}
|
|
5988
|
-
const home = homedir2();
|
|
6177
|
+
const projectLocalDir = findProjectRecordingsPath();
|
|
6178
|
+
if (projectLocalDir)
|
|
6179
|
+
return projectLocalDir;
|
|
6180
|
+
const home = getHomeDir();
|
|
5989
6181
|
const newDir = join2(home, ".hasna", "recordings");
|
|
5990
6182
|
const oldDir = join2(home, ".recordings");
|
|
5991
|
-
if (
|
|
6183
|
+
if (existsSync2(oldDir)) {
|
|
5992
6184
|
try {
|
|
5993
|
-
|
|
5994
|
-
cpSync(oldDir, newDir, { recursive: true });
|
|
6185
|
+
mergeDirectoryContents(oldDir, newDir);
|
|
5995
6186
|
} catch {}
|
|
5996
6187
|
}
|
|
5997
6188
|
return newDir;
|
|
5998
6189
|
}
|
|
6190
|
+
function findProjectRecordingsPath(entry) {
|
|
6191
|
+
const home = resolve(getHomeDir());
|
|
6192
|
+
let dir = resolve(process.cwd());
|
|
6193
|
+
while (true) {
|
|
6194
|
+
if (dir !== home) {
|
|
6195
|
+
const candidate = entry ? join2(dir, ".recordings", entry) : join2(dir, ".recordings");
|
|
6196
|
+
if (existsSync2(candidate))
|
|
6197
|
+
return candidate;
|
|
6198
|
+
}
|
|
6199
|
+
const parent = dirname(dir);
|
|
6200
|
+
if (parent === dir)
|
|
6201
|
+
break;
|
|
6202
|
+
dir = parent;
|
|
6203
|
+
}
|
|
6204
|
+
return null;
|
|
6205
|
+
}
|
|
6206
|
+
function mergeDirectoryContents(sourceDir, targetDir) {
|
|
6207
|
+
mkdirSync(targetDir, { recursive: true });
|
|
6208
|
+
for (const entry of readdirSync(sourceDir)) {
|
|
6209
|
+
const sourcePath = join2(sourceDir, entry);
|
|
6210
|
+
const targetPath = join2(targetDir, entry);
|
|
6211
|
+
const sourceStats = statSync(sourcePath);
|
|
6212
|
+
if (sourceStats.isDirectory()) {
|
|
6213
|
+
mergeDirectoryContents(sourcePath, targetPath);
|
|
6214
|
+
} else if (!existsSync2(targetPath)) {
|
|
6215
|
+
copyFileSync(sourcePath, targetPath);
|
|
6216
|
+
}
|
|
6217
|
+
}
|
|
6218
|
+
}
|
|
5999
6219
|
function loadSecretKey(keyName) {
|
|
6000
|
-
const secretsPath = join2(
|
|
6220
|
+
const secretsPath = join2(getHomeDir(), ".secrets");
|
|
6001
6221
|
if (!existsSync2(secretsPath))
|
|
6002
6222
|
return "";
|
|
6003
6223
|
for (const candidate of listSecretFiles(secretsPath)) {
|
|
@@ -6038,6 +6258,9 @@ function listSecretFiles(path) {
|
|
|
6038
6258
|
return [];
|
|
6039
6259
|
}
|
|
6040
6260
|
}
|
|
6261
|
+
function getHomeDir() {
|
|
6262
|
+
return process.env["HOME"] || process.env["USERPROFILE"] || homedir2();
|
|
6263
|
+
}
|
|
6041
6264
|
function ensureDataDir(config) {
|
|
6042
6265
|
const { mkdirSync: mkdirSync2 } = __require("fs");
|
|
6043
6266
|
mkdirSync2(config.audio_dir, { recursive: true });
|
|
@@ -6079,7 +6302,7 @@ class SqliteAdapter {
|
|
|
6079
6302
|
|
|
6080
6303
|
// src/db/database.ts
|
|
6081
6304
|
import { mkdirSync as mkdirSync2 } from "fs";
|
|
6082
|
-
import { dirname } from "path";
|
|
6305
|
+
import { dirname as dirname2 } from "path";
|
|
6083
6306
|
var _db = null;
|
|
6084
6307
|
var _adapter = null;
|
|
6085
6308
|
var MIGRATIONS = [
|
|
@@ -6165,7 +6388,7 @@ function getDatabase(dbPath) {
|
|
|
6165
6388
|
if (_db)
|
|
6166
6389
|
return _db;
|
|
6167
6390
|
const path = dbPath || loadConfig().db_path;
|
|
6168
|
-
const dir =
|
|
6391
|
+
const dir = dirname2(path);
|
|
6169
6392
|
mkdirSync2(dir, { recursive: true });
|
|
6170
6393
|
_adapter = new SqliteAdapter(path);
|
|
6171
6394
|
_db = _adapter.raw;
|
|
@@ -6768,7 +6991,7 @@ async function processText(rawText, config, systemPrompt) {
|
|
|
6768
6991
|
}
|
|
6769
6992
|
|
|
6770
6993
|
// src/version.ts
|
|
6771
|
-
var VERSION = "0.1.
|
|
6994
|
+
var VERSION = "0.1.36";
|
|
6772
6995
|
|
|
6773
6996
|
// src/cli/storage.ts
|
|
6774
6997
|
import chalk from "chalk";
|
|
@@ -6777,11 +7000,12 @@ import chalk from "chalk";
|
|
|
6777
7000
|
import { existsSync as existsSync4, readFileSync as readFileSync2 } from "fs";
|
|
6778
7001
|
import { homedir as homedir3 } from "os";
|
|
6779
7002
|
import { join as join4 } from "path";
|
|
6780
|
-
var
|
|
7003
|
+
var LEGACY_DATABASE_CONFIG_KEY = ["r", "d", "s"].join("");
|
|
6781
7004
|
var RECORDINGS_STORAGE_ENV = "HASNA_RECORDINGS_DATABASE_URL";
|
|
6782
7005
|
var RECORDINGS_STORAGE_FALLBACK_ENV = "RECORDINGS_DATABASE_URL";
|
|
6783
7006
|
var RECORDINGS_STORAGE_MODE_ENV = "HASNA_RECORDINGS_STORAGE_MODE";
|
|
6784
7007
|
var RECORDINGS_STORAGE_MODE_FALLBACK_ENV = "RECORDINGS_STORAGE_MODE";
|
|
7008
|
+
var RECORDINGS_STORAGE_CONFIG_ENV = "HASNA_RECORDINGS_STORAGE_CONFIG";
|
|
6785
7009
|
var STORAGE_DATABASE_ENV = [RECORDINGS_STORAGE_ENV, RECORDINGS_STORAGE_FALLBACK_ENV];
|
|
6786
7010
|
function readEnv(name) {
|
|
6787
7011
|
const value = process.env[name]?.trim();
|
|
@@ -6808,10 +7032,16 @@ function getStorageDatabaseUrl() {
|
|
|
6808
7032
|
const env = getStorageDatabaseEnv();
|
|
6809
7033
|
return env ? readEnv(env.name) : undefined;
|
|
6810
7034
|
}
|
|
7035
|
+
function getStorageConfigPath() {
|
|
7036
|
+
const override = readEnv(RECORDINGS_STORAGE_CONFIG_ENV);
|
|
7037
|
+
if (override)
|
|
7038
|
+
return override;
|
|
7039
|
+
return join4(homedir3(), ".hasna", "recordings", "storage", "config.json");
|
|
7040
|
+
}
|
|
6811
7041
|
function getStorageConfig() {
|
|
6812
7042
|
const config = {
|
|
6813
7043
|
mode: "local",
|
|
6814
|
-
|
|
7044
|
+
postgres: {
|
|
6815
7045
|
host: "",
|
|
6816
7046
|
port: 5432,
|
|
6817
7047
|
username: "",
|
|
@@ -6819,11 +7049,13 @@ function getStorageConfig() {
|
|
|
6819
7049
|
ssl: true
|
|
6820
7050
|
}
|
|
6821
7051
|
};
|
|
6822
|
-
|
|
7052
|
+
const storageConfigPath = getStorageConfigPath();
|
|
7053
|
+
if (existsSync4(storageConfigPath)) {
|
|
6823
7054
|
try {
|
|
6824
|
-
const raw = JSON.parse(readFileSync2(
|
|
7055
|
+
const raw = JSON.parse(readFileSync2(storageConfigPath, "utf-8"));
|
|
7056
|
+
const legacyDatabaseConfig = typeof raw[LEGACY_DATABASE_CONFIG_KEY] === "object" && raw[LEGACY_DATABASE_CONFIG_KEY] !== null ? raw[LEGACY_DATABASE_CONFIG_KEY] : {};
|
|
6825
7057
|
config.mode = normalizeMode(raw.mode) ?? config.mode;
|
|
6826
|
-
config.
|
|
7058
|
+
config.postgres = { ...config.postgres, ...legacyDatabaseConfig, ...raw.postgres ?? {} };
|
|
6827
7059
|
} catch {}
|
|
6828
7060
|
}
|
|
6829
7061
|
const modeOverride = readEnv(RECORDINGS_STORAGE_MODE_ENV) ?? readEnv(RECORDINGS_STORAGE_MODE_FALLBACK_ENV);
|
|
@@ -6840,7 +7072,7 @@ function getStorageConnectionString(dbName = "recordings") {
|
|
|
6840
7072
|
if (direct)
|
|
6841
7073
|
return direct;
|
|
6842
7074
|
const config = getStorageConfig();
|
|
6843
|
-
const { host, port, username, password_env, ssl } = config.
|
|
7075
|
+
const { host, port, username, password_env, ssl } = config.postgres;
|
|
6844
7076
|
if (!host || !username) {
|
|
6845
7077
|
throw new Error("Storage database is not configured. Set HASNA_RECORDINGS_DATABASE_URL or configure ~/.hasna/recordings/storage/config.json.");
|
|
6846
7078
|
}
|
|
@@ -6925,8 +7157,9 @@ async function runStorageMigrations(remote) {
|
|
|
6925
7157
|
function getStorageStatus(db = getDatabase()) {
|
|
6926
7158
|
const config = getStorageConfig();
|
|
6927
7159
|
const activeEnv = getStorageDatabaseEnv();
|
|
7160
|
+
const hasConfiguredPostgres = Boolean(config.postgres.host && config.postgres.username);
|
|
6928
7161
|
return {
|
|
6929
|
-
configured: Boolean(activeEnv),
|
|
7162
|
+
configured: Boolean(activeEnv) || hasConfiguredPostgres,
|
|
6930
7163
|
mode: config.mode,
|
|
6931
7164
|
enabled: config.mode === "hybrid" || config.mode === "remote",
|
|
6932
7165
|
env: STORAGE_DATABASE_ENV,
|
|
@@ -7162,13 +7395,13 @@ program.command("record").description("Record from microphone, transcribe, and o
|
|
|
7162
7395
|
} else {
|
|
7163
7396
|
console.log(chalk2.blue("Recording... Press") + chalk2.yellow(" Enter ") + chalk2.blue("to stop."));
|
|
7164
7397
|
audioPath = startRecording(config);
|
|
7165
|
-
await new Promise((
|
|
7398
|
+
await new Promise((resolve2) => {
|
|
7166
7399
|
process.stdin.setRawMode?.(true);
|
|
7167
7400
|
process.stdin.resume();
|
|
7168
7401
|
process.stdin.once("data", () => {
|
|
7169
7402
|
process.stdin.setRawMode?.(false);
|
|
7170
7403
|
process.stdin.pause();
|
|
7171
|
-
|
|
7404
|
+
resolve2();
|
|
7172
7405
|
});
|
|
7173
7406
|
});
|
|
7174
7407
|
stopRecording();
|
|
@@ -8092,7 +8325,7 @@ function tccAuthValueLabel(value) {
|
|
|
8092
8325
|
}
|
|
8093
8326
|
}
|
|
8094
8327
|
function findPackageRoot() {
|
|
8095
|
-
let current =
|
|
8328
|
+
let current = dirname3(fileURLToPath(import.meta.url));
|
|
8096
8329
|
while (true) {
|
|
8097
8330
|
const packagePath = pathJoin(current, "package.json");
|
|
8098
8331
|
if (existsSync5(packagePath)) {
|
|
@@ -8103,7 +8336,7 @@ function findPackageRoot() {
|
|
|
8103
8336
|
}
|
|
8104
8337
|
} catch {}
|
|
8105
8338
|
}
|
|
8106
|
-
const parent =
|
|
8339
|
+
const parent = dirname3(current);
|
|
8107
8340
|
if (parent === current) {
|
|
8108
8341
|
return process.cwd();
|
|
8109
8342
|
}
|