@brunyee-studio/onus-cli 2.30.1 → 2.31.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.
package/README.md CHANGED
@@ -204,12 +204,31 @@ onus project create --name <name> --team <teamId> [--status <status>] [--priorit
204
204
  onus project update <id> [--name <name>] [--status <status>] [--priority <priority>] [--lead <leadId>] [--target-date <date>] [--description <text>] [--description-file <path>] [--agent-context <text>] [--agent-context-file <path>] [--json]
205
205
  ```
206
206
 
207
+ ### `onus kb`
208
+
209
+ Read-only knowledge base access: no write verbs exist under `onus kb`.
210
+
211
+ ```bash
212
+ onus kb list [--team <id|KEY> | --project <id> | --repo <id>] [--kind <kind>] [--tag <tag>] [--limit <n>] [--cursor <cursor>] [--json]
213
+ onus kb get <slug-or-id> [--json]
214
+ onus kb search <query> [--mode <mode>] [--doc <slug-or-id>] [--limit <n>] [--json]
215
+ onus kb related <slug-or-id> [--json]
216
+ onus kb locate [--issue <issueId> | --query <text>] [--json]
217
+ ```
218
+
219
+ `--kind` accepts `index | area | process | incident`; `--mode` accepts
220
+ `string | semantic | hybrid | related` (default `string`; `related` requires
221
+ `--doc`). Doc refs are slugs or UUIDs — slugs are unique per owner, so an
222
+ ambiguous slug lists its candidate owners and exits 2; pass the doc UUID to
223
+ resolve unambiguously. List rows and detail carry the staleness verdict
224
+ (`stale`, `confidence`, `reason`).
225
+
207
226
  ### `onus issue`
208
227
 
209
228
  Issues are addressed by human reference (`ENG-42`); UUIDs are also accepted.
210
229
 
211
230
  ```bash
212
- onus issue list [--team <id>] [--project <id>] [--assignee <id>] [--parent <id>] [--status <status>] [--q <query>] [--limit <n>] [--cursor <cursor>] [--json]
231
+ onus issue list [--team <id>] [--project <id>] [--assignee <id>] [--parent <id>] [--status <status>] [--priority <priority>] [--q <query>] [--limit <n>] [--cursor <cursor>] [--json]
213
232
  onus issue get <ref> [--json]
214
233
  onus issue create --title <title> --team <teamId> [-d <description>] [--description-file <path>] [--status <status>] [--priority <priority>] [--assignee <id>] [--project <id>] [--parent <id>] [--label <labelId>] [--json]
215
234
  onus issue update <ref> [--title <title>] [-d <description>] [--description-file <path>] [--status <status>] [--priority <priority>] [--assignee <id>] [--project <id>] [--parent <id>] [--add-label <labelId>] [--remove-label <labelId>] [--json]
package/dist/bin.js CHANGED
@@ -1,9 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ IssueListItemStatusEnum,
4
+ IssuesGetPriorityEnum,
3
5
  cliVersion,
4
6
  createApiClient,
5
7
  loginWithOAuth
6
- } from "./chunk-NQJIFUZV.js";
8
+ } from "./chunk-35HNSEP2.js";
7
9
  import {
8
10
  clearConfig,
9
11
  getConfigDirectory,
@@ -786,7 +788,243 @@ function registerProjectCommands(program2) {
786
788
  // src/commands/issue.ts
787
789
  import { readFileSync as readFileSync2, statSync, writeFileSync } from "fs";
788
790
  import { basename } from "path";
791
+
792
+ // src/search-query.ts
793
+ var ME = "__me__";
794
+ var QUERY_ATTRIBUTES = ["status", "priority", "assignee", "label", "project"];
795
+ var QUERY_ATTRIBUTE_SET = new Set(QUERY_ATTRIBUTES);
789
796
  var UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
797
+ var ISSUE_STATUSES = new Set(Object.values(IssueListItemStatusEnum));
798
+ var ISSUE_PRIORITIES = new Set(Object.values(IssuesGetPriorityEnum));
799
+ function isIssuePriority(value) {
800
+ return ISSUE_PRIORITIES.has(value);
801
+ }
802
+ function canonicalEnumValue(value) {
803
+ return value.toLowerCase().replace(/[\s-]+/g, "_");
804
+ }
805
+ function mapTokenValues(attribute, values) {
806
+ if (attribute === "assignee") {
807
+ return {
808
+ clause: values.flatMap((v) => {
809
+ if (v.toLowerCase() === "me") return [ME];
810
+ return v === ME ? [] : [v];
811
+ }),
812
+ text: []
813
+ };
814
+ }
815
+ if (attribute === "status" || attribute === "priority") {
816
+ const isValidEnum = attribute === "status" ? ISSUE_STATUSES : ISSUE_PRIORITIES;
817
+ const clause2 = [];
818
+ const text = [];
819
+ for (const value of values) {
820
+ const canonical = canonicalEnumValue(value);
821
+ if (isValidEnum.has(canonical)) clause2.push(canonical);
822
+ else text.push(value);
823
+ }
824
+ return { clause: clause2, text };
825
+ }
826
+ return { clause: [...values], text: [] };
827
+ }
828
+ function tokenize(input) {
829
+ const tokens = [];
830
+ let i = 0;
831
+ while (i < input.length) {
832
+ if (/\s/.test(input.charAt(i))) {
833
+ i += 1;
834
+ continue;
835
+ }
836
+ if (input.charAt(i) === '"') {
837
+ const end = input.indexOf('"', i + 1);
838
+ if (end === -1) {
839
+ tokens.push({ kind: "text", value: input.slice(i + 1) });
840
+ break;
841
+ }
842
+ if (end > i + 1) tokens.push({ kind: "text", value: input.slice(i + 1, end) });
843
+ i = end + 1;
844
+ continue;
845
+ }
846
+ let j = i;
847
+ while (j < input.length && !/\s/.test(input.charAt(j))) j += 1;
848
+ let run = input.slice(i, j);
849
+ const colon = run.indexOf(":");
850
+ const attribute = colon > 0 ? run.slice(0, colon).toLowerCase() : "";
851
+ if (colon > 0 && run.charAt(colon + 1) === '"') {
852
+ const close = input.indexOf('"', i + colon + 2);
853
+ if (close !== -1) {
854
+ j = close + 1;
855
+ while (j < input.length && !/\s/.test(input.charAt(j))) j += 1;
856
+ run = input.slice(i, j);
857
+ }
858
+ }
859
+ if (QUERY_ATTRIBUTE_SET.has(attribute) && run.endsWith(",")) {
860
+ for (; ; ) {
861
+ let k = j;
862
+ while (k < input.length && /\s/.test(input.charAt(k))) k += 1;
863
+ if (k >= input.length || input.charAt(k) === '"') break;
864
+ let n = k;
865
+ while (n < input.length && !/\s/.test(input.charAt(n)) && input.charAt(n) !== ":") n += 1;
866
+ if (input.charAt(n) === ":") break;
867
+ let m = k;
868
+ while (m < input.length && !/\s/.test(input.charAt(m))) m += 1;
869
+ run += input.slice(j, m);
870
+ j = m;
871
+ if (!input.slice(k, m).endsWith(",")) break;
872
+ }
873
+ }
874
+ const rawValues = colon > 0 ? run.slice(colon + 1) : "";
875
+ const trimmedRaw = rawValues.trim();
876
+ const fullyQuoted = trimmedRaw.length >= 2 && trimmedRaw.startsWith('"') && trimmedRaw.endsWith('"');
877
+ let parts = fullyQuoted ? [trimmedRaw.slice(1, -1)] : rawValues.split(",");
878
+ if (!fullyQuoted) {
879
+ const merged = [];
880
+ for (const segment of parts) {
881
+ const last = merged[merged.length - 1];
882
+ if (last !== void 0 && last.startsWith('"') && (last.match(/"/g)?.length ?? 0) % 2 === 1) {
883
+ merged[merged.length - 1] = `${last},${segment}`;
884
+ } else {
885
+ merged.push(segment);
886
+ }
887
+ }
888
+ parts = merged;
889
+ }
890
+ const values = parts.map((v) => v.trim().replace(/^"(.*)"$/, "$1")).filter((v) => v.length > 0);
891
+ if (QUERY_ATTRIBUTE_SET.has(attribute) && values.length > 0) {
892
+ const mapped = mapTokenValues(attribute, values);
893
+ if (mapped.clause.length > 0) {
894
+ tokens.push({
895
+ kind: "clause",
896
+ attribute,
897
+ values: mapped.clause
898
+ });
899
+ }
900
+ for (const degraded of mapped.text) {
901
+ tokens.push({ kind: "text", value: degraded, degraded: true });
902
+ }
903
+ if (mapped.clause.length === 0 && mapped.text.length === 0) {
904
+ tokens.push({ kind: "text", value: run });
905
+ }
906
+ } else {
907
+ tokens.push({ kind: "text", value: run });
908
+ }
909
+ i = j;
910
+ }
911
+ return tokens;
912
+ }
913
+ function parseSearchQuery(input) {
914
+ if (input.trim().length === 0) return { clauses: [], text: "" };
915
+ const valuesByAttribute = /* @__PURE__ */ new Map();
916
+ const textParts = [];
917
+ const degradedParts = [];
918
+ for (const token of tokenize(input)) {
919
+ if (token.kind === "text") {
920
+ if (token.value.length > 0) {
921
+ textParts.push(token.value);
922
+ if (token.degraded) degradedParts.push(token.value);
923
+ }
924
+ continue;
925
+ }
926
+ const existing = valuesByAttribute.get(token.attribute) ?? [];
927
+ for (const value of token.values) {
928
+ if (!existing.includes(value)) existing.push(value);
929
+ }
930
+ valuesByAttribute.set(token.attribute, existing);
931
+ }
932
+ const clauseList = [];
933
+ for (const attribute of QUERY_ATTRIBUTES) {
934
+ const values = valuesByAttribute.get(attribute);
935
+ if (values) clauseList.push({ attribute, values });
936
+ }
937
+ return {
938
+ clauses: clauseList,
939
+ text: textParts.join(" "),
940
+ // Present only when non-empty (same shape convention as degradedTerms).
941
+ ...degradedParts.length > 0 ? { degraded: degradedParts } : {}
942
+ };
943
+ }
944
+ function nonEmptyFlag(value) {
945
+ const trimmed = value?.trim();
946
+ return trimmed ? trimmed : void 0;
947
+ }
948
+ function issueListQueryLookupsNeeded(parsed, flags) {
949
+ const assignee = nonEmptyFlag(flags.assignee) ? void 0 : clause(parsed, "assignee");
950
+ const project = nonEmptyFlag(flags.project) ? void 0 : clause(parsed, "project");
951
+ return {
952
+ // Multi-value clauses degrade to text without reading the viewer id, so
953
+ // only the single-value case warrants the `me` lookup.
954
+ viewerId: assignee?.values.length === 1 && assignee.values[0] === ME,
955
+ projects: project?.values.length === 1 && !UUID_RE.test(project.values[0] ?? "")
956
+ };
957
+ }
958
+ function clause(parsed, attribute) {
959
+ return parsed.clauses.find((c) => c.attribute === attribute);
960
+ }
961
+ function resolveAssigneeParam(assigneeClause, lookups, degraded) {
962
+ if (!assigneeClause) return void 0;
963
+ if (assigneeClause.values.length !== 1) {
964
+ degraded.push(...assigneeClause.values.map((v) => v === ME ? "me" : v));
965
+ return void 0;
966
+ }
967
+ const value = assigneeClause.values[0] ?? "";
968
+ if (value === ME) {
969
+ if (lookups.viewerId) return lookups.viewerId;
970
+ degraded.push("me");
971
+ return void 0;
972
+ }
973
+ if (UUID_RE.test(value)) return value;
974
+ degraded.push(value);
975
+ return void 0;
976
+ }
977
+ function resolveProjectParam(projectClause, lookups, degraded) {
978
+ if (!projectClause) return void 0;
979
+ if (projectClause.values.length !== 1) {
980
+ degraded.push(...projectClause.values);
981
+ return void 0;
982
+ }
983
+ const value = projectClause.values[0] ?? "";
984
+ if (UUID_RE.test(value)) return value;
985
+ const name = value.toLowerCase();
986
+ const match = lookups.projects?.find((p) => p.name.toLowerCase() === name);
987
+ if (match) return match.id;
988
+ degraded.push(value);
989
+ return void 0;
990
+ }
991
+ function translateIssueListQuery(parsed, flags, lookups = {}) {
992
+ const degraded = [];
993
+ const explicitStatus = nonEmptyFlag(flags.status);
994
+ const explicitPriority = flags.priority;
995
+ const explicitAssignee = nonEmptyFlag(flags.assignee);
996
+ const explicitProject = nonEmptyFlag(flags.project);
997
+ const explicitQ = nonEmptyFlag(flags.q);
998
+ const statusClause = explicitStatus ? void 0 : clause(parsed, "status");
999
+ const assigneeId = explicitAssignee ? void 0 : resolveAssigneeParam(clause(parsed, "assignee"), lookups, degraded);
1000
+ const projectId = explicitProject ? void 0 : resolveProjectParam(clause(parsed, "project"), lookups, degraded);
1001
+ degraded.push(...clause(parsed, "label")?.values ?? []);
1002
+ const priorityValues = explicitPriority ? [] : clause(parsed, "priority")?.values ?? [];
1003
+ const [onlyPriority] = priorityValues;
1004
+ const priority = priorityValues.length === 1 && onlyPriority !== void 0 && isIssuePriority(onlyPriority) ? onlyPriority : void 0;
1005
+ if (priorityValues.length > 1) degraded.push(...priorityValues);
1006
+ const statusValues = statusClause?.values ?? [];
1007
+ const textTerms = explicitQ !== void 0 ? [explicitQ] : [parsed.text];
1008
+ const parseDegraded = parsed.degraded ?? [];
1009
+ const degradedParts = explicitQ !== void 0 ? [...parseDegraded, ...degraded] : degraded;
1010
+ const q = [.../* @__PURE__ */ new Set([...textTerms, ...degradedParts])].filter((p) => p.length > 0).join(" ");
1011
+ const allDegraded = [...parseDegraded, ...degraded];
1012
+ const degradedTerms = allDegraded.length > 0 ? [...new Set(allDegraded)] : void 0;
1013
+ return {
1014
+ status: explicitStatus ?? (statusValues.length > 0 ? statusValues.join(",") : void 0),
1015
+ priority: explicitPriority ?? priority,
1016
+ assigneeId: explicitAssignee ?? assigneeId,
1017
+ projectId: explicitProject ?? projectId,
1018
+ q: q.length > 0 ? q : void 0,
1019
+ // Present only when non-empty; degradation drops filter intent, so
1020
+ // callers can explain empty-looking results.
1021
+ ...degradedTerms ? { degradedTerms } : {}
1022
+ };
1023
+ }
1024
+
1025
+ // src/commands/issue.ts
1026
+ var UUID_RE2 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1027
+ var ISSUE_PRIORITIES2 = Object.values(IssuesGetPriorityEnum);
790
1028
  var CLEAR_SENTINELS = /* @__PURE__ */ new Set(["null", "none", ""]);
791
1029
  function isClearSentinel(value) {
792
1030
  return CLEAR_SENTINELS.has(value.trim().toLowerCase());
@@ -795,7 +1033,7 @@ function collect(value, previous) {
795
1033
  return [...previous ?? [], value];
796
1034
  }
797
1035
  function refToRelationTarget(ref) {
798
- return UUID_RE.test(ref) ? { related_issue_id: ref } : { related_issue_ref: ref };
1036
+ return UUID_RE2.test(ref) ? { related_issue_id: ref } : { related_issue_ref: ref };
799
1037
  }
800
1038
  var MIME_BY_EXT = {
801
1039
  ".jpg": "image/jpeg",
@@ -821,9 +1059,104 @@ function contentTypeFor(filename) {
821
1059
  }
822
1060
  return "application/octet-stream";
823
1061
  }
1062
+ async function resolveIssueListQueryParams(client, opts, globals, teamId) {
1063
+ if (!opts.query) {
1064
+ return {
1065
+ status: opts.status,
1066
+ priority: opts.priority,
1067
+ assigneeId: opts.assignee,
1068
+ projectId: opts.project,
1069
+ q: opts.q
1070
+ };
1071
+ }
1072
+ const jsonMode = isJsonMode(globals.json);
1073
+ const parsed = parseSearchQuery(opts.query);
1074
+ const flags = {
1075
+ status: opts.status,
1076
+ priority: opts.priority,
1077
+ assignee: opts.assignee,
1078
+ project: opts.project,
1079
+ q: opts.q
1080
+ };
1081
+ const lookups = {};
1082
+ const needed = issueListQueryLookupsNeeded(parsed, flags);
1083
+ const settle = (p) => p.then(
1084
+ (value) => [value, void 0],
1085
+ (err) => [void 0, err]
1086
+ );
1087
+ const describeLookupError = (err) => err instanceof Error ? err.message : String(err);
1088
+ const [viewerResult, projectsResult] = await Promise.all([
1089
+ needed.viewerId ? settle(client.meGet()) : void 0,
1090
+ needed.projects ? settle(client.projectsGet()) : void 0
1091
+ ]);
1092
+ const [viewer, viewerLookupError] = viewerResult ?? [];
1093
+ const [projects, projectsLookupError] = projectsResult ?? [];
1094
+ const lookupFailures = [];
1095
+ if (needed.viewerId && !viewer) {
1096
+ lookupFailures.push({ part: "assignee:me", error: viewerLookupError });
1097
+ }
1098
+ if (needed.projects && !projects) {
1099
+ lookupFailures.push({ part: "project name", error: projectsLookupError });
1100
+ }
1101
+ if (!jsonMode) {
1102
+ for (const { part, error } of lookupFailures) {
1103
+ process.stderr.write(
1104
+ `warning: ${part} lookup failed (${sanitizeTerminal(describeLookupError(error))}); degrading to text search
1105
+ `
1106
+ );
1107
+ }
1108
+ }
1109
+ if (viewer) lookups.viewerId = viewer.id;
1110
+ if (projects) {
1111
+ const scoped = teamId ? projects.filter((p) => p.team_id === teamId) : projects;
1112
+ const nameCounts = /* @__PURE__ */ new Map();
1113
+ for (const p of scoped) {
1114
+ const key = p.name.toLowerCase();
1115
+ nameCounts.set(key, (nameCounts.get(key) ?? 0) + 1);
1116
+ }
1117
+ lookups.projects = scoped.filter((p) => (nameCounts.get(p.name.toLowerCase()) ?? 0) === 1).map((p) => ({ id: p.id, name: p.name }));
1118
+ }
1119
+ const translated = translateIssueListQuery(parsed, flags, lookups);
1120
+ if (jsonMode) {
1121
+ const diagnostics = {};
1122
+ if (lookupFailures.length > 0) {
1123
+ diagnostics.lookupFailures = lookupFailures.map(({ part }) => part);
1124
+ diagnostics.lookupErrors = Object.fromEntries(
1125
+ lookupFailures.map(({ part, error }) => [
1126
+ part,
1127
+ sanitizeTerminal(describeLookupError(error))
1128
+ ])
1129
+ );
1130
+ }
1131
+ if (translated.degradedTerms?.length) {
1132
+ diagnostics.degradedTerms = translated.degradedTerms;
1133
+ }
1134
+ if (Object.keys(diagnostics).length > 0) {
1135
+ process.stderr.write(`${JSON.stringify(diagnostics)}
1136
+ `);
1137
+ }
1138
+ } else if (translated.degradedTerms?.length) {
1139
+ process.stderr.write(
1140
+ `note: unsupported query parts (${sanitizeTerminal(
1141
+ translated.degradedTerms.join(", ")
1142
+ )}) were added to the text search
1143
+ `
1144
+ );
1145
+ }
1146
+ return {
1147
+ status: translated.status,
1148
+ priority: translated.priority,
1149
+ assigneeId: translated.assigneeId,
1150
+ projectId: translated.projectId,
1151
+ q: translated.q
1152
+ };
1153
+ }
824
1154
  function registerIssueCommands(program2) {
825
1155
  const issue = program2.command("issue").description("Manage Onus issues");
826
- issue.command("list").description("List and filter issues").option("-j, --json", "Output raw JSON").option("--team <team>", "Team UUID or KEY (e.g. ONUS)").option("--project <projectId>", "Filter by project UUID").option("--assignee <assigneeId>", "Filter by assignee UUID").option("--parent <parentId>", "Filter by parent issue UUID").option("--status <status>", "Filter by status (backlog|todo|in_progress|done|canceled)").option("--q <query>", "Free-text search query").option(
1156
+ issue.command("list").description("List and filter issues").option("-j, --json", "Output raw JSON").option("--team <team>", "Team UUID or KEY (e.g. ONUS)").option("--project <projectId>", "Filter by project UUID").option("--assignee <assigneeId>", "Filter by assignee UUID").option("--parent <parentId>", "Filter by parent issue UUID").option("--status <status>", "Filter by status (backlog|todo|in_progress|done|canceled)").option("--priority <priority>", `Filter by priority (${ISSUE_PRIORITIES2.join("|")})`).option("--q <query>", "Free-text search query (supports display keys like ONUS-42)").option(
1157
+ "--query <query>",
1158
+ `Structured query mini-syntax, e.g. 'status:todo label:bug "rate limit"' (status/assignee/project and single-value priority map to /v1 filters; label, multi-value priority and unresolvable values degrade to text search; unsupported parts are appended to the text search; attribute values are single tokens (status:in_progress, not in progress) or a quoted span (status:"in progress"); text joins into ONE phrase; explicit --status/--priority/--assignee/--project/--q flags win on conflict)`
1159
+ ).option(
827
1160
  "--created-after <date>",
828
1161
  "Inclusive lower bound on created_at (yyyy-MM-dd or ISO datetime)"
829
1162
  ).option(
@@ -843,16 +1176,30 @@ function registerIssueCommands(program2) {
843
1176
  "Inclusive upper bound on completed_at (yyyy-MM-dd or ISO datetime)"
844
1177
  ).option("--due-after <date>", "Inclusive lower bound on due_date (yyyy-MM-dd)").option("--due-before <date>", "Inclusive upper bound on due_date (yyyy-MM-dd)").option("--limit <n>", "Limit result count", (v) => Number(v)).option("--cursor <cursor>", "Pagination cursor").action(async function(opts) {
845
1178
  const globals = this.optsWithGlobals();
1179
+ if (opts.priority !== void 0 && !ISSUE_PRIORITIES2.includes(opts.priority)) {
1180
+ await handleApiError(
1181
+ {
1182
+ status: 400,
1183
+ json: () => Promise.resolve({
1184
+ error: "invalid_request",
1185
+ message: `Invalid priority: ${sanitizeTerminal(opts.priority)} (valid values: ${ISSUE_PRIORITIES2.join("|")})`
1186
+ })
1187
+ },
1188
+ globals.json
1189
+ );
1190
+ }
846
1191
  try {
847
1192
  const client = createApiClient({ token: globals.token, apiUrl: globals.apiUrl });
848
1193
  const teamId = opts.team ? await resolveTeamId(client, opts.team) : void 0;
1194
+ const resolved = await resolveIssueListQueryParams(client, opts, globals, teamId);
849
1195
  const response = await client.issuesGet({
850
- status: opts.status,
1196
+ status: resolved.status,
1197
+ priority: resolved.priority,
851
1198
  teamId,
852
- assigneeId: opts.assignee,
853
- projectId: opts.project,
1199
+ assigneeId: resolved.assigneeId,
1200
+ projectId: resolved.projectId,
854
1201
  parentId: opts.parent,
855
- q: opts.q,
1202
+ q: resolved.q,
856
1203
  createdAfter: opts.createdAfter,
857
1204
  createdBefore: opts.createdBefore,
858
1205
  updatedAfter: opts.updatedAfter,
@@ -883,6 +1230,11 @@ function registerIssueCommands(program2) {
883
1230
  console.log(` ${parts.map((p) => sanitizeTerminal(String(p))).join(" \xB7 ")}`);
884
1231
  }
885
1232
  }
1233
+ const count = `${items.length} ${items.length === 1 ? "issue" : "issues"}`;
1234
+ console.log(
1235
+ `
1236
+ ${response?.next_cursor ? `${count} (this page; more available)` : count}`
1237
+ );
886
1238
  if (response?.next_cursor) {
887
1239
  console.log(`
888
1240
  Next cursor: ${sanitizeTerminal(response.next_cursor)}`);
@@ -1065,7 +1417,9 @@ Comments (${issue2.comments.length}):`);
1065
1417
  [
1066
1418
  "onus issue list",
1067
1419
  "onus issue list --status in_progress --team ONUS",
1068
- "onus issue list --json"
1420
+ "onus issue list --q ONUS-42 --priority high",
1421
+ "onus issue list --json",
1422
+ "onus issue list --query 'assignee:me status:todo,in_progress'"
1069
1423
  ]
1070
1424
  );
1071
1425
  withExamples(
@@ -1638,6 +1992,210 @@ function registerLabelCommands(program2) {
1638
1992
  );
1639
1993
  }
1640
1994
 
1995
+ // src/commands/kb.ts
1996
+ var DOC_UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1997
+ function docRefError(status, message) {
1998
+ const err = new Error(message);
1999
+ err.status = status;
2000
+ return err;
2001
+ }
2002
+ async function resolveDocRef(client, ref) {
2003
+ if (DOC_UUID_RE.test(ref)) return ref;
2004
+ const page = await client.kbDocsGet({ slug: ref, limit: 5 });
2005
+ if (page.data.length === 0) {
2006
+ throw docRefError(404, `doc not found: ${ref}`);
2007
+ }
2008
+ if (page.data.length > 1) {
2009
+ const candidates = page.data.map((d) => ` ${d.ownerType}/${d.ownerId}: ${d.slug} \u2014 ${d.title}`).join("\n");
2010
+ throw docRefError(
2011
+ 400,
2012
+ `ambiguous slug "${ref}" matches ${page.data.length} docs:
2013
+ ${candidates}
2014
+ Pass the doc UUID to resolve unambiguously.`
2015
+ );
2016
+ }
2017
+ return page.data[0].id;
2018
+ }
2019
+ function renderResultRows(results) {
2020
+ if (results.length === 0) {
2021
+ console.log("No matching docs.");
2022
+ return;
2023
+ }
2024
+ for (const r of results) {
2025
+ console.log(`\u2022 ${sanitizeTerminal(r.slug)} ${r.score} ${sanitizeTerminal(r.title)}`);
2026
+ const parts = [r.kind, `${r.ownerType}/${r.ownerId}`];
2027
+ if (r.matchType) parts.push(r.matchType);
2028
+ if (r.stale) parts.push(`stale (${r.confidence})`);
2029
+ console.log(` ${parts.map((p) => sanitizeTerminal(String(p))).join(" \xB7 ")}`);
2030
+ }
2031
+ }
2032
+ function registerKbCommands(program2) {
2033
+ const kb = program2.command("kb").description("Read-only knowledge base docs (list, get, search, related, locate)");
2034
+ kb.command("list").description("List knowledge-base docs (owner/kind/tag filters, keyset pagination)").option("--team <team>", "Owner team UUID or KEY (e.g. ONUS)").option("--project <projectId>", "Owner project UUID").option("--repo <repoId>", "Owner repo UUID").option("--kind <kind>", "Doc kind (index|area|process|incident)").option("--tag <tag>", "Taxonomy tag").option("--limit <n>", "Limit result count (1-100)", (v) => Number(v)).option("--cursor <cursor>", "Pagination cursor").option("-j, --json", "Output raw JSON").action(async function(opts) {
2035
+ const globals = this.optsWithGlobals();
2036
+ if ([opts.team, opts.project, opts.repo].filter(Boolean).length > 1) {
2037
+ console.error("Provide at most one of --team, --project, --repo.");
2038
+ process.exit(2);
2039
+ return;
2040
+ }
2041
+ try {
2042
+ const client = createApiClient({ token: globals.token, apiUrl: globals.apiUrl });
2043
+ const ownerType = opts.team ? "team" : opts.project ? "project" : opts.repo ? "repo" : void 0;
2044
+ const ownerId = opts.team ? await resolveTeamId(client, opts.team) : opts.project ?? opts.repo;
2045
+ const response = await client.kbDocsGet({
2046
+ ownerType,
2047
+ ownerId,
2048
+ kind: opts.kind,
2049
+ tag: opts.tag,
2050
+ limit: opts.limit,
2051
+ cursor: opts.cursor
2052
+ });
2053
+ const items = response.data ?? [];
2054
+ printJsonOrFormatted(response, globals.json, () => {
2055
+ if (items.length === 0) {
2056
+ console.log("No KB docs found.");
2057
+ return;
2058
+ }
2059
+ for (const doc of items) {
2060
+ console.log(`\u2022 ${sanitizeTerminal(doc.slug)} ${sanitizeTerminal(doc.title)}`);
2061
+ const parts = [doc.kind, `${doc.ownerType}/${doc.ownerId}`, doc.status];
2062
+ if (doc.tags.length > 0) parts.push(doc.tags.join(", "));
2063
+ if (doc.staleness.stale) parts.push(`stale (${doc.staleness.confidence})`);
2064
+ console.log(` ${parts.map((p) => sanitizeTerminal(String(p))).join(" \xB7 ")}`);
2065
+ }
2066
+ if (response.next_cursor) {
2067
+ console.log(`
2068
+ Next cursor: ${sanitizeTerminal(response.next_cursor)}`);
2069
+ }
2070
+ });
2071
+ } catch (err) {
2072
+ await handleApiError(err, globals.json);
2073
+ }
2074
+ });
2075
+ kb.command("get <slug-or-id>").description("Get a KB doc by slug or UUID (content, metadata, staleness)").option("-j, --json", "Output raw JSON").action(async function(ref) {
2076
+ const globals = this.optsWithGlobals();
2077
+ try {
2078
+ const client = createApiClient({ token: globals.token, apiUrl: globals.apiUrl });
2079
+ const id = await resolveDocRef(client, ref);
2080
+ const doc = await client.kbDocsIdGet({ id });
2081
+ printJsonOrFormatted(doc, globals.json, () => {
2082
+ console.log(
2083
+ formatRecord([
2084
+ { label: "ID", value: doc.id },
2085
+ { label: "Slug", value: doc.slug },
2086
+ { label: "Title", value: doc.title },
2087
+ { label: "Kind", value: doc.kind },
2088
+ { label: "Owner", value: `${doc.ownerType}/${doc.ownerId}` },
2089
+ { label: "Tags", value: doc.tags.join(", ") },
2090
+ { label: "Paths governed", value: doc.pathsGoverned.join(", ") },
2091
+ { label: "Source", value: doc.sourceType },
2092
+ { label: "Status", value: doc.status },
2093
+ {
2094
+ label: "Stale",
2095
+ value: `${doc.staleness.stale} (confidence ${doc.staleness.confidence})`
2096
+ },
2097
+ { label: "Stale reason", value: doc.staleness.reason },
2098
+ { label: "Updated", value: doc.updatedAt }
2099
+ ])
2100
+ );
2101
+ if (doc.content) {
2102
+ console.log(`
2103
+ Content:
2104
+ ${sanitizeTerminal(doc.content)}`);
2105
+ }
2106
+ });
2107
+ } catch (err) {
2108
+ await handleApiError(err, globals.json);
2109
+ }
2110
+ });
2111
+ kb.command("search <query>").description("Search KB docs (scored results + snippets)").option("--mode <mode>", "Retrieval mode (string|semantic|hybrid|related)").option("--doc <slug-or-id>", "Seed doc (required with --mode related)").option("--limit <n>", "Limit result count", (v) => Number(v)).option("-j, --json", "Output raw JSON").action(async function(query, opts) {
2112
+ const globals = this.optsWithGlobals();
2113
+ if (opts.mode === "related" && !opts.doc) {
2114
+ console.error("--doc is required when --mode related.");
2115
+ process.exit(2);
2116
+ return;
2117
+ }
2118
+ try {
2119
+ const client = createApiClient({ token: globals.token, apiUrl: globals.apiUrl });
2120
+ const docId = opts.doc ? await resolveDocRef(client, opts.doc) : void 0;
2121
+ const response = await client.kbSearchGet({
2122
+ q: query,
2123
+ mode: opts.mode,
2124
+ docId,
2125
+ limit: opts.limit
2126
+ });
2127
+ printJsonOrFormatted(response, globals.json, () => {
2128
+ console.log(`Mode: ${response.mode}`);
2129
+ renderResultRows(response.results);
2130
+ });
2131
+ } catch (err) {
2132
+ await handleApiError(err, globals.json);
2133
+ }
2134
+ });
2135
+ kb.command("related <slug-or-id>").description("Expand docs related to a seed doc (slug or UUID)").option("-j, --json", "Output raw JSON").action(async function(ref) {
2136
+ const globals = this.optsWithGlobals();
2137
+ try {
2138
+ const client = createApiClient({ token: globals.token, apiUrl: globals.apiUrl });
2139
+ const id = await resolveDocRef(client, ref);
2140
+ const response = await client.kbDocsIdRelatedGet({ id });
2141
+ printJsonOrFormatted(response, globals.json, () => {
2142
+ console.log(`Mode: ${response.mode}`);
2143
+ renderResultRows(response.results);
2144
+ });
2145
+ } catch (err) {
2146
+ await handleApiError(err, globals.json);
2147
+ }
2148
+ });
2149
+ kb.command("locate").description("Locate docs for an issue or free-text query (agent routing)").option("--issue <issueId>", "Issue UUID to route from").option("--query <text>", "Free-text query to route from").option("-j, --json", "Output raw JSON").action(async function(opts) {
2150
+ const globals = this.optsWithGlobals();
2151
+ if ((opts.issue ? 1 : 0) + (opts.query ? 1 : 0) !== 1) {
2152
+ console.error("Provide exactly one of --issue or --query.");
2153
+ process.exit(2);
2154
+ return;
2155
+ }
2156
+ try {
2157
+ const client = createApiClient({ token: globals.token, apiUrl: globals.apiUrl });
2158
+ const response = await client.kbLocateGet({ issue: opts.issue, q: opts.query });
2159
+ printJsonOrFormatted(response, globals.json, () => {
2160
+ console.log(`Mode: ${response.mode}`);
2161
+ renderResultRows(response.results);
2162
+ });
2163
+ } catch (err) {
2164
+ await handleApiError(err, globals.json);
2165
+ }
2166
+ });
2167
+ withExamples(
2168
+ kb.commands.find((c) => c.name() === "list"),
2169
+ [
2170
+ "onus kb list",
2171
+ "onus kb list --team ONUS --kind area --tag auth --json",
2172
+ "onus kb list --cursor <cursor>"
2173
+ ]
2174
+ );
2175
+ withExamples(
2176
+ kb.commands.find((c) => c.name() === "get"),
2177
+ ["onus kb get auth-overview", "onus kb get 44444444-4444-4444-8444-444444444444"]
2178
+ );
2179
+ withExamples(
2180
+ kb.commands.find((c) => c.name() === "search"),
2181
+ [
2182
+ 'onus kb search "auth flow" --mode hybrid --limit 10',
2183
+ 'onus kb search "runbook" --mode related --doc auth-overview'
2184
+ ]
2185
+ );
2186
+ withExamples(
2187
+ kb.commands.find((c) => c.name() === "related"),
2188
+ ["onus kb related auth-overview"]
2189
+ );
2190
+ withExamples(
2191
+ kb.commands.find((c) => c.name() === "locate"),
2192
+ [
2193
+ "onus kb locate --issue 33333333-3333-4333-8333-333333333333",
2194
+ 'onus kb locate --query "how do rotations work"'
2195
+ ]
2196
+ );
2197
+ }
2198
+
1641
2199
  // src/commands/dsn.ts
1642
2200
  function registerDsnCommands(program2) {
1643
2201
  const dsn = program2.command("dsn").description("Manage team ingest keys (DSNs) \u2014 errors + analytics ingestion credentials");
@@ -2250,7 +2808,7 @@ function registerWatchCommand(program2) {
2250
2808
  const opts = this.opts();
2251
2809
  let teamId = opts.team;
2252
2810
  if (teamId) {
2253
- const { createApiClient: createApiClient2 } = await import("./client-CBLKSKYG.js");
2811
+ const { createApiClient: createApiClient2 } = await import("./client-R7T5S6RN.js");
2254
2812
  const client = createApiClient2({ token, apiUrl });
2255
2813
  teamId = await resolveTeamId(client, teamId);
2256
2814
  }
@@ -2275,6 +2833,7 @@ function buildProgram() {
2275
2833
  registerIssueCommands(program2);
2276
2834
  registerCommentCommands(program2);
2277
2835
  registerLabelCommands(program2);
2836
+ registerKbCommands(program2);
2278
2837
  registerDsnCommands(program2);
2279
2838
  registerSkillCommands(program2);
2280
2839
  registerTemplateCommands(program2);