@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 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 wildcardToRegExp(pattern) {
4507
- const escaped = pattern.replace(/[|\\{}()[\]^$+?.]/g, "\\$&").replace(/\*/g, ".*");
4508
- return new RegExp(`^${escaped}$`);
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 actual = getPathValue(input, path);
4523
- if (typeof expected === "string" || Array.isArray(expected)) {
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 testChannel(id, input = {}) {
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 registerWebhookCommands(program, options) {
5058
- const webhooks = program.command(options.webhooksCommandName ?? "webhooks").description("Manage Hasna event webhook subscriptions");
5059
- 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) => {
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: parseFilter(actionOptions),
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
- webhooks.command("list").description("List configured subscriptions").option("-j, --json", "Print JSON output", false).action(async (actionOptions, command) => {
5083
- const channels = await createClient(options).listChannels();
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(channels), null, 2));
5279
+ console.log(JSON.stringify(sanitizeChannelsForOutput(channels2), null, 2));
5086
5280
  return;
5087
5281
  }
5088
- if (!channels.length) {
5282
+ if (!channels2.length) {
5089
5283
  console.log("No channels configured.");
5090
5284
  return;
5091
5285
  }
5092
- for (const channel of channels) {
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
- webhooks.command("remove").description("Remove a subscription").argument("<id>", "Subscription/channel identifier").option("-j, --json", "Print JSON output", false).action(async (id, actionOptions, command) => {
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
- 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) => {
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
- return webhooks;
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
- registerWebhookCommands(program, options);
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.2",
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",
@@ -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/body.js
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 instanceof HonoRequest ? request.raw.headers : request.headers;
66
+ const headers = isRawRequest(request) ? request.headers : request.raw.headers;
56
67
  const contentType = headers.get("Content-Type");
57
- if (contentType?.startsWith("multipart/form-data") || contentType?.startsWith("application/x-www-form-urlencoded")) {
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 formData = await request.formData();
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 = url.pathname.slice(pathPrefixLength) || "/";
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 = { basePath: this._basePath, path, method, handler };
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 defaults = {
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 !== "*" || opts.credentials) {
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 !== "*" || opts.credentials) {
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.2",
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",