@hasna/instructions 0.4.2 → 0.4.3
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/index.js +258 -48
- package/dist/mcp/index.js +1 -1
- package/dist/server/index.js +57 -37
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -4486,7 +4486,7 @@ var init_package_manager_guard = __esm(() => {
|
|
|
4486
4486
|
];
|
|
4487
4487
|
});
|
|
4488
4488
|
|
|
4489
|
-
// node_modules/@hasna/events/dist/commander.js
|
|
4489
|
+
// node_modules/.pnpm/@hasna+events@0.1.13/node_modules/@hasna/events/dist/commander.js
|
|
4490
4490
|
import { chmod, mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
4491
4491
|
import { existsSync } from "fs";
|
|
4492
4492
|
import { homedir } from "os";
|
|
@@ -4503,29 +4503,83 @@ function getPathValue(input, path) {
|
|
|
4503
4503
|
return;
|
|
4504
4504
|
}, input);
|
|
4505
4505
|
}
|
|
4506
|
-
function
|
|
4507
|
-
const
|
|
4508
|
-
|
|
4506
|
+
function getFieldValues(input, path) {
|
|
4507
|
+
const values = [];
|
|
4508
|
+
const push = (value) => {
|
|
4509
|
+
if (!values.some((item) => Object.is(item, value)))
|
|
4510
|
+
values.push(value);
|
|
4511
|
+
};
|
|
4512
|
+
if (path.includes(".") && path in input)
|
|
4513
|
+
push(input[path]);
|
|
4514
|
+
const nestedValue = getPathValue(input, path);
|
|
4515
|
+
if (nestedValue !== undefined || !path.includes("."))
|
|
4516
|
+
push(nestedValue);
|
|
4517
|
+
return values;
|
|
4518
|
+
}
|
|
4519
|
+
function wildcardToRegExp(pattern, options = {}) {
|
|
4520
|
+
let body = "";
|
|
4521
|
+
for (let index = 0;index < pattern.length; index += 1) {
|
|
4522
|
+
const char = pattern[index];
|
|
4523
|
+
if (char === "*") {
|
|
4524
|
+
if (pattern[index + 1] === "*") {
|
|
4525
|
+
body += ".*";
|
|
4526
|
+
index += 1;
|
|
4527
|
+
} else {
|
|
4528
|
+
body += options.segmentSafe ? "[^/]*" : ".*";
|
|
4529
|
+
}
|
|
4530
|
+
} else {
|
|
4531
|
+
body += char.replace(/[|\\{}()[\]^$+?.]/g, "\\$&");
|
|
4532
|
+
}
|
|
4533
|
+
}
|
|
4534
|
+
return new RegExp(`^${body}$`);
|
|
4509
4535
|
}
|
|
4510
|
-
function matchString(value, matcher) {
|
|
4536
|
+
function matchString(value, matcher, options = {}) {
|
|
4511
4537
|
if (matcher === undefined)
|
|
4512
4538
|
return true;
|
|
4513
4539
|
if (value === undefined)
|
|
4514
4540
|
return false;
|
|
4515
4541
|
const matchers = Array.isArray(matcher) ? matcher : [matcher];
|
|
4516
|
-
return matchers.some((item) => wildcardToRegExp(item).test(value));
|
|
4542
|
+
return matchers.some((item) => wildcardToRegExp(item, options).test(value));
|
|
4517
4543
|
}
|
|
4518
4544
|
function matchRecord(input, matcher) {
|
|
4519
4545
|
if (!matcher)
|
|
4520
4546
|
return true;
|
|
4521
4547
|
return Object.entries(matcher).every(([path, expected]) => {
|
|
4522
|
-
const
|
|
4523
|
-
|
|
4524
|
-
return matchString(actual === undefined ? undefined : String(actual), expected);
|
|
4525
|
-
}
|
|
4526
|
-
return actual === expected;
|
|
4548
|
+
const actualValues = getFieldValues(input, path);
|
|
4549
|
+
return matchField(actualValues, expected, path);
|
|
4527
4550
|
});
|
|
4528
4551
|
}
|
|
4552
|
+
function matchField(actualValues, expected, path) {
|
|
4553
|
+
if (isNegativeMatcher(expected)) {
|
|
4554
|
+
return !actualValues.some((actual) => matchPositiveField(actual, expected.not, path));
|
|
4555
|
+
}
|
|
4556
|
+
return actualValues.some((actual) => matchPositiveField(actual, expected, path));
|
|
4557
|
+
}
|
|
4558
|
+
function matchPositiveField(actual, expected, path) {
|
|
4559
|
+
if (typeof expected === "string" || Array.isArray(expected)) {
|
|
4560
|
+
return stringCandidates(actual).some((candidate) => matchString(candidate, expected, {
|
|
4561
|
+
segmentSafe: path.endsWith("_path") || path.endsWith(".path")
|
|
4562
|
+
}));
|
|
4563
|
+
}
|
|
4564
|
+
if (Array.isArray(actual)) {
|
|
4565
|
+
return actual.some((item) => item === expected);
|
|
4566
|
+
}
|
|
4567
|
+
return actual === expected;
|
|
4568
|
+
}
|
|
4569
|
+
function stringCandidates(actual) {
|
|
4570
|
+
if (actual === undefined)
|
|
4571
|
+
return [];
|
|
4572
|
+
if (Array.isArray(actual)) {
|
|
4573
|
+
return actual.flatMap((item) => isPrimitiveFieldValue(item) ? [String(item)] : []);
|
|
4574
|
+
}
|
|
4575
|
+
return [String(actual)];
|
|
4576
|
+
}
|
|
4577
|
+
function isPrimitiveFieldValue(value) {
|
|
4578
|
+
return value === null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
|
|
4579
|
+
}
|
|
4580
|
+
function isNegativeMatcher(value) {
|
|
4581
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value) && "not" in value);
|
|
4582
|
+
}
|
|
4529
4583
|
function eventMatchesFilter(event, filter) {
|
|
4530
4584
|
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);
|
|
4531
4585
|
}
|
|
@@ -4541,6 +4595,14 @@ var HASNA_EVENTS_HOME_ENV = "HASNA_EVENTS_HOME";
|
|
|
4541
4595
|
function getEventsDataDir(override) {
|
|
4542
4596
|
return override || process.env[HASNA_EVENTS_DIR_ENV] || process.env[HASNA_EVENTS_HOME_ENV] || join(homedir(), ".hasna", "events");
|
|
4543
4597
|
}
|
|
4598
|
+
function getActiveEventsDirEnv() {
|
|
4599
|
+
if (process.env[HASNA_EVENTS_DIR_ENV])
|
|
4600
|
+
return HASNA_EVENTS_DIR_ENV;
|
|
4601
|
+
if (process.env[HASNA_EVENTS_HOME_ENV])
|
|
4602
|
+
return HASNA_EVENTS_HOME_ENV;
|
|
4603
|
+
return null;
|
|
4604
|
+
}
|
|
4605
|
+
|
|
4544
4606
|
class JsonEventsStore {
|
|
4545
4607
|
dataDir;
|
|
4546
4608
|
channelsPath;
|
|
@@ -4652,6 +4714,52 @@ class JsonEventsStore {
|
|
|
4652
4714
|
});
|
|
4653
4715
|
}
|
|
4654
4716
|
}
|
|
4717
|
+
async function getEventsStatus(dataDir) {
|
|
4718
|
+
const store = new JsonEventsStore(dataDir);
|
|
4719
|
+
await store.init();
|
|
4720
|
+
const [channels, events, deliveries] = await Promise.all([
|
|
4721
|
+
store.listChannels(),
|
|
4722
|
+
store.listEvents(),
|
|
4723
|
+
store.listDeliveries()
|
|
4724
|
+
]);
|
|
4725
|
+
const transports = channels.reduce((counts, channel) => {
|
|
4726
|
+
counts[channel.transport] = (counts[channel.transport] ?? 0) + 1;
|
|
4727
|
+
return counts;
|
|
4728
|
+
}, {});
|
|
4729
|
+
return {
|
|
4730
|
+
service: "events",
|
|
4731
|
+
schemaVersion: "1.0",
|
|
4732
|
+
dataDir: store.dataDir,
|
|
4733
|
+
env: {
|
|
4734
|
+
primary: HASNA_EVENTS_DIR_ENV,
|
|
4735
|
+
fallback: HASNA_EVENTS_HOME_ENV,
|
|
4736
|
+
active: getActiveEventsDirEnv()
|
|
4737
|
+
},
|
|
4738
|
+
files: {
|
|
4739
|
+
channels: statusFile(store.dataDir, "channels.json", channels.length),
|
|
4740
|
+
events: statusFile(store.dataDir, "events.json", events.length),
|
|
4741
|
+
deliveries: statusFile(store.dataDir, "deliveries.json", deliveries.length)
|
|
4742
|
+
},
|
|
4743
|
+
counts: {
|
|
4744
|
+
channels: channels.length,
|
|
4745
|
+
enabledChannels: channels.filter((channel) => channel.enabled).length,
|
|
4746
|
+
disabledChannels: channels.filter((channel) => !channel.enabled).length,
|
|
4747
|
+
events: events.length,
|
|
4748
|
+
deliveries: deliveries.length
|
|
4749
|
+
},
|
|
4750
|
+
transports,
|
|
4751
|
+
safety: {
|
|
4752
|
+
includesEventPayloads: false,
|
|
4753
|
+
includesWebhookSecrets: false,
|
|
4754
|
+
listOutputsRedactSecrets: true,
|
|
4755
|
+
statusOutputIsMetadataOnly: true
|
|
4756
|
+
}
|
|
4757
|
+
};
|
|
4758
|
+
}
|
|
4759
|
+
function statusFile(dataDir, fileName, records) {
|
|
4760
|
+
const path = join(dataDir, fileName);
|
|
4761
|
+
return { path, exists: existsSync(path), records };
|
|
4762
|
+
}
|
|
4655
4763
|
var DEFAULT_SIGNATURE_TOLERANCE_MS = 5 * 60 * 1000;
|
|
4656
4764
|
function buildSignatureBase(timestamp, body) {
|
|
4657
4765
|
return `${timestamp}.${body}`;
|
|
@@ -4877,7 +4985,7 @@ class EventsClient {
|
|
|
4877
4985
|
}
|
|
4878
4986
|
return deliveries;
|
|
4879
4987
|
}
|
|
4880
|
-
async
|
|
4988
|
+
async matchChannel(id, input = {}) {
|
|
4881
4989
|
const channel = await this.store.getChannel(id);
|
|
4882
4990
|
if (!channel)
|
|
4883
4991
|
throw new Error(`Channel not found: ${id}`);
|
|
@@ -4894,6 +5002,34 @@ class EventsClient {
|
|
|
4894
5002
|
time: input.time,
|
|
4895
5003
|
id: input.id
|
|
4896
5004
|
});
|
|
5005
|
+
const matched = channelMatchesEvent(channel, event);
|
|
5006
|
+
return {
|
|
5007
|
+
channelId: channel.id,
|
|
5008
|
+
matched,
|
|
5009
|
+
event,
|
|
5010
|
+
filters: channel.filters,
|
|
5011
|
+
reason: matched ? undefined : channel.enabled ? "event did not match channel filters" : "channel is disabled"
|
|
5012
|
+
};
|
|
5013
|
+
}
|
|
5014
|
+
async testChannel(id, input = {}, options = {}) {
|
|
5015
|
+
const channel = await this.store.getChannel(id);
|
|
5016
|
+
if (!channel)
|
|
5017
|
+
throw new Error(`Channel not found: ${id}`);
|
|
5018
|
+
const match = await this.matchChannel(id, input);
|
|
5019
|
+
const event = match.event;
|
|
5020
|
+
if (options.honorFilters && !match.matched) {
|
|
5021
|
+
const timestamp = new Date().toISOString();
|
|
5022
|
+
const result2 = createDeliveryResult(event, channel, [{
|
|
5023
|
+
attempt: 1,
|
|
5024
|
+
status: "skipped",
|
|
5025
|
+
startedAt: timestamp,
|
|
5026
|
+
completedAt: timestamp,
|
|
5027
|
+
error: match.reason
|
|
5028
|
+
}]);
|
|
5029
|
+
result2.metadata = { reason: "filter_mismatch" };
|
|
5030
|
+
await this.store.appendDelivery(result2);
|
|
5031
|
+
return result2;
|
|
5032
|
+
}
|
|
4897
5033
|
const eventForChannel = await this.applyRedaction(event, channel);
|
|
4898
5034
|
const result = await this.deliverWithRetry(eventForChannel, channel);
|
|
4899
5035
|
await this.store.appendDelivery(result);
|
|
@@ -5004,6 +5140,76 @@ function normalizeRetryPolicy(policy) {
|
|
|
5004
5140
|
multiplier: Math.max(1, policy?.multiplier ?? 2)
|
|
5005
5141
|
};
|
|
5006
5142
|
}
|
|
5143
|
+
function parseFieldMatchers(values, label, typed = false) {
|
|
5144
|
+
if (!values?.length)
|
|
5145
|
+
return;
|
|
5146
|
+
const result = {};
|
|
5147
|
+
for (const value of values) {
|
|
5148
|
+
const parsed = parseMatcherExpression(value, label);
|
|
5149
|
+
const path = parsed.path;
|
|
5150
|
+
if (path in result)
|
|
5151
|
+
throw new Error(`Duplicate ${label} filter path: ${path}`);
|
|
5152
|
+
const matcherValue = typed ? parseTypedMatcherValue(parsed.rawValue, label) : parsed.rawValue;
|
|
5153
|
+
result[path] = parsed.negated ? { not: matcherValue } : matcherValue;
|
|
5154
|
+
}
|
|
5155
|
+
return result;
|
|
5156
|
+
}
|
|
5157
|
+
function parseFilterOptions(options) {
|
|
5158
|
+
const filter2 = {};
|
|
5159
|
+
if (options.source)
|
|
5160
|
+
filter2.source = options.source;
|
|
5161
|
+
if (options.type)
|
|
5162
|
+
filter2.type = options.type;
|
|
5163
|
+
if (options.subject)
|
|
5164
|
+
filter2.subject = options.subject;
|
|
5165
|
+
if (options.severity)
|
|
5166
|
+
filter2.severity = options.severity;
|
|
5167
|
+
const data = mergeMatchers(parseFieldMatchers(options.data, "data"), parseFieldMatchers(options.dataJson, "data-json", true));
|
|
5168
|
+
const metadata = mergeMatchers(parseFieldMatchers(options.metadata, "metadata"), parseFieldMatchers(options.metadataJson, "metadata-json", true));
|
|
5169
|
+
if (Object.keys(data).length > 0)
|
|
5170
|
+
filter2.data = data;
|
|
5171
|
+
if (Object.keys(metadata).length > 0)
|
|
5172
|
+
filter2.metadata = metadata;
|
|
5173
|
+
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
5174
|
+
}
|
|
5175
|
+
function mergeMatchers(...records) {
|
|
5176
|
+
const result = {};
|
|
5177
|
+
for (const record of records) {
|
|
5178
|
+
if (!record)
|
|
5179
|
+
continue;
|
|
5180
|
+
for (const [path, value] of Object.entries(record)) {
|
|
5181
|
+
if (path in result)
|
|
5182
|
+
throw new Error(`Duplicate filter path: ${path}`);
|
|
5183
|
+
result[path] = value;
|
|
5184
|
+
}
|
|
5185
|
+
}
|
|
5186
|
+
return result;
|
|
5187
|
+
}
|
|
5188
|
+
function parseTypedMatcherValue(value, label) {
|
|
5189
|
+
const parsed = JSON.parse(value);
|
|
5190
|
+
if (parsed === null || typeof parsed === "string" || typeof parsed === "number" || typeof parsed === "boolean" || Array.isArray(parsed) && parsed.every((item) => typeof item === "string")) {
|
|
5191
|
+
return parsed;
|
|
5192
|
+
}
|
|
5193
|
+
throw new Error(`${label} filter JSON values must be string, string[], number, boolean, or null`);
|
|
5194
|
+
}
|
|
5195
|
+
function parseMatcherExpression(value, label) {
|
|
5196
|
+
const negativeSeparator = value.indexOf("!=");
|
|
5197
|
+
if (negativeSeparator > 0) {
|
|
5198
|
+
return {
|
|
5199
|
+
path: value.slice(0, negativeSeparator),
|
|
5200
|
+
rawValue: value.slice(negativeSeparator + 2),
|
|
5201
|
+
negated: true
|
|
5202
|
+
};
|
|
5203
|
+
}
|
|
5204
|
+
const separator = value.indexOf("=");
|
|
5205
|
+
if (separator <= 0)
|
|
5206
|
+
throw new Error(`Invalid ${label} filter, expected path=value or path!=value: ${value}`);
|
|
5207
|
+
return {
|
|
5208
|
+
path: value.slice(0, separator),
|
|
5209
|
+
rawValue: value.slice(separator + 1),
|
|
5210
|
+
negated: false
|
|
5211
|
+
};
|
|
5212
|
+
}
|
|
5007
5213
|
function parseJsonObject(value, fallback) {
|
|
5008
5214
|
if (!value)
|
|
5009
5215
|
return fallback;
|
|
@@ -5025,18 +5231,6 @@ function parseHeaders(values) {
|
|
|
5025
5231
|
}
|
|
5026
5232
|
return headers;
|
|
5027
5233
|
}
|
|
5028
|
-
function parseFilter(options) {
|
|
5029
|
-
const filter2 = {};
|
|
5030
|
-
if (options.source)
|
|
5031
|
-
filter2.source = options.source;
|
|
5032
|
-
if (options.type)
|
|
5033
|
-
filter2.type = options.type;
|
|
5034
|
-
if (options.subject)
|
|
5035
|
-
filter2.subject = options.subject;
|
|
5036
|
-
if (options.severity)
|
|
5037
|
-
filter2.severity = options.severity;
|
|
5038
|
-
return Object.keys(filter2).length > 0 ? [filter2] : undefined;
|
|
5039
|
-
}
|
|
5040
5234
|
function createClient(options) {
|
|
5041
5235
|
if (options.createClient)
|
|
5042
5236
|
return options.createClient();
|
|
@@ -5054,16 +5248,16 @@ function hasJsonOption(options) {
|
|
|
5054
5248
|
function wantsJson(actionOptions, command) {
|
|
5055
5249
|
return hasJsonOption(actionOptions) || hasJsonOption(command);
|
|
5056
5250
|
}
|
|
5057
|
-
function
|
|
5058
|
-
const
|
|
5059
|
-
|
|
5251
|
+
function registerChannelCommands(program, options) {
|
|
5252
|
+
const channels = program.command(options.channelsCommandName ?? "channels").description("Manage Hasna event channels");
|
|
5253
|
+
channels.command("add").description("Add or replace a channel").argument("<target>", "Webhook URL or command binary").requiredOption("--id <id>", "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) => {
|
|
5060
5254
|
const timestamp = new Date().toISOString();
|
|
5061
5255
|
const channel = {
|
|
5062
5256
|
id: actionOptions.id,
|
|
5063
5257
|
name: actionOptions.name,
|
|
5064
5258
|
enabled: !actionOptions.disabled,
|
|
5065
5259
|
transport: actionOptions.transport,
|
|
5066
|
-
filters:
|
|
5260
|
+
filters: parseFilterOptions(actionOptions),
|
|
5067
5261
|
retry: actionOptions.retryAttempts || actionOptions.retryBackoffMs ? { maxAttempts: actionOptions.retryAttempts, backoffMs: actionOptions.retryBackoffMs } : undefined,
|
|
5068
5262
|
redact: actionOptions.redact?.length ? { paths: actionOptions.redact } : undefined,
|
|
5069
5263
|
createdAt: timestamp,
|
|
@@ -5079,35 +5273,51 @@ function registerWebhookCommands(program, options) {
|
|
|
5079
5273
|
const saved = await createClient(options).addChannel(channel);
|
|
5080
5274
|
print(sanitizeChannelForOutput(saved), wantsJson(actionOptions, command), `Added ${saved.transport} channel ${saved.id}`);
|
|
5081
5275
|
});
|
|
5082
|
-
|
|
5083
|
-
const
|
|
5276
|
+
channels.command("list").description("List configured channels").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
5277
|
+
const channels2 = await createClient(options).listChannels();
|
|
5084
5278
|
if (wantsJson(actionOptions, command)) {
|
|
5085
|
-
console.log(JSON.stringify(sanitizeChannelsForOutput(
|
|
5279
|
+
console.log(JSON.stringify(sanitizeChannelsForOutput(channels2), null, 2));
|
|
5086
5280
|
return;
|
|
5087
5281
|
}
|
|
5088
|
-
if (!
|
|
5282
|
+
if (!channels2.length) {
|
|
5089
5283
|
console.log("No channels configured.");
|
|
5090
5284
|
return;
|
|
5091
5285
|
}
|
|
5092
|
-
for (const channel of
|
|
5286
|
+
for (const channel of channels2) {
|
|
5093
5287
|
console.log(`${channel.id} ${channel.enabled ? "enabled" : "disabled"} ${channel.transport} ${channel.webhook?.url ?? channel.command?.command ?? channel.transport}`);
|
|
5094
5288
|
}
|
|
5095
5289
|
});
|
|
5096
|
-
|
|
5290
|
+
channels.command("status").description("Show events channel storage status").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
|
|
5291
|
+
const status = await getEventsStatus(options.dataDir);
|
|
5292
|
+
print(status, wantsJson(actionOptions, command), `events dataDir: ${status.dataDir}`);
|
|
5293
|
+
});
|
|
5294
|
+
channels.command("remove").description("Remove a channel").argument("<id>", "Channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
|
|
5097
5295
|
const removed = await createClient(options).removeChannel(id);
|
|
5098
5296
|
print({ removed }, wantsJson(actionOptions, command), removed ? `Removed ${id}` : `Channel not found: ${id}`);
|
|
5099
5297
|
});
|
|
5100
|
-
|
|
5298
|
+
channels.command("test").description("Send a test event to one channel").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 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) => {
|
|
5101
5299
|
const result = await createClient(options).testChannel(id, {
|
|
5102
|
-
source: options.source,
|
|
5300
|
+
source: actionOptions.source ?? options.source,
|
|
5103
5301
|
type: actionOptions.type,
|
|
5104
5302
|
subject: actionOptions.subject ?? id,
|
|
5105
5303
|
message: actionOptions.message,
|
|
5106
|
-
data: parseJsonObject(actionOptions.data, { test: true })
|
|
5107
|
-
|
|
5304
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
5305
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
5306
|
+
}, { honorFilters: actionOptions.honorFilters });
|
|
5108
5307
|
print(result, wantsJson(actionOptions, command), `${result.status}: ${result.channelId}`);
|
|
5109
5308
|
});
|
|
5110
|
-
|
|
5309
|
+
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) => {
|
|
5310
|
+
const result = await createClient(options).matchChannel(id, {
|
|
5311
|
+
source: actionOptions.source ?? options.source,
|
|
5312
|
+
type: actionOptions.type,
|
|
5313
|
+
subject: actionOptions.subject ?? id,
|
|
5314
|
+
message: actionOptions.message,
|
|
5315
|
+
data: parseJsonObject(actionOptions.data, { test: true }),
|
|
5316
|
+
metadata: parseJsonObject(actionOptions.metadata, {})
|
|
5317
|
+
});
|
|
5318
|
+
print(result, wantsJson(actionOptions, command), `${result.matched ? "matched" : "skipped"}: ${result.channelId}`);
|
|
5319
|
+
});
|
|
5320
|
+
return channels;
|
|
5111
5321
|
}
|
|
5112
5322
|
function registerEventCommands(program, options) {
|
|
5113
5323
|
const events = program.command(options.eventsCommandName ?? "events").description("Emit, list, and replay Hasna events");
|
|
@@ -5155,7 +5365,7 @@ function registerEventCommands(program, options) {
|
|
|
5155
5365
|
return events;
|
|
5156
5366
|
}
|
|
5157
5367
|
function registerEventsCommands(program, options) {
|
|
5158
|
-
|
|
5368
|
+
registerChannelCommands(program, options);
|
|
5159
5369
|
registerEventCommands(program, options);
|
|
5160
5370
|
}
|
|
5161
5371
|
function parseNumber(value) {
|
|
@@ -7098,14 +7308,14 @@ program.command("list").alias("ls").description("List stored configs").option("-
|
|
|
7098
7308
|
tags: opts.tag ? [opts.tag] : undefined,
|
|
7099
7309
|
search: opts.search
|
|
7100
7310
|
});
|
|
7101
|
-
if (configs.length === 0) {
|
|
7102
|
-
console.log(chalk.dim("No configs found."));
|
|
7103
|
-
return;
|
|
7104
|
-
}
|
|
7105
7311
|
if (fmt === "json") {
|
|
7106
7312
|
printJson(configs);
|
|
7107
7313
|
return;
|
|
7108
7314
|
}
|
|
7315
|
+
if (configs.length === 0) {
|
|
7316
|
+
console.log(chalk.dim("No configs found."));
|
|
7317
|
+
return;
|
|
7318
|
+
}
|
|
7109
7319
|
const page = paginate(configs, { limit: opts.limit, cursor: opts.cursor });
|
|
7110
7320
|
if (fmt === "compact") {
|
|
7111
7321
|
printConfigRows(page.items);
|
|
@@ -7334,14 +7544,14 @@ profileCmd.command("list").description("List all profiles").option("--brief", "c
|
|
|
7334
7544
|
const fmt = opts.json ? "json" : opts.verbose ? "table" : opts.brief ? "compact" : opts.format;
|
|
7335
7545
|
const store = resolveConfigStore();
|
|
7336
7546
|
const profiles = await store.listProfiles();
|
|
7337
|
-
if (profiles.length === 0) {
|
|
7338
|
-
console.log(chalk.dim("No profiles."));
|
|
7339
|
-
return;
|
|
7340
|
-
}
|
|
7341
7547
|
if (fmt === "json") {
|
|
7342
7548
|
printJson(profiles);
|
|
7343
7549
|
return;
|
|
7344
7550
|
}
|
|
7551
|
+
if (profiles.length === 0) {
|
|
7552
|
+
console.log(chalk.dim("No profiles."));
|
|
7553
|
+
return;
|
|
7554
|
+
}
|
|
7345
7555
|
const page = paginate(profiles, { limit: opts.limit, cursor: opts.cursor });
|
|
7346
7556
|
if (fmt === "compact")
|
|
7347
7557
|
console.log(`${pad("slug", 28)} ${pad("configs", 8)} ${pad("match", 36)} vars`);
|
package/dist/mcp/index.js
CHANGED
|
@@ -2040,7 +2040,7 @@ var init_sync_dir = __esm(() => {
|
|
|
2040
2040
|
var require_package = __commonJS((exports, module) => {
|
|
2041
2041
|
module.exports = {
|
|
2042
2042
|
name: "@hasna/instructions",
|
|
2043
|
-
version: "0.4.
|
|
2043
|
+
version: "0.4.3",
|
|
2044
2044
|
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.",
|
|
2045
2045
|
type: "module",
|
|
2046
2046
|
main: "dist/index.js",
|
package/dist/server/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
var __require = import.meta.require;
|
|
4
4
|
|
|
5
|
-
// node_modules/hono/dist/compose.js
|
|
5
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/compose.js
|
|
6
6
|
var compose = (middleware, onError, onNotFound) => {
|
|
7
7
|
return (context, next) => {
|
|
8
8
|
let index = -1;
|
|
@@ -46,21 +46,39 @@ var compose = (middleware, onError, onNotFound) => {
|
|
|
46
46
|
};
|
|
47
47
|
};
|
|
48
48
|
|
|
49
|
-
// node_modules/hono/dist/request/constants.js
|
|
49
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/request/constants.js
|
|
50
50
|
var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
|
|
51
51
|
|
|
52
|
-
// node_modules/hono/dist/utils/
|
|
52
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/utils/buffer.js
|
|
53
|
+
var bufferToFormData = (arrayBuffer, contentType) => {
|
|
54
|
+
const response = new Response(arrayBuffer, {
|
|
55
|
+
headers: {
|
|
56
|
+
"Content-Type": contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase())
|
|
57
|
+
}
|
|
58
|
+
});
|
|
59
|
+
return response.formData();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/utils/body.js
|
|
63
|
+
var isRawRequest = (request) => ("headers" in request);
|
|
53
64
|
var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
|
|
54
65
|
const { all = false, dot = false } = options;
|
|
55
|
-
const headers = request
|
|
66
|
+
const headers = isRawRequest(request) ? request.headers : request.raw.headers;
|
|
56
67
|
const contentType = headers.get("Content-Type");
|
|
57
|
-
|
|
68
|
+
const mediaType = contentType?.split(";")[0].trim().toLowerCase();
|
|
69
|
+
if (mediaType === "multipart/form-data" || mediaType === "application/x-www-form-urlencoded") {
|
|
58
70
|
return parseFormData(request, { all, dot });
|
|
59
71
|
}
|
|
60
72
|
return {};
|
|
61
73
|
};
|
|
62
74
|
async function parseFormData(request, options) {
|
|
63
|
-
const
|
|
75
|
+
const headers = isRawRequest(request) ? request.headers : request.raw.headers;
|
|
76
|
+
const arrayBuffer = await request.arrayBuffer();
|
|
77
|
+
const formDataPromise = bufferToFormData(arrayBuffer, headers.get("Content-Type") || "");
|
|
78
|
+
if (!isRawRequest(request)) {
|
|
79
|
+
request.bodyCache.formData = formDataPromise;
|
|
80
|
+
}
|
|
81
|
+
const formData = await formDataPromise;
|
|
64
82
|
if (formData) {
|
|
65
83
|
return convertFormDataToBodyData(formData, options);
|
|
66
84
|
}
|
|
@@ -120,7 +138,7 @@ var handleParsingNestedValues = (form, key, value) => {
|
|
|
120
138
|
});
|
|
121
139
|
};
|
|
122
140
|
|
|
123
|
-
// node_modules/hono/dist/utils/url.js
|
|
141
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/utils/url.js
|
|
124
142
|
var splitPath = (path) => {
|
|
125
143
|
const paths = path.split("/");
|
|
126
144
|
if (paths[0] === "") {
|
|
@@ -320,7 +338,7 @@ var getQueryParams = (url, key) => {
|
|
|
320
338
|
};
|
|
321
339
|
var decodeURIComponent_ = decodeURIComponent;
|
|
322
340
|
|
|
323
|
-
// node_modules/hono/dist/request.js
|
|
341
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/request.js
|
|
324
342
|
var tryDecodeURIComponent = (str) => tryDecode(str, decodeURIComponent_);
|
|
325
343
|
var HonoRequest = class {
|
|
326
344
|
raw;
|
|
@@ -402,6 +420,9 @@ var HonoRequest = class {
|
|
|
402
420
|
arrayBuffer() {
|
|
403
421
|
return this.#cachedBody("arrayBuffer");
|
|
404
422
|
}
|
|
423
|
+
bytes() {
|
|
424
|
+
return this.#cachedBody("arrayBuffer").then((buffer) => new Uint8Array(buffer));
|
|
425
|
+
}
|
|
405
426
|
blob() {
|
|
406
427
|
return this.#cachedBody("blob");
|
|
407
428
|
}
|
|
@@ -431,7 +452,7 @@ var HonoRequest = class {
|
|
|
431
452
|
}
|
|
432
453
|
};
|
|
433
454
|
|
|
434
|
-
// node_modules/hono/dist/utils/html.js
|
|
455
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/utils/html.js
|
|
435
456
|
var HtmlEscapedCallbackPhase = {
|
|
436
457
|
Stringify: 1,
|
|
437
458
|
BeforeStream: 2,
|
|
@@ -469,7 +490,7 @@ var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) =>
|
|
|
469
490
|
}
|
|
470
491
|
};
|
|
471
492
|
|
|
472
|
-
// node_modules/hono/dist/context.js
|
|
493
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/context.js
|
|
473
494
|
var TEXT_PLAIN = "text/plain; charset=UTF-8";
|
|
474
495
|
var setDefaultContentType = (contentType, headers) => {
|
|
475
496
|
return {
|
|
@@ -636,7 +657,7 @@ var Context = class {
|
|
|
636
657
|
};
|
|
637
658
|
};
|
|
638
659
|
|
|
639
|
-
// node_modules/hono/dist/router.js
|
|
660
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router.js
|
|
640
661
|
var METHOD_NAME_ALL = "ALL";
|
|
641
662
|
var METHOD_NAME_ALL_LOWERCASE = "all";
|
|
642
663
|
var METHODS = ["get", "post", "put", "delete", "options", "patch"];
|
|
@@ -644,10 +665,10 @@ var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is
|
|
|
644
665
|
var UnsupportedPathError = class extends Error {
|
|
645
666
|
};
|
|
646
667
|
|
|
647
|
-
// node_modules/hono/dist/utils/constants.js
|
|
668
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/utils/constants.js
|
|
648
669
|
var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
|
|
649
670
|
|
|
650
|
-
// node_modules/hono/dist/hono-base.js
|
|
671
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/hono-base.js
|
|
651
672
|
var notFoundHandler = (c) => {
|
|
652
673
|
return c.text("404 Not Found", 404);
|
|
653
674
|
};
|
|
@@ -738,7 +759,7 @@ var Hono = class _Hono {
|
|
|
738
759
|
handler = async (c, next) => (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res;
|
|
739
760
|
handler[COMPOSED_HANDLER] = r.handler;
|
|
740
761
|
}
|
|
741
|
-
subApp.#addRoute(r.method, r.path, handler);
|
|
762
|
+
subApp.#addRoute(r.method, r.path, handler, r.basePath);
|
|
742
763
|
});
|
|
743
764
|
return this;
|
|
744
765
|
}
|
|
@@ -785,7 +806,7 @@ var Hono = class _Hono {
|
|
|
785
806
|
const pathPrefixLength = mergedPath === "/" ? 0 : mergedPath.length;
|
|
786
807
|
return (request) => {
|
|
787
808
|
const url = new URL(request.url);
|
|
788
|
-
url.pathname =
|
|
809
|
+
url.pathname = this.getPath(request).slice(pathPrefixLength) || "/";
|
|
789
810
|
return new Request(url, request);
|
|
790
811
|
};
|
|
791
812
|
})();
|
|
@@ -799,10 +820,15 @@ var Hono = class _Hono {
|
|
|
799
820
|
this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler);
|
|
800
821
|
return this;
|
|
801
822
|
}
|
|
802
|
-
#addRoute(method, path, handler) {
|
|
823
|
+
#addRoute(method, path, handler, baseRoutePath) {
|
|
803
824
|
method = method.toUpperCase();
|
|
804
825
|
path = mergePath(this._basePath, path);
|
|
805
|
-
const r = {
|
|
826
|
+
const r = {
|
|
827
|
+
basePath: baseRoutePath !== undefined ? mergePath(this._basePath, baseRoutePath) : this._basePath,
|
|
828
|
+
path,
|
|
829
|
+
method,
|
|
830
|
+
handler
|
|
831
|
+
};
|
|
806
832
|
this.router.add(method, path, [handler, r]);
|
|
807
833
|
this.routes.push(r);
|
|
808
834
|
}
|
|
@@ -866,7 +892,7 @@ var Hono = class _Hono {
|
|
|
866
892
|
};
|
|
867
893
|
};
|
|
868
894
|
|
|
869
|
-
// node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
895
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/matcher.js
|
|
870
896
|
var emptyParam = [];
|
|
871
897
|
function match(method, path) {
|
|
872
898
|
const matchers = this.buildAllMatchers();
|
|
@@ -887,7 +913,7 @@ function match(method, path) {
|
|
|
887
913
|
return match2(method, path);
|
|
888
914
|
}
|
|
889
915
|
|
|
890
|
-
// node_modules/hono/dist/router/reg-exp-router/node.js
|
|
916
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/node.js
|
|
891
917
|
var LABEL_REG_EXP_STR = "[^/]+";
|
|
892
918
|
var ONLY_WILDCARD_REG_EXP_STR = ".*";
|
|
893
919
|
var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
|
|
@@ -991,7 +1017,7 @@ var Node = class _Node {
|
|
|
991
1017
|
}
|
|
992
1018
|
};
|
|
993
1019
|
|
|
994
|
-
// node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
1020
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/trie.js
|
|
995
1021
|
var Trie = class {
|
|
996
1022
|
#context = { varIndex: 0 };
|
|
997
1023
|
#root = new Node;
|
|
@@ -1047,7 +1073,7 @@ var Trie = class {
|
|
|
1047
1073
|
}
|
|
1048
1074
|
};
|
|
1049
1075
|
|
|
1050
|
-
// node_modules/hono/dist/router/reg-exp-router/router.js
|
|
1076
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/router.js
|
|
1051
1077
|
var nullMatcher = [/^$/, [], /* @__PURE__ */ Object.create(null)];
|
|
1052
1078
|
var wildcardRegExpCache = /* @__PURE__ */ Object.create(null);
|
|
1053
1079
|
function buildWildcardRegExp(path) {
|
|
@@ -1212,7 +1238,7 @@ var RegExpRouter = class {
|
|
|
1212
1238
|
}
|
|
1213
1239
|
};
|
|
1214
1240
|
|
|
1215
|
-
// node_modules/hono/dist/router/reg-exp-router/prepared-router.js
|
|
1241
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/reg-exp-router/prepared-router.js
|
|
1216
1242
|
var PreparedRegExpRouter = class {
|
|
1217
1243
|
name = "PreparedRegExpRouter";
|
|
1218
1244
|
#matchers;
|
|
@@ -1284,7 +1310,7 @@ var PreparedRegExpRouter = class {
|
|
|
1284
1310
|
match = match;
|
|
1285
1311
|
};
|
|
1286
1312
|
|
|
1287
|
-
// node_modules/hono/dist/router/smart-router/router.js
|
|
1313
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/smart-router/router.js
|
|
1288
1314
|
var SmartRouter = class {
|
|
1289
1315
|
name = "SmartRouter";
|
|
1290
1316
|
#routers = [];
|
|
@@ -1339,7 +1365,7 @@ var SmartRouter = class {
|
|
|
1339
1365
|
}
|
|
1340
1366
|
};
|
|
1341
1367
|
|
|
1342
|
-
// node_modules/hono/dist/router/trie-router/node.js
|
|
1368
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/trie-router/node.js
|
|
1343
1369
|
var emptyParams = /* @__PURE__ */ Object.create(null);
|
|
1344
1370
|
var hasChildren = (children) => {
|
|
1345
1371
|
for (const _ in children) {
|
|
@@ -1508,7 +1534,7 @@ var Node2 = class _Node2 {
|
|
|
1508
1534
|
}
|
|
1509
1535
|
};
|
|
1510
1536
|
|
|
1511
|
-
// node_modules/hono/dist/router/trie-router/router.js
|
|
1537
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/router/trie-router/router.js
|
|
1512
1538
|
var TrieRouter = class {
|
|
1513
1539
|
name = "TrieRouter";
|
|
1514
1540
|
#node;
|
|
@@ -1530,7 +1556,7 @@ var TrieRouter = class {
|
|
|
1530
1556
|
}
|
|
1531
1557
|
};
|
|
1532
1558
|
|
|
1533
|
-
// node_modules/hono/dist/hono.js
|
|
1559
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/hono.js
|
|
1534
1560
|
var Hono2 = class extends Hono {
|
|
1535
1561
|
constructor(options = {}) {
|
|
1536
1562
|
super(options);
|
|
@@ -1540,24 +1566,18 @@ var Hono2 = class extends Hono {
|
|
|
1540
1566
|
}
|
|
1541
1567
|
};
|
|
1542
1568
|
|
|
1543
|
-
// node_modules/hono/dist/middleware/cors/index.js
|
|
1569
|
+
// node_modules/.pnpm/hono@4.12.28/node_modules/hono/dist/middleware/cors/index.js
|
|
1544
1570
|
var cors = (options) => {
|
|
1545
|
-
const
|
|
1571
|
+
const opts = {
|
|
1546
1572
|
origin: "*",
|
|
1547
1573
|
allowMethods: ["GET", "HEAD", "PUT", "POST", "DELETE", "PATCH"],
|
|
1548
1574
|
allowHeaders: [],
|
|
1549
|
-
exposeHeaders: []
|
|
1550
|
-
};
|
|
1551
|
-
const opts = {
|
|
1552
|
-
...defaults,
|
|
1575
|
+
exposeHeaders: [],
|
|
1553
1576
|
...options
|
|
1554
1577
|
};
|
|
1555
1578
|
const findAllowOrigin = ((optsOrigin) => {
|
|
1556
1579
|
if (typeof optsOrigin === "string") {
|
|
1557
1580
|
if (optsOrigin === "*") {
|
|
1558
|
-
if (opts.credentials) {
|
|
1559
|
-
return (origin) => origin || null;
|
|
1560
|
-
}
|
|
1561
1581
|
return () => optsOrigin;
|
|
1562
1582
|
} else {
|
|
1563
1583
|
return (origin) => optsOrigin === origin ? origin : null;
|
|
@@ -1592,7 +1612,7 @@ var cors = (options) => {
|
|
|
1592
1612
|
set("Access-Control-Expose-Headers", opts.exposeHeaders.join(","));
|
|
1593
1613
|
}
|
|
1594
1614
|
if (c.req.method === "OPTIONS") {
|
|
1595
|
-
if (opts.origin !== "*"
|
|
1615
|
+
if (opts.origin !== "*") {
|
|
1596
1616
|
set("Vary", "Origin");
|
|
1597
1617
|
}
|
|
1598
1618
|
if (opts.maxAge != null) {
|
|
@@ -1622,7 +1642,7 @@ var cors = (options) => {
|
|
|
1622
1642
|
});
|
|
1623
1643
|
}
|
|
1624
1644
|
await next();
|
|
1625
|
-
if (opts.origin !== "*"
|
|
1645
|
+
if (opts.origin !== "*") {
|
|
1626
1646
|
c.header("Vary", "Origin", { append: true });
|
|
1627
1647
|
}
|
|
1628
1648
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/instructions",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.3",
|
|
4
4
|
"description": "AI coding agent instruction & configuration manager — store, version, apply, and share all your AI coding configs. CLI + MCP + HTTP API (instructions-serve) + generated SDK + Dashboard.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|