@hasna/instructions 0.3.0 → 0.3.1
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/dashboard/dist/assets/index-D7p6fFQw.js +11 -0
- package/dashboard/dist/assets/index-DQ3P1g1z.css +1 -0
- package/dashboard/dist/index.html +14 -0
- package/dashboard/dist/vite.svg +1 -0
- package/dist/cli/index.js +40 -250
- package/dist/generated/storage-kit/index.d.ts +1 -1
- package/dist/mcp/index.js +2 -2
- package/dist/server/index.js +37 -39
- package/package.json +2 -2
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
|
package/dist/cli/index.js
CHANGED
|
@@ -3581,7 +3581,7 @@ var init_sync = __esm(() => {
|
|
|
3581
3581
|
];
|
|
3582
3582
|
});
|
|
3583
3583
|
|
|
3584
|
-
// node_modules
|
|
3584
|
+
// node_modules/@hasna/events/dist/commander.js
|
|
3585
3585
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
3586
3586
|
import { existsSync } from "fs";
|
|
3587
3587
|
import { homedir } from "os";
|
|
@@ -3598,83 +3598,29 @@ function getPathValue(input, path) {
|
|
|
3598
3598
|
return;
|
|
3599
3599
|
}, input);
|
|
3600
3600
|
}
|
|
3601
|
-
function
|
|
3602
|
-
const
|
|
3603
|
-
|
|
3604
|
-
if (!values.some((item) => Object.is(item, value)))
|
|
3605
|
-
values.push(value);
|
|
3606
|
-
};
|
|
3607
|
-
if (path.includes(".") && path in input)
|
|
3608
|
-
push(input[path]);
|
|
3609
|
-
const nestedValue = getPathValue(input, path);
|
|
3610
|
-
if (nestedValue !== undefined || !path.includes("."))
|
|
3611
|
-
push(nestedValue);
|
|
3612
|
-
return values;
|
|
3613
|
-
}
|
|
3614
|
-
function wildcardToRegExp(pattern, options = {}) {
|
|
3615
|
-
let body = "";
|
|
3616
|
-
for (let index = 0;index < pattern.length; index += 1) {
|
|
3617
|
-
const char = pattern[index];
|
|
3618
|
-
if (char === "*") {
|
|
3619
|
-
if (pattern[index + 1] === "*") {
|
|
3620
|
-
body += ".*";
|
|
3621
|
-
index += 1;
|
|
3622
|
-
} else {
|
|
3623
|
-
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
3624
|
-
}
|
|
3625
|
-
} else {
|
|
3626
|
-
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
3627
|
-
}
|
|
3628
|
-
}
|
|
3629
|
-
return new RegExp(`^${body}$`);
|
|
3601
|
+
function wildcardToRegExp(pattern) {
|
|
3602
|
+
const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
|
|
3603
|
+
return new RegExp(`^${escaped}$`);
|
|
3630
3604
|
}
|
|
3631
|
-
function matchString(value, matcher
|
|
3605
|
+
function matchString(value, matcher) {
|
|
3632
3606
|
if (matcher === undefined)
|
|
3633
3607
|
return true;
|
|
3634
3608
|
if (value === undefined)
|
|
3635
3609
|
return false;
|
|
3636
3610
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
3637
|
-
return matchers.some((item) => wildcardToRegExp(item
|
|
3611
|
+
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
3638
3612
|
}
|
|
3639
3613
|
function matchRecord(input, matcher) {
|
|
3640
3614
|
if (!matcher)
|
|
3641
3615
|
return true;
|
|
3642
3616
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3617
|
+
const actual = getPathValue(input, path);
|
|
3618
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
3619
|
+
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
3620
|
+
}
|
|
3621
|
+
return actual === expected;
|
|
3645
3622
|
});
|
|
3646
3623
|
}
|
|
3647
|
-
function matchField(actualValues, expected, path) {
|
|
3648
|
-
if (isNegativeMatcher(expected)) {
|
|
3649
|
-
return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
|
|
3650
|
-
}
|
|
3651
|
-
return actualValues.some((actual) => matchPositiveField(actual, expected, path));
|
|
3652
|
-
}
|
|
3653
|
-
function matchPositiveField(actual, expected, path) {
|
|
3654
|
-
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
3655
|
-
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
3656
|
-
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
3657
|
-
}));
|
|
3658
|
-
}
|
|
3659
|
-
if (Array.isArray(actual)) {
|
|
3660
|
-
return actual.some((item) => item === expected);
|
|
3661
|
-
}
|
|
3662
|
-
return actual === expected;
|
|
3663
|
-
}
|
|
3664
|
-
function stringCandidates(actual) {
|
|
3665
|
-
if (actual === undefined)
|
|
3666
|
-
return [];
|
|
3667
|
-
if (Array.isArray(actual)) {
|
|
3668
|
-
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
3669
|
-
}
|
|
3670
|
-
return [String(actual)];
|
|
3671
|
-
}
|
|
3672
|
-
function isPrimitiveFieldValue(value) {
|
|
3673
|
-
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
3674
|
-
}
|
|
3675
|
-
function isNegativeMatcher(value) {
|
|
3676
|
-
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
3677
|
-
}
|
|
3678
3624
|
function eventMatchesFilter(event, filter) {
|
|
3679
3625
|
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);
|
|
3680
3626
|
}
|
|
@@ -3690,14 +3636,6 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
3690
3636
|
function getEventsDataDir(override) {
|
|
3691
3637
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
3692
3638
|
}
|
|
3693
|
-
function getActiveEventsDirEnv() {
|
|
3694
|
-
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
3695
|
-
return HASNA_EVENTS_DIR_ENV;
|
|
3696
|
-
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
3697
|
-
return HASNA_EVENTS_HOME_ENV;
|
|
3698
|
-
return null;
|
|
3699
|
-
}
|
|
3700
|
-
|
|
3701
3639
|
class JsonEventsStore {
|
|
3702
3640
|
dataDir;
|
|
3703
3641
|
channelsPath;
|
|
@@ -3809,52 +3747,6 @@ class JsonEventsStore {
|
|
|
3809
3747
|
});
|
|
3810
3748
|
}
|
|
3811
3749
|
}
|
|
3812
|
-
async function getEventsStatus(dataDir) {
|
|
3813
|
-
const store = new JsonEventsStore(dataDir);
|
|
3814
|
-
await store.init();
|
|
3815
|
-
const [channels, events, deliveries] = await Promise.all([
|
|
3816
|
-
store.listChannels(),
|
|
3817
|
-
store.listEvents(),
|
|
3818
|
-
store.listDeliveries()
|
|
3819
|
-
]);
|
|
3820
|
-
const transports = channels.reduce((counts, channel) => {
|
|
3821
|
-
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
3822
|
-
return counts;
|
|
3823
|
-
}, {});
|
|
3824
|
-
return {
|
|
3825
|
-
service: "events",
|
|
3826
|
-
schemaVersion: "1.0",
|
|
3827
|
-
dataDir: store.dataDir,
|
|
3828
|
-
env: {
|
|
3829
|
-
primary: HASNA_EVENTS_DIR_ENV,
|
|
3830
|
-
fallback: HASNA_EVENTS_HOME_ENV,
|
|
3831
|
-
active: getActiveEventsDirEnv()
|
|
3832
|
-
},
|
|
3833
|
-
files: {
|
|
3834
|
-
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
3835
|
-
events: statusFile(store.dataDir, "events.json", events.length),
|
|
3836
|
-
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
3837
|
-
},
|
|
3838
|
-
counts: {
|
|
3839
|
-
channels: channels.length,
|
|
3840
|
-
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
3841
|
-
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
3842
|
-
events: events.length,
|
|
3843
|
-
deliveries: deliveries.length
|
|
3844
|
-
},
|
|
3845
|
-
transports,
|
|
3846
|
-
safety: {
|
|
3847
|
-
includesEventPayloads: false,
|
|
3848
|
-
includesWebhookSecrets: false,
|
|
3849
|
-
listOutputsRedactSecrets: true,
|
|
3850
|
-
statusOutputIsMetadataOnly: true
|
|
3851
|
-
}
|
|
3852
|
-
};
|
|
3853
|
-
}
|
|
3854
|
-
function statusFile(dataDir, fileName, records) {
|
|
3855
|
-
const path = join(dataDir, fileName);
|
|
3856
|
-
return { path, exists: existsSync(path), records };
|
|
3857
|
-
}
|
|
3858
3750
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
3859
3751
|
function buildSignatureBase(timestamp, body) {
|
|
3860
3752
|
return `${timestamp}.${body}`;
|
|
@@ -4080,7 +3972,7 @@ class EventsClient {
|
|
|
4080
3972
|
}
|
|
4081
3973
|
return deliveries;
|
|
4082
3974
|
}
|
|
4083
|
-
async
|
|
3975
|
+
async testChannel(id, input = {}) {
|
|
4084
3976
|
const channel = await this.store.getChannel(id);
|
|
4085
3977
|
if (!channel)
|
|
4086
3978
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -4097,34 +3989,6 @@ class EventsClient {
|
|
|
4097
3989
|
time: input.time,
|
|
4098
3990
|
id: input.id
|
|
4099
3991
|
});
|
|
4100
|
-
const matched = channelMatchesEvent(channel, event);
|
|
4101
|
-
return {
|
|
4102
|
-
channelId: channel.id,
|
|
4103
|
-
matched,
|
|
4104
|
-
event,
|
|
4105
|
-
filters: channel.filters,
|
|
4106
|
-
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
4107
|
-
};
|
|
4108
|
-
}
|
|
4109
|
-
async testChannel(id, input = {}, options = {}) {
|
|
4110
|
-
const channel = await this.store.getChannel(id);
|
|
4111
|
-
if (!channel)
|
|
4112
|
-
throw new Error(`Channel not found: ${id}`);
|
|
4113
|
-
const match = await this.matchChannel(id, input);
|
|
4114
|
-
const event = match.event;
|
|
4115
|
-
if (options.honorFilters && !match.matched) {
|
|
4116
|
-
const timestamp = new Date().toISOString();
|
|
4117
|
-
const result2 = createDeliveryResult(event, channel, [{
|
|
4118
|
-
attempt: 1,
|
|
4119
|
-
status: "skipped",
|
|
4120
|
-
startedAt: timestamp,
|
|
4121
|
-
completedAt: timestamp,
|
|
4122
|
-
error: match.reason
|
|
4123
|
-
}]);
|
|
4124
|
-
result2.metadata = { reason: "filter_mismatch" };
|
|
4125
|
-
await this.store.appendDelivery(result2);
|
|
4126
|
-
return result2;
|
|
4127
|
-
}
|
|
4128
3992
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
4129
3993
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
4130
3994
|
await this.store.appendDelivery(result);
|
|
@@ -4235,76 +4099,6 @@ function normalizeRetryPolicy(policy) {
|
|
|
4235
4099
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
4236
4100
|
};
|
|
4237
4101
|
}
|
|
4238
|
-
function parseFieldMatchers(values, label, typed = false) {
|
|
4239
|
-
if (!values?.length)
|
|
4240
|
-
return;
|
|
4241
|
-
const result = {};
|
|
4242
|
-
for (const value of values) {
|
|
4243
|
-
const parsed = parseMatcherExpression(value, label);
|
|
4244
|
-
const path = parsed.path;
|
|
4245
|
-
if (path in result)
|
|
4246
|
-
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
4247
|
-
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
4248
|
-
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
4249
|
-
}
|
|
4250
|
-
return result;
|
|
4251
|
-
}
|
|
4252
|
-
function parseFilterOptions(options) {
|
|
4253
|
-
const filter2 = {};
|
|
4254
|
-
if (options.source)
|
|
4255
|
-
filter2.source = options.source;
|
|
4256
|
-
if (options.type)
|
|
4257
|
-
filter2.type = options.type;
|
|
4258
|
-
if (options.subject)
|
|
4259
|
-
filter2.subject = options.subject;
|
|
4260
|
-
if (options.severity)
|
|
4261
|
-
filter2.severity = options.severity;
|
|
4262
|
-
const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
|
|
4263
|
-
const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
|
|
4264
|
-
if (Object.keys(data).length > 0)
|
|
4265
|
-
filter2.data = data;
|
|
4266
|
-
if (Object.keys(metadata).length > 0)
|
|
4267
|
-
filter2.metadata = metadata;
|
|
4268
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
4269
|
-
}
|
|
4270
|
-
function mergeMatchers(...records) {
|
|
4271
|
-
const result = {};
|
|
4272
|
-
for (const record of records) {
|
|
4273
|
-
if (!record)
|
|
4274
|
-
continue;
|
|
4275
|
-
for (const [path, value] of Object.entries(record)) {
|
|
4276
|
-
if (path in result)
|
|
4277
|
-
throw new Error(`Duplicate filter path: ${path}`);
|
|
4278
|
-
result[path] = value;
|
|
4279
|
-
}
|
|
4280
|
-
}
|
|
4281
|
-
return result;
|
|
4282
|
-
}
|
|
4283
|
-
function parseTypedMatcherValue(value, label) {
|
|
4284
|
-
const parsed = JSON.parse(value);
|
|
4285
|
-
if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
4286
|
-
return parsed;
|
|
4287
|
-
}
|
|
4288
|
-
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
4289
|
-
}
|
|
4290
|
-
function parseMatcherExpression(value, label) {
|
|
4291
|
-
const negativeSeparator = value.indexOf("!=");
|
|
4292
|
-
if (negativeSeparator > 0) {
|
|
4293
|
-
return {
|
|
4294
|
-
path: value.slice(0, negativeSeparator),
|
|
4295
|
-
rawValue: value.slice(negativeSeparator + 2),
|
|
4296
|
-
negated: true
|
|
4297
|
-
};
|
|
4298
|
-
}
|
|
4299
|
-
const separator = value.indexOf("=");
|
|
4300
|
-
if (separator <= 0)
|
|
4301
|
-
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
4302
|
-
return {
|
|
4303
|
-
path: value.slice(0, separator),
|
|
4304
|
-
rawValue: value.slice(separator + 1),
|
|
4305
|
-
negated: false
|
|
4306
|
-
};
|
|
4307
|
-
}
|
|
4308
4102
|
function parseJsonObject(value, fallback) {
|
|
4309
4103
|
if (!value)
|
|
4310
4104
|
return fallback;
|
|
@@ -4326,6 +4120,18 @@ function parseHeaders(values) {
|
|
|
4326
4120
|
}
|
|
4327
4121
|
return headers;
|
|
4328
4122
|
}
|
|
4123
|
+
function parseFilter(options) {
|
|
4124
|
+
const filter2 = {};
|
|
4125
|
+
if (options.source)
|
|
4126
|
+
filter2.source = options.source;
|
|
4127
|
+
if (options.type)
|
|
4128
|
+
filter2.type = options.type;
|
|
4129
|
+
if (options.subject)
|
|
4130
|
+
filter2.subject = options.subject;
|
|
4131
|
+
if (options.severity)
|
|
4132
|
+
filter2.severity = options.severity;
|
|
4133
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
4134
|
+
}
|
|
4329
4135
|
function createClient(options) {
|
|
4330
4136
|
if (options.createClient)
|
|
4331
4137
|
return options.createClient();
|
|
@@ -4343,16 +4149,16 @@ function hasJsonOption(options) {
|
|
|
4343
4149
|
function wantsJson(actionOptions, command) {
|
|
4344
4150
|
return hasJsonOption(actionOptions) || hasJsonOption(command);
|
|
4345
4151
|
}
|
|
4346
|
-
function
|
|
4347
|
-
const
|
|
4348
|
-
|
|
4152
|
+
function registerWebhookCommands(program, options) {
|
|
4153
|
+
const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
|
|
4154
|
+
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) => {
|
|
4349
4155
|
const timestamp = new Date().toISOString();
|
|
4350
4156
|
const channel = {
|
|
4351
4157
|
id: actionOptions.id,
|
|
4352
4158
|
name: actionOptions.name,
|
|
4353
4159
|
enabled: !actionOptions.disabled,
|
|
4354
4160
|
transport: actionOptions.transport,
|
|
4355
|
-
filters:
|
|
4161
|
+
filters: parseFilter(actionOptions),
|
|
4356
4162
|
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
4357
4163
|
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
4358
4164
|
createdAt: timestamp,
|
|
@@ -4368,51 +4174,35 @@ function registerChannelCommands(program, options) {
|
|
|
4368
4174
|
const saved = await createClient(options).addChannel(channel);
|
|
4369
4175
|
print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
|
|
4370
4176
|
});
|
|
4371
|
-
|
|
4372
|
-
const
|
|
4177
|
+
webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
4178
|
+
const channels = await createClient(options).listChannels();
|
|
4373
4179
|
if (wantsJson(actionOptions, command)) {
|
|
4374
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(
|
|
4180
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels), null, 2));
|
|
4375
4181
|
return;
|
|
4376
4182
|
}
|
|
4377
|
-
if (!
|
|
4183
|
+
if (!channels.length) {
|
|
4378
4184
|
console.log("No channels configured.");
|
|
4379
4185
|
return;
|
|
4380
4186
|
}
|
|
4381
|
-
for (const channel of
|
|
4187
|
+
for (const channel of channels) {
|
|
4382
4188
|
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
4383
4189
|
}
|
|
4384
4190
|
});
|
|
4385
|
-
|
|
4386
|
-
const status = await getEventsStatus(options.dataDir);
|
|
4387
|
-
print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
|
|
4388
|
-
});
|
|
4389
|
-
channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
4191
|
+
webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
4390
4192
|
const removed = await createClient(options).removeChannel(id);
|
|
4391
4193
|
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
4392
4194
|
});
|
|
4393
|
-
|
|
4195
|
+
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) => {
|
|
4394
4196
|
const result = await createClient(options).testChannel(id, {
|
|
4395
|
-
source:
|
|
4396
|
-
type: actionOptions.type,
|
|
4397
|
-
subject: actionOptions.subject ?? id,
|
|
4398
|
-
message: actionOptions.message,
|
|
4399
|
-
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
4400
|
-
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
4401
|
-
}, { honorFilters: actionOptions.honorFilters });
|
|
4402
|
-
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
4403
|
-
});
|
|
4404
|
-
channels.command("match").description("Check whether a sample event matches one channel without delivering").argument("<id>", "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) => {
|
|
4405
|
-
const result = await createClient(options).matchChannel(id, {
|
|
4406
|
-
source: actionOptions.source ?? options.source,
|
|
4197
|
+
source: options.source,
|
|
4407
4198
|
type: actionOptions.type,
|
|
4408
4199
|
subject: actionOptions.subject ?? id,
|
|
4409
4200
|
message: actionOptions.message,
|
|
4410
|
-
data: parseJsonObject(actionOptions.data, { test: true })
|
|
4411
|
-
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
4201
|
+
data: parseJsonObject(actionOptions.data, { test: true })
|
|
4412
4202
|
});
|
|
4413
|
-
print(result, wantsJson(actionOptions, command), `${result.
|
|
4203
|
+
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
4414
4204
|
});
|
|
4415
|
-
return
|
|
4205
|
+
return webhooks;
|
|
4416
4206
|
}
|
|
4417
4207
|
function registerEventCommands(program, options) {
|
|
4418
4208
|
const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
@@ -4460,7 +4250,7 @@ function registerEventCommands(program, options) {
|
|
|
4460
4250
|
return events;
|
|
4461
4251
|
}
|
|
4462
4252
|
function registerEventsCommands(program, options) {
|
|
4463
|
-
|
|
4253
|
+
registerWebhookCommands(program, options);
|
|
4464
4254
|
registerEventCommands(program, options);
|
|
4465
4255
|
}
|
|
4466
4256
|
function parseNumber(value) {
|
package/dist/mcp/index.js
CHANGED
|
@@ -1549,7 +1549,7 @@ var init_sync_dir = __esm(() => {
|
|
|
1549
1549
|
var require_package = __commonJS((exports, module) => {
|
|
1550
1550
|
module.exports = {
|
|
1551
1551
|
name: "@hasna/instructions",
|
|
1552
|
-
version: "0.3.
|
|
1552
|
+
version: "0.3.1",
|
|
1553
1553
|
description: "AI coding agent instruction & configuration manager \u2014 store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
|
|
1554
1554
|
type: "module",
|
|
1555
1555
|
main: "dist/index.js",
|
|
@@ -1625,7 +1625,7 @@ var require_package = __commonJS((exports, module) => {
|
|
|
1625
1625
|
author: "Andrei Hasna <andrei@hasna.com>",
|
|
1626
1626
|
license: "Apache-2.0",
|
|
1627
1627
|
dependencies: {
|
|
1628
|
-
"@hasna/contracts": "
|
|
1628
|
+
"@hasna/contracts": "0.4.2",
|
|
1629
1629
|
"@hasna/events": "^0.1.6",
|
|
1630
1630
|
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
1631
1631
|
chalk: "^5.4.1",
|