@sentry/junior 0.21.1 → 0.22.0

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.
@@ -8,6 +8,14 @@ import {
8
8
 
9
9
  // src/chat/logging.ts
10
10
  import { AsyncLocalStorage } from "async_hooks";
11
+ import path from "path";
12
+ import { styleText } from "util";
13
+ import {
14
+ ConfigError,
15
+ configureSync,
16
+ getConfig,
17
+ getLogger
18
+ } from "@logtape/logtape";
11
19
 
12
20
  // src/chat/coerce.ts
13
21
  function toOptionalString(value) {
@@ -58,20 +66,13 @@ var LEGACY_KEY_MAP = {
58
66
  };
59
67
  var contextStorage = new AsyncLocalStorage();
60
68
  var logRecordSinks = /* @__PURE__ */ new Set();
61
- var ANSI = {
62
- reset: "\x1B[0m",
63
- faint: "\x1B[2m",
64
- red: "\x1B[31m",
65
- yellow: "\x1B[33m",
66
- green: "\x1B[32m",
67
- blue: "\x1B[34m",
68
- cyan: "\x1B[36m",
69
- gray: "\x1B[90m"
70
- };
69
+ var LOGTAPE_BODY_KEY = "__logtape_body";
70
+ var ROOT_LOGGER_CATEGORY = ["junior"];
71
71
  var CONSOLE_PRIORITY_KEYS = [
72
72
  "gen_ai.conversation.id",
73
73
  "app.turn.id",
74
74
  "event.name",
75
+ "app.log.source",
75
76
  "error.message",
76
77
  "messaging.message.id",
77
78
  "app.trace_id",
@@ -119,6 +120,42 @@ function shouldEmitConsole(level) {
119
120
  }
120
121
  return true;
121
122
  }
123
+ function isDevelopmentLoggingMode() {
124
+ if (process.env.NODE_ENV !== "development") {
125
+ return false;
126
+ }
127
+ if (process.env.CI) {
128
+ return false;
129
+ }
130
+ return true;
131
+ }
132
+ function shouldUseDevelopmentConsoleFormat() {
133
+ if (!isDevelopmentLoggingMode()) {
134
+ return false;
135
+ }
136
+ return process.env.JUNIOR_LOG_FORMAT?.trim().toLowerCase() !== "structured";
137
+ }
138
+ function shouldUsePrettyConsole(level) {
139
+ if (level === "warn" || level === "error") {
140
+ return false;
141
+ }
142
+ return shouldUseDevelopmentConsoleFormat();
143
+ }
144
+ function shouldUseConsoleColor() {
145
+ if (!shouldUseDevelopmentConsoleFormat()) {
146
+ return false;
147
+ }
148
+ if (process.env.NO_COLOR) {
149
+ return false;
150
+ }
151
+ return process.env.FORCE_COLOR?.trim() === "1" || Boolean(process.stdout?.isTTY) || Boolean(process.stderr?.isTTY);
152
+ }
153
+ function formatConsoleTimestamp(timestamp) {
154
+ if (shouldUseDevelopmentConsoleFormat()) {
155
+ return timestamp.toTimeString().slice(0, 8);
156
+ }
157
+ return timestamp.toISOString();
158
+ }
122
159
  function findNextBlankLineBoundary(input, start) {
123
160
  const lfBoundary = input.indexOf("\n\n", start);
124
161
  const crlfBoundary = input.indexOf("\r\n\r\n", start);
@@ -311,6 +348,126 @@ function mergeAttributes(...maps) {
311
348
  }
312
349
  return merged;
313
350
  }
351
+ function fromLogTapeLevel(level) {
352
+ if (level === "warning") {
353
+ return "warn";
354
+ }
355
+ if (level === "fatal") {
356
+ return "error";
357
+ }
358
+ if (level === "trace") {
359
+ return "debug";
360
+ }
361
+ return level;
362
+ }
363
+ function getLogSource(category) {
364
+ if (category.length <= ROOT_LOGGER_CATEGORY.length) {
365
+ return void 0;
366
+ }
367
+ const sourceParts = category.slice(ROOT_LOGGER_CATEGORY.length);
368
+ return sourceParts.length > 0 ? sourceParts.join(".") : void 0;
369
+ }
370
+ function toEmittedLogRecord(record) {
371
+ const properties = { ...record.properties };
372
+ const rawBody = properties[LOGTAPE_BODY_KEY];
373
+ delete properties[LOGTAPE_BODY_KEY];
374
+ const attributes = mergeAttributes(properties);
375
+ const source = getLogSource(record.category);
376
+ if (source && attributes["app.log.source"] === void 0) {
377
+ attributes["app.log.source"] = source;
378
+ }
379
+ const body = toOptionalString(rawBody) ?? record.message.map(
380
+ (segment) => typeof segment === "string" ? segment : String(segment ?? "")
381
+ ).join("");
382
+ const eventName = toOptionalString(attributes["event.name"]) ?? "log_record_emitted";
383
+ return {
384
+ level: fromLogTapeLevel(record.level),
385
+ eventName,
386
+ body,
387
+ attributes
388
+ };
389
+ }
390
+ function createConsoleSink() {
391
+ return (record) => {
392
+ const emitted = toEmittedLogRecord(record);
393
+ emitConsole(
394
+ emitted.level,
395
+ emitted.eventName,
396
+ emitted.body,
397
+ emitted.attributes
398
+ );
399
+ };
400
+ }
401
+ function createSentrySink() {
402
+ return (record) => {
403
+ const emitted = toEmittedLogRecord(record);
404
+ emitSentry(emitted.level, emitted.body, emitted.attributes);
405
+ };
406
+ }
407
+ function createRecordSink() {
408
+ return (record) => {
409
+ const emitted = toEmittedLogRecord(record);
410
+ for (const sink of logRecordSinks) {
411
+ try {
412
+ sink(emitted);
413
+ } catch {
414
+ }
415
+ }
416
+ };
417
+ }
418
+ var rootLogger;
419
+ var ownsLogTapeBackend = false;
420
+ var usesDirectEmissionFallback = false;
421
+ function ensureLoggerBackend() {
422
+ if (rootLogger || usesDirectEmissionFallback) {
423
+ return;
424
+ }
425
+ if (getConfig() !== null) {
426
+ usesDirectEmissionFallback = true;
427
+ return;
428
+ }
429
+ try {
430
+ configureSync({
431
+ sinks: {
432
+ console: createConsoleSink(),
433
+ sentry: createSentrySink(),
434
+ records: createRecordSink()
435
+ },
436
+ loggers: [
437
+ {
438
+ category: [...ROOT_LOGGER_CATEGORY],
439
+ sinks: ["console", "sentry", "records"],
440
+ lowestLevel: "debug"
441
+ },
442
+ {
443
+ category: ["logtape"],
444
+ sinks: ["console"],
445
+ lowestLevel: "error"
446
+ }
447
+ ],
448
+ contextLocalStorage: contextStorage
449
+ });
450
+ ownsLogTapeBackend = true;
451
+ rootLogger = getLogger([...ROOT_LOGGER_CATEGORY]);
452
+ } catch (error) {
453
+ if (error instanceof ConfigError && getConfig() !== null) {
454
+ usesDirectEmissionFallback = true;
455
+ return;
456
+ }
457
+ throw error;
458
+ }
459
+ }
460
+ function getLogTapeLogger(category = []) {
461
+ ensureLoggerBackend();
462
+ if (!rootLogger) {
463
+ throw new Error("LogTape backend is unavailable");
464
+ }
465
+ let logger = rootLogger;
466
+ for (const part of category) {
467
+ logger = logger.getChild(part);
468
+ }
469
+ return logger;
470
+ }
314
471
  function emitSentry(level, body, attributes) {
315
472
  if (shouldSuppressInfoLog(level)) {
316
473
  return;
@@ -343,11 +500,11 @@ function formatConsoleLevel(level) {
343
500
  if (level === "warn") return "WRN";
344
501
  return "ERR";
345
502
  }
346
- function consoleLevelColor(level) {
347
- if (level === "error") return ANSI.red;
348
- if (level === "warn") return ANSI.yellow;
349
- if (level === "info") return ANSI.green;
350
- return ANSI.blue;
503
+ function consoleLevelStyle(level) {
504
+ if (level === "error") return "red";
505
+ if (level === "warn") return "yellow";
506
+ if (level === "info") return "green";
507
+ return "blue";
351
508
  }
352
509
  function quoteConsoleValue(value) {
353
510
  return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
@@ -412,6 +569,119 @@ function summarizeConsoleString(value, maxChars) {
412
569
  }
413
570
  return `${collapsed.slice(0, maxChars)}... [${collapsed.length} chars]`;
414
571
  }
572
+ function abbreviateConsoleId(value) {
573
+ if (value.length <= 20) {
574
+ return value;
575
+ }
576
+ return `${value.slice(0, 12)}...${value.slice(-4)}`;
577
+ }
578
+ function toRelativeConsolePath(value) {
579
+ const normalized = value.trim();
580
+ if (!normalized) {
581
+ return normalized;
582
+ }
583
+ try {
584
+ const relative = path.relative(process.cwd(), normalized);
585
+ if (relative.length > 0 && !relative.startsWith("..") && !path.isAbsolute(relative)) {
586
+ return relative;
587
+ }
588
+ } catch {
589
+ }
590
+ return normalized;
591
+ }
592
+ function pushPrettyConsoleToken(tokens, token) {
593
+ if (!token || tokens.includes(token)) {
594
+ return;
595
+ }
596
+ tokens.push(token);
597
+ }
598
+ function numericConsoleToken(label, value) {
599
+ return typeof value === "number" ? `${label}=${value}` : void 0;
600
+ }
601
+ function booleanConsoleToken(label, value) {
602
+ return typeof value === "boolean" ? `${label}=${value ? "yes" : "no"}` : void 0;
603
+ }
604
+ function shouldShowPrettyCorrelation(eventName) {
605
+ return !(eventName === "plugin_loaded" || eventName === "startup_discovery_summary" || eventName === "capability_catalog_loaded" || eventName.endsWith("_loaded"));
606
+ }
607
+ function getPrettyConsoleSummaryTokens(level, eventName, attributes) {
608
+ const tokens = [];
609
+ pushPrettyConsoleToken(
610
+ tokens,
611
+ toOptionalString(attributes["app.log.source"]) ?? void 0
612
+ );
613
+ pushPrettyConsoleToken(
614
+ tokens,
615
+ toOptionalString(attributes["app.plugin.name"]) ?? void 0
616
+ );
617
+ pushPrettyConsoleToken(
618
+ tokens,
619
+ numericConsoleToken("caps", attributes["app.plugin.capability_count"])
620
+ );
621
+ pushPrettyConsoleToken(
622
+ tokens,
623
+ numericConsoleToken("config", attributes["app.plugin.config_key_count"])
624
+ );
625
+ pushPrettyConsoleToken(
626
+ tokens,
627
+ booleanConsoleToken("mcp", attributes["app.plugin.has_mcp"])
628
+ );
629
+ pushPrettyConsoleToken(
630
+ tokens,
631
+ numericConsoleToken("plugins", attributes["app.plugin.count"])
632
+ );
633
+ pushPrettyConsoleToken(
634
+ tokens,
635
+ numericConsoleToken("skills", attributes["app.skill.count"])
636
+ );
637
+ pushPrettyConsoleToken(
638
+ tokens,
639
+ numericConsoleToken("capabilities", attributes["app.capability.count"])
640
+ );
641
+ pushPrettyConsoleToken(
642
+ tokens,
643
+ numericConsoleToken("config", attributes["app.config.key_count"])
644
+ );
645
+ pushPrettyConsoleToken(
646
+ tokens,
647
+ numericConsoleToken("chars", attributes["app.message.length"])
648
+ );
649
+ pushPrettyConsoleToken(
650
+ tokens,
651
+ numericConsoleToken(
652
+ "attachments",
653
+ attributes["app.message.attachment_count"]
654
+ )
655
+ );
656
+ const filePath = toOptionalString(attributes["file.path"]);
657
+ if (filePath && eventName.endsWith("_loaded")) {
658
+ pushPrettyConsoleToken(tokens, toRelativeConsolePath(filePath));
659
+ }
660
+ if (shouldShowPrettyCorrelation(eventName)) {
661
+ const conversationId = toOptionalString(
662
+ attributes["gen_ai.conversation.id"]
663
+ );
664
+ const turnId = toOptionalString(attributes["app.turn.id"]);
665
+ const messageId = toOptionalString(attributes["messaging.message.id"]);
666
+ if (conversationId) {
667
+ pushPrettyConsoleToken(
668
+ tokens,
669
+ `conv=${abbreviateConsoleId(conversationId)}`
670
+ );
671
+ }
672
+ if (turnId) {
673
+ pushPrettyConsoleToken(tokens, `turn=${abbreviateConsoleId(turnId)}`);
674
+ }
675
+ if (messageId) {
676
+ pushPrettyConsoleToken(tokens, `msg=${abbreviateConsoleId(messageId)}`);
677
+ }
678
+ }
679
+ const model = toOptionalString(attributes["gen_ai.request.model"]);
680
+ if (model && shouldShowConsoleModel(level, eventName)) {
681
+ pushPrettyConsoleToken(tokens, `model=${model}`);
682
+ }
683
+ return tokens;
684
+ }
415
685
  function projectConsoleValue(level, key, value) {
416
686
  if ((level === "debug" || level === "info") && CONSOLE_PREVIEW_KEYS.has(key) && typeof value === "string") {
417
687
  return summarizeConsoleString(
@@ -435,12 +705,25 @@ function projectConsoleAttributes(level, eventName, attributes) {
435
705
  return projected;
436
706
  }
437
707
  function formatConsoleLine(level, eventName, body, attributes) {
438
- const timestamp = (/* @__PURE__ */ new Date()).toISOString();
439
- const useColor = process.env.NODE_ENV === "development" && Boolean(process.stdout?.isTTY);
440
- const levelColor = consoleLevelColor(level);
441
- const colorize = (text, color) => useColor ? `${color}${text}${ANSI.reset}` : text;
708
+ const timestamp = /* @__PURE__ */ new Date();
709
+ const useColor = shouldUseConsoleColor();
710
+ const levelStyle = consoleLevelStyle(level);
711
+ const colorize = (text, style) => useColor ? styleText(style, text) : text;
712
+ if (shouldUsePrettyConsole(level)) {
713
+ const summaryTokens = getPrettyConsoleSummaryTokens(
714
+ level,
715
+ eventName,
716
+ attributes
717
+ );
718
+ const summary = [body, ...summaryTokens].join(" ");
719
+ return [
720
+ colorize(formatConsoleTimestamp(timestamp), "gray"),
721
+ colorize(formatConsoleLevel(level), levelStyle),
722
+ summary
723
+ ].join(" ");
724
+ }
442
725
  const parts = [
443
- `${colorize(timestamp, ANSI.gray)} ${colorize(formatConsoleLevel(level), levelColor)} ${body}`
726
+ `${colorize(formatConsoleTimestamp(timestamp), "gray")} ${colorize(formatConsoleLevel(level), levelStyle)} ${body}`
444
727
  ];
445
728
  const projectedAttributes = projectConsoleAttributes(
446
729
  level,
@@ -460,7 +743,7 @@ function formatConsoleLine(level, eventName, body, attributes) {
460
743
  }
461
744
  );
462
745
  for (const [key, value] of sortedAttributes) {
463
- const rendered = `${colorize(key, ANSI.cyan)}=${colorize(formatConsoleValue(value), ANSI.faint)}`;
746
+ const rendered = `${colorize(key, "cyan")}=${colorize(formatConsoleValue(value), "dim")}`;
464
747
  parts.push(rendered);
465
748
  }
466
749
  return parts.join(" ");
@@ -484,28 +767,58 @@ function emitConsole(level, eventName, body, attributes) {
484
767
  }
485
768
  console.debug(line);
486
769
  }
487
- function emit(level, eventName, attrs = {}, body) {
488
- const contextAttributes = contextStorage.getStore() ?? {};
489
- const traceAttributes = getTraceCorrelationAttributes();
490
- const normalizedEventName = toSnakeCase(eventName);
491
- const message = body ? redactSecrets(body) : normalizedEventName;
492
- const attributes = mergeAttributes(contextAttributes, traceAttributes, {
493
- "event.name": normalizedEventName,
494
- ...attrs
495
- });
770
+ function emitDirect(level, eventName, body, attributes) {
496
771
  for (const sink of logRecordSinks) {
497
772
  try {
498
773
  sink({
499
774
  level,
500
- eventName: normalizedEventName,
501
- body: message,
775
+ eventName,
776
+ body,
502
777
  attributes
503
778
  });
504
779
  } catch {
505
780
  }
506
781
  }
507
- emitConsole(level, normalizedEventName, message, attributes);
508
- emitSentry(level, message, attributes);
782
+ emitConsole(level, eventName, body, attributes);
783
+ emitSentry(level, body, attributes);
784
+ }
785
+ function emitRecord(category, level, eventName, attrs = {}, body) {
786
+ ensureLoggerBackend();
787
+ const traceAttributes = getTraceCorrelationAttributes();
788
+ const normalizedEventName = toSnakeCase(eventName);
789
+ const message = body ? redactSecrets(body) : normalizedEventName;
790
+ const source = getLogSource([...ROOT_LOGGER_CATEGORY, ...category]);
791
+ const contextAttributes = ownsLogTapeBackend ? void 0 : contextStorage.getStore();
792
+ const attributes = mergeAttributes(contextAttributes, traceAttributes, {
793
+ "event.name": normalizedEventName,
794
+ ...source ? { "app.log.source": source } : {},
795
+ ...attrs
796
+ });
797
+ if (usesDirectEmissionFallback) {
798
+ emitDirect(level, normalizedEventName, message, attributes);
799
+ return;
800
+ }
801
+ const logger = getLogTapeLogger(category);
802
+ const properties = {
803
+ [LOGTAPE_BODY_KEY]: message,
804
+ ...attributes
805
+ };
806
+ if (level === "error") {
807
+ logger.error(`{${LOGTAPE_BODY_KEY}}`, properties);
808
+ return;
809
+ }
810
+ if (level === "warn") {
811
+ logger.warn(`{${LOGTAPE_BODY_KEY}}`, properties);
812
+ return;
813
+ }
814
+ if (level === "info") {
815
+ logger.info(`{${LOGTAPE_BODY_KEY}}`, properties);
816
+ return;
817
+ }
818
+ logger.debug(`{${LOGTAPE_BODY_KEY}}`, properties);
819
+ }
820
+ function emit(level, eventName, attrs = {}, body) {
821
+ emitRecord([], level, eventName, attrs, body);
509
822
  }
510
823
  var log = {
511
824
  debug(eventName, attrs = {}, body) {
@@ -555,6 +868,83 @@ var log = {
555
868
  return eventId;
556
869
  }
557
870
  };
871
+ var CHAT_SDK_LEVEL_PRIORITY = {
872
+ debug: 10,
873
+ info: 20,
874
+ warn: 30,
875
+ error: 40
876
+ };
877
+ function resolveChatSdkLogLevel() {
878
+ if (isDevelopmentLoggingMode()) {
879
+ return "warn";
880
+ }
881
+ return "info";
882
+ }
883
+ function shouldEmitChatSdkLevel(level, minimumLevel) {
884
+ if (minimumLevel === "silent") {
885
+ return false;
886
+ }
887
+ return CHAT_SDK_LEVEL_PRIORITY[level] >= CHAT_SDK_LEVEL_PRIORITY[minimumLevel];
888
+ }
889
+ function renderChatSdkArgument(value) {
890
+ if (value === null || value === void 0) {
891
+ return "";
892
+ }
893
+ if (typeof value === "string") {
894
+ return value;
895
+ }
896
+ if (value instanceof Error) {
897
+ return value.message;
898
+ }
899
+ try {
900
+ return JSON.stringify(value);
901
+ } catch {
902
+ return String(value);
903
+ }
904
+ }
905
+ function formatChatSdkBody(message, args) {
906
+ const renderedArgs = args.map((arg) => renderChatSdkArgument(arg).trim()).filter((arg) => arg.length > 0);
907
+ if (renderedArgs.length === 0) {
908
+ return message;
909
+ }
910
+ return `${message} ${renderedArgs.join(" ")}`;
911
+ }
912
+ function createChatSdkLoggerImpl(category, minimumLevel) {
913
+ const emitChatSdkLog = (level, message, args) => {
914
+ if (!shouldEmitChatSdkLevel(level, minimumLevel)) {
915
+ return;
916
+ }
917
+ emitRecord(
918
+ category,
919
+ level === "warn" ? "warn" : level,
920
+ level === "error" ? "chat_sdk_error" : level === "warn" ? "chat_sdk_warning" : "chat_sdk_log",
921
+ args.length > 0 ? {
922
+ "app.log.args": args.length === 1 ? args[0] : args
923
+ } : {},
924
+ formatChatSdkBody(message, args)
925
+ );
926
+ };
927
+ return {
928
+ child(prefix) {
929
+ return createChatSdkLoggerImpl([...category, prefix], minimumLevel);
930
+ },
931
+ debug(message, ...args) {
932
+ emitChatSdkLog("debug", message, args);
933
+ },
934
+ info(message, ...args) {
935
+ emitChatSdkLog("info", message, args);
936
+ },
937
+ warn(message, ...args) {
938
+ emitChatSdkLog("warn", message, args);
939
+ },
940
+ error(message, ...args) {
941
+ emitChatSdkLog("error", message, args);
942
+ }
943
+ };
944
+ }
945
+ function createChatSdkLogger() {
946
+ return createChatSdkLoggerImpl(["chat-sdk"], resolveChatSdkLogLevel());
947
+ }
558
948
  function withLogContext(context, callback) {
559
949
  const next = mergeAttributes(
560
950
  contextStorage.getStore(),
@@ -871,6 +1261,7 @@ import { parse as parseYaml } from "yaml";
871
1261
  var PLUGIN_NAME_RE = /^[a-z][a-z0-9-]*$/;
872
1262
  var SHORT_CAPABILITY_RE = /^[a-z0-9-]+(\.[a-z0-9-]+)*$/;
873
1263
  var SHORT_CONFIG_KEY_RE = /^[a-z0-9]+(\.[a-z0-9-]+)*$/;
1264
+ var TARGET_FLAG_RE = /^-{1,2}[A-Za-z0-9][A-Za-z0-9-]*$/;
874
1265
  var AUTH_TOKEN_ENV_RE = /^[A-Z][A-Z0-9_]*$/;
875
1266
  var API_DOMAIN_RE = /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?$/;
876
1267
  var RUNTIME_POSTINSTALL_CMD_RE = /^[A-Za-z0-9._/-]+$/;
@@ -1036,16 +1427,17 @@ var oauthSourceSchema = z.object({
1036
1427
  var mcpSourceSchema = z.object({
1037
1428
  transport: nonEmptyTrimmedString.refine((value) => value === "http", {
1038
1429
  error: 'must be "http"'
1039
- }),
1430
+ }).optional(),
1040
1431
  url: httpsUrlString,
1041
1432
  headers: stringMapSchema.optional(),
1042
1433
  "allowed-tools": nonEmptyStringArraySchema("allowed-tools").optional()
1043
1434
  }).passthrough();
1044
1435
  var targetSourceSchema = z.object({
1045
- type: z.literal("repo", {
1046
- error: 'type must be "repo"'
1436
+ type: nonEmptyTrimmedString.refine((value) => PLUGIN_NAME_RE.test(value), {
1437
+ error: "type must be a lowercase target identifier"
1047
1438
  }),
1048
- "config-key": nonEmptyTrimmedString
1439
+ "config-key": nonEmptyTrimmedString,
1440
+ "command-flags": nonEmptyStringArraySchema("command-flags").optional()
1049
1441
  }).passthrough();
1050
1442
  var manifestSourceSchema = z.object({
1051
1443
  name: z.string().refine((value) => PLUGIN_NAME_RE.test(value), {
@@ -1077,8 +1469,8 @@ var manifestSourceSchema = z.object({
1077
1469
  error: "must be an object"
1078
1470
  }).optional()
1079
1471
  }).passthrough();
1080
- function formatPath(path2) {
1081
- return path2.map((segment) => String(segment)).join(".");
1472
+ function formatPath(path3) {
1473
+ return path3.map((segment) => String(segment)).join(".");
1082
1474
  }
1083
1475
  function issueMessage(error, prefix) {
1084
1476
  const issue = error.issues[0];
@@ -1305,51 +1697,51 @@ function parsePluginManifest(raw, dir) {
1305
1697
  const sourceResult = manifestSourceSchema.safeParse(parsedYaml);
1306
1698
  if (!sourceResult.success) {
1307
1699
  const issue = sourceResult.error.issues[0];
1308
- const path2 = formatPath(issue?.path ?? []);
1309
- if (path2 === "name") {
1700
+ const path3 = formatPath(issue?.path ?? []);
1701
+ if (path3 === "name") {
1310
1702
  throw new Error(
1311
1703
  `Invalid plugin name in ${dir}: "${parsedYaml.name}"`
1312
1704
  );
1313
1705
  }
1314
- if (path2 === "description") {
1706
+ if (path3 === "description") {
1315
1707
  throw new Error(`Invalid plugin description in ${dir}`);
1316
1708
  }
1317
- if (path2 === "capabilities") {
1709
+ if (path3 === "capabilities") {
1318
1710
  throw new Error(
1319
1711
  `Plugin ${parsedYaml.name ?? "unknown"} capabilities must be an array when provided`
1320
1712
  );
1321
1713
  }
1322
- if (path2 === "config-keys") {
1714
+ if (path3 === "config-keys") {
1323
1715
  throw new Error(
1324
1716
  `Plugin ${parsedYaml.name ?? "unknown"} config-keys must be an array when provided`
1325
1717
  );
1326
1718
  }
1327
- if (path2 === "credentials") {
1719
+ if (path3 === "credentials") {
1328
1720
  throw new Error(
1329
1721
  `Plugin ${parsedYaml.name ?? "unknown"} credentials must be an object when provided`
1330
1722
  );
1331
1723
  }
1332
- if (path2 === "runtime-dependencies") {
1724
+ if (path3 === "runtime-dependencies") {
1333
1725
  throw new Error(
1334
1726
  `Plugin ${parsedYaml.name ?? "unknown"} runtime-dependencies must be an array`
1335
1727
  );
1336
1728
  }
1337
- if (path2 === "runtime-postinstall") {
1729
+ if (path3 === "runtime-postinstall") {
1338
1730
  throw new Error(
1339
1731
  `Plugin ${parsedYaml.name ?? "unknown"} runtime-postinstall must be an array`
1340
1732
  );
1341
1733
  }
1342
- if (path2 === "mcp") {
1734
+ if (path3 === "mcp") {
1343
1735
  throw new Error(
1344
1736
  `Plugin ${parsedYaml.name ?? "unknown"} mcp must be an object`
1345
1737
  );
1346
1738
  }
1347
- if (path2 === "oauth") {
1739
+ if (path3 === "oauth") {
1348
1740
  throw new Error(
1349
1741
  `Plugin ${parsedYaml.name ?? "unknown"} oauth must be an object`
1350
1742
  );
1351
1743
  }
1352
- if (path2 === "target") {
1744
+ if (path3 === "target") {
1353
1745
  throw new Error(
1354
1746
  `Plugin ${parsedYaml.name ?? "unknown"} target must be an object`
1355
1747
  );
@@ -1439,14 +1831,24 @@ function parsePluginManifest(raw, dir) {
1439
1831
  `Plugin ${data.name} target.config-key "${result.data["config-key"]}" must be listed in config-keys`
1440
1832
  );
1441
1833
  }
1442
- manifest.target = { type: "repo", configKey: qualifiedKey };
1834
+ const commandFlags = result.data["command-flags"];
1835
+ if (commandFlags && commandFlags.some((flag) => !TARGET_FLAG_RE.test(flag))) {
1836
+ throw new Error(
1837
+ `Plugin ${data.name} target.command-flags must contain CLI flags like --repo or -R`
1838
+ );
1839
+ }
1840
+ manifest.target = {
1841
+ type: result.data.type,
1842
+ configKey: qualifiedKey,
1843
+ ...commandFlags ? { commandFlags } : {}
1844
+ };
1443
1845
  }
1444
1846
  return manifest;
1445
1847
  }
1446
1848
 
1447
1849
  // src/chat/plugins/registry.ts
1448
1850
  import { readFileSync, readdirSync, statSync } from "fs";
1449
- import path from "path";
1851
+ import path2 from "path";
1450
1852
 
1451
1853
  // src/chat/plugins/auth/github-app-broker.ts
1452
1854
  import { createPrivateKey, createSign, randomUUID } from "crypto";
@@ -1462,13 +1864,26 @@ function resolveAuthTokenPlaceholder(credentials) {
1462
1864
 
1463
1865
  // src/chat/plugins/auth/github-app-broker.ts
1464
1866
  var MAX_LEASE_MS = 60 * 60 * 1e3;
1465
- function normalizeTargetScope(target) {
1466
- const owner = target?.owner?.trim().toLowerCase();
1467
- const repo = target?.repo?.trim().toLowerCase();
1468
- if (!owner || !repo) {
1469
- return "all";
1867
+ function parseRepoTarget(value) {
1868
+ const trimmed = value.trim();
1869
+ if (!trimmed) {
1870
+ return void 0;
1871
+ }
1872
+ const [repoRef] = trimmed.split("#");
1873
+ const [owner, repo, extra] = repoRef.split("/");
1874
+ if (!owner || !repo || extra) {
1875
+ return void 0;
1470
1876
  }
1471
- return `${owner}/${repo}`;
1877
+ return {
1878
+ owner: owner.toLowerCase(),
1879
+ repo: repo.toLowerCase()
1880
+ };
1881
+ }
1882
+ function getRepoTarget(target) {
1883
+ if (!target || target.type !== "repo") {
1884
+ return void 0;
1885
+ }
1886
+ return parseRepoTarget(target.value);
1472
1887
  }
1473
1888
  function base64Url(input) {
1474
1889
  return Buffer.from(input).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
@@ -1527,8 +1942,8 @@ function createAppJwt(appId, privateKeyEnv) {
1527
1942
  const signature = signer.sign(getPrivateKey(privateKeyEnv)).toString("base64").replace(/=/g, "").replace(/\+/g, "-").replace(/\//g, "_");
1528
1943
  return `${signingInput}.${signature}`;
1529
1944
  }
1530
- async function githubRequest(apiBase, path2, params) {
1531
- const response = await fetch(`${apiBase}${path2}`, {
1945
+ async function githubRequest(apiBase, path3, params) {
1946
+ const response = await fetch(`${apiBase}${path3}`, {
1532
1947
  method: params.method ?? "GET",
1533
1948
  headers: {
1534
1949
  Accept: "application/vnd.github+json",
@@ -1553,10 +1968,6 @@ async function githubRequest(apiBase, path2, params) {
1553
1968
  }
1554
1969
  return parsed;
1555
1970
  }
1556
- var CAPABILITY_ALIASES = {
1557
- "issues.comment": { permission: "issues", level: "write" },
1558
- "labels.write": { permission: "issues", level: "write" }
1559
- };
1560
1971
  var KNOWN_SCOPES = /* @__PURE__ */ new Set([
1561
1972
  "actions",
1562
1973
  "administration",
@@ -1585,10 +1996,6 @@ function capabilityToPermissions(capability, pluginName) {
1585
1996
  throw new Error(`Unsupported GitHub capability: ${capability}`);
1586
1997
  }
1587
1998
  const suffix = capability.slice(prefix.length);
1588
- const alias = CAPABILITY_ALIASES[suffix];
1589
- if (alias) {
1590
- return { [alias.permission]: alias.level };
1591
- }
1592
1999
  const lastDot = suffix.lastIndexOf(".");
1593
2000
  if (lastDot === -1) {
1594
2001
  throw new Error(`Unsupported GitHub capability: ${capability}`);
@@ -1632,6 +2039,17 @@ function createGitHubAppBroker(manifest, credentials) {
1632
2039
  return `Bearer ${token}`;
1633
2040
  }
1634
2041
  const supportedCapabilities = new Set(manifest.capabilities);
2042
+ function resolveInstallationId() {
2043
+ const installationIdRaw = process.env[installationIdEnv]?.trim();
2044
+ if (!installationIdRaw) {
2045
+ throw new Error(`Missing ${installationIdEnv}`);
2046
+ }
2047
+ const installationId = Number(installationIdRaw);
2048
+ if (!Number.isFinite(installationId)) {
2049
+ throw new Error(`Invalid ${installationIdEnv}`);
2050
+ }
2051
+ return installationId;
2052
+ }
1635
2053
  return {
1636
2054
  async issue(input) {
1637
2055
  if (!supportedCapabilities.has(input.capability)) {
@@ -1640,19 +2058,20 @@ function createGitHubAppBroker(manifest, credentials) {
1640
2058
  );
1641
2059
  }
1642
2060
  const permissions = capabilityToPermissions(input.capability, provider);
1643
- const appId = process.env[appIdEnv];
1644
- if (!appId) {
1645
- throw new Error(`Missing ${appIdEnv}`);
1646
- }
1647
- const installationIdRaw = process.env[installationIdEnv]?.trim();
1648
- if (!installationIdRaw) {
1649
- throw new Error(`Missing ${installationIdEnv}`);
1650
- }
1651
- const installationId = Number(installationIdRaw);
1652
- if (!Number.isFinite(installationId)) {
1653
- throw new Error(`Invalid ${installationIdEnv}`);
2061
+ const installationId = resolveInstallationId();
2062
+ let repoTarget;
2063
+ if (input.target) {
2064
+ if (input.target.type !== "repo") {
2065
+ throw new Error(
2066
+ `Unsupported github target type: ${input.target.type}`
2067
+ );
2068
+ }
2069
+ repoTarget = getRepoTarget(input.target);
2070
+ if (!repoTarget) {
2071
+ throw new Error("Invalid github repo target: expected owner/repo");
2072
+ }
1654
2073
  }
1655
- const targetScope = normalizeTargetScope(input.target);
2074
+ const targetScope = repoTarget ? `${repoTarget.owner}/${repoTarget.repo}` : "all";
1656
2075
  const cacheKey = `${installationId}:${input.capability}:${targetScope}`;
1657
2076
  const cached = tokenCache.get(cacheKey);
1658
2077
  const now = Date.now();
@@ -1678,14 +2097,17 @@ function createGitHubAppBroker(manifest, credentials) {
1678
2097
  }
1679
2098
  };
1680
2099
  }
1681
- const appJwt = createAppJwt(appId, privateKeyEnv);
1682
- const repositoryName = input.target?.repo?.trim().toLowerCase();
1683
2100
  const tokenRequestBody = {
1684
2101
  permissions
1685
2102
  };
1686
- if (repositoryName) {
1687
- tokenRequestBody.repositories = [repositoryName];
2103
+ if (repoTarget) {
2104
+ tokenRequestBody.repositories = [repoTarget.repo];
1688
2105
  }
2106
+ const appId = process.env[appIdEnv];
2107
+ if (!appId) {
2108
+ throw new Error(`Missing ${appIdEnv}`);
2109
+ }
2110
+ const appJwt = createAppJwt(appId, privateKeyEnv);
1689
2111
  const accessTokenResponse = await githubRequest(apiBase, `/app/installations/${installationId}/access_tokens`, {
1690
2112
  method: "POST",
1691
2113
  token: appJwt,
@@ -2034,7 +2456,7 @@ function registerPluginManifest(state, raw, pluginDir) {
2034
2456
  const definition = {
2035
2457
  manifest,
2036
2458
  dir: pluginDir,
2037
- skillsDir: path.join(pluginDir, "skills")
2459
+ skillsDir: path2.join(pluginDir, "skills")
2038
2460
  };
2039
2461
  state.pluginDefinitions.push(definition);
2040
2462
  state.pluginsByName.set(manifest.name, definition);
@@ -2049,7 +2471,7 @@ function normalizePluginRoots(roots) {
2049
2471
  const resolved = [];
2050
2472
  const seen = /* @__PURE__ */ new Set();
2051
2473
  for (const root of roots) {
2052
- const normalized = path.resolve(root);
2474
+ const normalized = path2.resolve(root);
2053
2475
  if (seen.has(normalized)) {
2054
2476
  continue;
2055
2477
  }
@@ -2076,7 +2498,7 @@ function getExtraPluginRoots() {
2076
2498
  }
2077
2499
  }
2078
2500
  return normalizePluginRoots(
2079
- raw.split(path.delimiter).map((entry) => entry.trim()).filter((entry) => entry.length > 0)
2501
+ raw.split(path2.delimiter).map((entry) => entry.trim()).filter((entry) => entry.length > 0)
2080
2502
  );
2081
2503
  }
2082
2504
  function getPluginCatalogSource() {
@@ -2124,7 +2546,7 @@ function buildLoadedPluginState(source) {
2124
2546
  continue;
2125
2547
  }
2126
2548
  if (rootStat.isDirectory()) {
2127
- const manifestPath = path.join(pluginsRoot, "plugin.yaml");
2549
+ const manifestPath = path2.join(pluginsRoot, "plugin.yaml");
2128
2550
  let hasRootManifest = false;
2129
2551
  try {
2130
2552
  hasRootManifest = statSync(manifestPath).isFile();
@@ -2152,14 +2574,14 @@ function buildLoadedPluginState(source) {
2152
2574
  continue;
2153
2575
  }
2154
2576
  for (const entry of entries.sort()) {
2155
- const pluginDir = path.join(pluginsRoot, entry);
2577
+ const pluginDir = path2.join(pluginsRoot, entry);
2156
2578
  try {
2157
2579
  const stat = statSync(pluginDir);
2158
2580
  if (!stat.isDirectory()) continue;
2159
2581
  } catch {
2160
2582
  continue;
2161
2583
  }
2162
- const manifestPath = path.join(pluginDir, "plugin.yaml");
2584
+ const manifestPath = path2.join(pluginDir, "plugin.yaml");
2163
2585
  let raw;
2164
2586
  try {
2165
2587
  raw = readFileSync(manifestPath, "utf8");
@@ -2214,7 +2636,12 @@ function getPluginCapabilityProviders() {
2214
2636
  provider: plugin.manifest.name,
2215
2637
  capabilities: [...plugin.manifest.capabilities],
2216
2638
  configKeys: [...plugin.manifest.configKeys],
2217
- ...plugin.manifest.target ? { target: { ...plugin.manifest.target } } : {}
2639
+ ...plugin.manifest.target ? {
2640
+ target: {
2641
+ ...plugin.manifest.target,
2642
+ ...plugin.manifest.target.commandFlags ? { commandFlags: [...plugin.manifest.target.commandFlags] } : {}
2643
+ }
2644
+ } : {}
2218
2645
  }));
2219
2646
  }
2220
2647
  function getPluginProviders() {
@@ -2295,10 +2722,10 @@ function getPluginSkillRoots() {
2295
2722
  }
2296
2723
  function getPluginForSkillPath(skillPath) {
2297
2724
  const state = ensurePluginsLoaded();
2298
- const resolvedSkillPath = path.resolve(skillPath);
2725
+ const resolvedSkillPath = path2.resolve(skillPath);
2299
2726
  return state.pluginDefinitions.find((plugin) => {
2300
- const resolvedSkillsDir = path.resolve(plugin.skillsDir);
2301
- return resolvedSkillPath === resolvedSkillsDir || resolvedSkillPath.startsWith(`${resolvedSkillsDir}${path.sep}`);
2727
+ const resolvedSkillsDir = path2.resolve(plugin.skillsDir);
2728
+ return resolvedSkillPath === resolvedSkillsDir || resolvedSkillPath.startsWith(`${resolvedSkillsDir}${path2.sep}`);
2302
2729
  });
2303
2730
  }
2304
2731
  function getPluginDefinition(provider) {
@@ -2336,6 +2763,7 @@ export {
2336
2763
  toOptionalString,
2337
2764
  toOptionalNumber,
2338
2765
  isRecord,
2766
+ createChatSdkLogger,
2339
2767
  logInfo,
2340
2768
  logWarn,
2341
2769
  logError,