@brunyee-studio/onus-cli 2.30.0 → 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 +20 -1
- package/dist/bin.js +568 -9
- package/dist/{chunk-HJQGUY5T.js → chunk-35HNSEP2.js} +427 -1
- package/dist/{client-QNEDGCP7.js → client-R7T5S6RN.js} +1 -1
- package/dist/index.d.ts +1748 -3
- package/dist/index.js +1353 -1
- package/package.json +1 -1
- package/skills/onus-cli/SKILL.md +96 -2
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-
|
|
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
|
|
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:
|
|
1196
|
+
status: resolved.status,
|
|
1197
|
+
priority: resolved.priority,
|
|
851
1198
|
teamId,
|
|
852
|
-
assigneeId:
|
|
853
|
-
projectId:
|
|
1199
|
+
assigneeId: resolved.assigneeId,
|
|
1200
|
+
projectId: resolved.projectId,
|
|
854
1201
|
parentId: opts.parent,
|
|
855
|
-
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 --
|
|
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-
|
|
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);
|