@oxygen-agent/cli 1.591.1 → 1.615.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.
Files changed (28) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.d.ts +18 -0
  3. package/dist/command-manifest.js +106 -5
  4. package/dist/help.js +2 -1
  5. package/dist/index.js +273 -38
  6. package/dist/skills.d.ts +6 -0
  7. package/dist/skills.js +200 -1
  8. package/node_modules/@oxygen/shared/dist/capability-discovery.d.ts +36 -0
  9. package/node_modules/@oxygen/shared/dist/capability-discovery.js +766 -0
  10. package/node_modules/@oxygen/shared/dist/cli-result.js +4 -3
  11. package/node_modules/@oxygen/shared/dist/index.d.ts +2 -0
  12. package/node_modules/@oxygen/shared/dist/index.js +2 -0
  13. package/node_modules/@oxygen/shared/dist/pricing-sheet.d.ts +1 -1
  14. package/node_modules/@oxygen/shared/dist/pricing-sheet.js +1 -1
  15. package/node_modules/@oxygen/shared/dist/recipes.d.ts +1 -1
  16. package/node_modules/@oxygen/shared/dist/recipes.js +4 -2
  17. package/node_modules/@oxygen/shared/dist/sequence-template.js +0 -0
  18. package/node_modules/@oxygen/shared/dist/sequence-terminal-events.d.ts +47 -0
  19. package/node_modules/@oxygen/shared/dist/sequence-terminal-events.js +70 -0
  20. package/node_modules/@oxygen/shared/dist/version.d.ts +1 -1
  21. package/node_modules/@oxygen/shared/dist/version.js +1 -1
  22. package/node_modules/@oxygen/workflows/dist/graph/lint.js +3 -0
  23. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.d.ts +5 -0
  24. package/node_modules/@oxygen/workflows/dist/graph/manifest-schema.js +5 -0
  25. package/node_modules/@oxygen/workflows/dist/graph/types.d.ts +2 -0
  26. package/node_modules/@oxygen/workflows/dist/index.d.ts +4 -0
  27. package/node_modules/@oxygen/workflows/dist/index.js +5 -0
  28. package/package.json +1 -1
package/dist/skills.js CHANGED
@@ -2,7 +2,7 @@ import { spawnSync } from "node:child_process";
2
2
  import { existsSync, mkdirSync, mkdtempSync, readdirSync, rmSync, writeFileSync } from "node:fs";
3
3
  import { homedir, tmpdir } from "node:os";
4
4
  import { dirname, join, resolve } from "node:path";
5
- import { OxygenError, toFailure } from "@oxygen/shared";
5
+ import { OxygenError, inferCapabilityRoute, serializeCapabilityRoute, toFailure, } from "@oxygen/shared";
6
6
  import { defaultApiUrl, loadCredentials, normalizeApiUrl } from "./credentials.js";
7
7
  import { requestOxygen } from "./http-client.js";
8
8
  import { isRecord, readOption } from "./util.js";
@@ -86,6 +86,85 @@ export async function listAgentSkills(options, runtime = {}) {
86
86
  install_command_args: recommendedSkillsInstallArgs(apiUrl),
87
87
  };
88
88
  }
89
+ export async function searchAgentSkills(query, options, runtime = {}) {
90
+ const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
91
+ const source = await resolveSkillsInstallSource({ apiUrl, ...runtime });
92
+ const index = await inspectSkillIndex(source.index_url, runtime.fetch);
93
+ if (!index.reachable) {
94
+ throw new OxygenError("skills_index_unreachable", "Oxygen skill index is not reachable.", {
95
+ details: { index_url: source.index_url, error: index.error },
96
+ exitCode: 1,
97
+ });
98
+ }
99
+ const normalized = query.trim();
100
+ const tokens = tokenizeSkillQuery(normalized);
101
+ const route = inferCapabilityRoute(normalized);
102
+ const requestedLimit = Number.parseInt(readOption(options.limit) ?? "5", 10);
103
+ const limit = Number.isFinite(requestedLimit) ? Math.min(10, Math.max(1, requestedLimit)) : 5;
104
+ const matches = index.skills
105
+ .map((skill) => ({
106
+ skill,
107
+ score: scoreSkill(skill, tokens, route?.card.skills ?? []),
108
+ matching_documents: matchingSkillDocuments(skill, tokens),
109
+ }))
110
+ .filter((entry) => entry.score > 0)
111
+ .sort((a, b) => b.score - a.score || a.skill.name.localeCompare(b.skill.name));
112
+ return {
113
+ query: normalized,
114
+ route: serializeCapabilityRoute(route),
115
+ total_matches: matches.length,
116
+ returned: Math.min(limit, matches.length),
117
+ skills: matches.slice(0, limit).map(({ skill, score, matching_documents }) => ({
118
+ name: skill.name,
119
+ title: skill.title,
120
+ description: skill.description,
121
+ dependencies: skill.dependencies,
122
+ capability_ids: skill.capability_ids,
123
+ endpoint_sections: skill.endpoint_sections,
124
+ score,
125
+ matching_documents: matching_documents.slice(0, 5),
126
+ })),
127
+ hydrate: "oxygen skills get <exact-skill-name> --json",
128
+ };
129
+ }
130
+ export async function getAgentSkill(skillName, options, runtime = {}) {
131
+ const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
132
+ const source = await resolveSkillsInstallSource({ apiUrl, ...runtime });
133
+ const index = await inspectSkillIndex(source.index_url, runtime.fetch);
134
+ if (!index.reachable) {
135
+ throw new OxygenError("skills_index_unreachable", "Oxygen skill index is not reachable.", {
136
+ details: { index_url: source.index_url, error: index.error },
137
+ exitCode: 1,
138
+ });
139
+ }
140
+ const normalized = skillName.trim();
141
+ const skill = index.skills.find((entry) => entry.name === normalized);
142
+ if (!skill) {
143
+ throw new OxygenError("skill_not_found", `Unknown Oxygen skill: ${normalized}`, {
144
+ details: { available_names: index.skill_names },
145
+ exitCode: 2,
146
+ });
147
+ }
148
+ if (!skill.entrypoint) {
149
+ throw new OxygenError("skill_entrypoint_missing", `Oxygen skill ${normalized} has no entrypoint.`, {
150
+ exitCode: 1,
151
+ });
152
+ }
153
+ const fetchImpl = runtime.fetch ?? fetch;
154
+ const response = await fetchImpl(skill.entrypoint, { headers: { Accept: "text/markdown" } });
155
+ if (!response.ok) {
156
+ throw new OxygenError("skill_entrypoint_unreachable", `Unable to hydrate Oxygen skill ${normalized}.`, {
157
+ details: { status: response.status },
158
+ exitCode: 1,
159
+ });
160
+ }
161
+ const content = await response.text();
162
+ return {
163
+ ...skill,
164
+ content,
165
+ install: `oxygen skills install --skill ${skill.name} --json`,
166
+ };
167
+ }
89
168
  export async function doctorAgentSkills(options, runtime = {}) {
90
169
  const apiUrl = normalizeApiUrl(readOption(options.apiUrl) ?? defaultApiUrl());
91
170
  const source = await resolveSkillsInstallSource({ apiUrl, ...runtime });
@@ -547,6 +626,18 @@ function parseSkillIndex(value) {
547
626
  .filter((dep) => typeof dep === "string" && dep.trim().length > 0)
548
627
  .map((dep) => dep.trim())
549
628
  : [],
629
+ capability_ids: readSkillIndexStrings(entry.capability_ids),
630
+ endpoint_sections: readSkillIndexStrings(entry.endpoint_sections),
631
+ documents: Array.isArray(entry.documents)
632
+ ? entry.documents
633
+ .map(parseSkillIndexDocument)
634
+ .filter((document) => document !== null)
635
+ : [],
636
+ files: Array.isArray(entry.files)
637
+ ? entry.files
638
+ .filter((file) => typeof file === "string" && file.trim().length > 0)
639
+ .map((file) => file.trim())
640
+ : [],
550
641
  };
551
642
  })
552
643
  .filter((entry) => entry !== null);
@@ -556,6 +647,114 @@ function parseSkillIndex(value) {
556
647
  skills,
557
648
  };
558
649
  }
650
+ function parseSkillIndexDocument(value) {
651
+ if (!isRecord(value) || typeof value.path !== "string" || !value.path.trim())
652
+ return null;
653
+ const readStrings = (candidate) => Array.isArray(candidate)
654
+ ? candidate
655
+ .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
656
+ .map((entry) => entry.trim())
657
+ : [];
658
+ return {
659
+ path: value.path.trim(),
660
+ kind: typeof value.kind === "string" ? value.kind : null,
661
+ title: typeof value.title === "string" ? value.title : null,
662
+ tags: readStrings(value.tags),
663
+ providers: readStrings(value.providers),
664
+ url: typeof value.url === "string" ? value.url : null,
665
+ };
666
+ }
667
+ function readSkillIndexStrings(candidate) {
668
+ return Array.isArray(candidate)
669
+ ? candidate
670
+ .filter((entry) => typeof entry === "string" && entry.trim().length > 0)
671
+ .map((entry) => entry.trim())
672
+ : [];
673
+ }
674
+ const SKILL_SEARCH_STOP_WORDS = new Set([
675
+ "a", "an", "and", "for", "from", "how", "i", "in", "is", "of", "on",
676
+ "or", "please", "the", "to", "want", "with",
677
+ ]);
678
+ function tokenizeSkillQuery(query) {
679
+ return query
680
+ .toLowerCase()
681
+ .split(/[^a-z0-9]+/)
682
+ .filter((token) => token.length > 0 && !SKILL_SEARCH_STOP_WORDS.has(token));
683
+ }
684
+ function skillSearchText(skill) {
685
+ return [
686
+ skill.name,
687
+ skill.title ?? "",
688
+ skill.description ?? "",
689
+ ...skill.capability_ids,
690
+ ...skill.endpoint_sections,
691
+ ...skill.documents.flatMap((document) => [
692
+ document.path,
693
+ document.title ?? "",
694
+ document.kind ?? "",
695
+ ...document.tags,
696
+ ...document.providers,
697
+ ]),
698
+ ].join(" ").toLowerCase();
699
+ }
700
+ function scoreSkill(skill, tokens, recommendedSkills) {
701
+ if (tokens.length === 0)
702
+ return 0;
703
+ const name = skill.name.toLowerCase();
704
+ const title = skill.title?.toLowerCase() ?? "";
705
+ const description = skill.description?.toLowerCase() ?? "";
706
+ const allText = skillSearchText(skill);
707
+ let score = 0;
708
+ let matched = 0;
709
+ for (const token of tokens) {
710
+ let tokenMatched = false;
711
+ if (name.includes(token)) {
712
+ score += 10;
713
+ tokenMatched = true;
714
+ }
715
+ if (title.includes(token)) {
716
+ score += 6;
717
+ tokenMatched = true;
718
+ }
719
+ if (description.includes(token)) {
720
+ score += 3;
721
+ tokenMatched = true;
722
+ }
723
+ if (allText.includes(token)) {
724
+ score += 1;
725
+ tokenMatched = true;
726
+ }
727
+ if (tokenMatched)
728
+ matched += 1;
729
+ }
730
+ if (matched > 0)
731
+ score += Math.round((matched / tokens.length) * 20);
732
+ const routeIndex = recommendedSkills.indexOf(skill.name);
733
+ if (routeIndex >= 0)
734
+ score += 400 - (routeIndex * 20);
735
+ return score;
736
+ }
737
+ function matchingSkillDocuments(skill, tokens) {
738
+ if (tokens.length === 0)
739
+ return [];
740
+ return skill.documents
741
+ .map((document) => ({
742
+ document,
743
+ score: tokens.reduce((total, token) => {
744
+ const text = [
745
+ document.path,
746
+ document.title ?? "",
747
+ document.kind ?? "",
748
+ ...document.tags,
749
+ ...document.providers,
750
+ ].join(" ").toLowerCase();
751
+ return total + (text.includes(token) ? 1 : 0);
752
+ }, 0),
753
+ }))
754
+ .filter((entry) => entry.score > 0)
755
+ .sort((a, b) => b.score - a.score || a.document.path.localeCompare(b.document.path))
756
+ .map((entry) => entry.document);
757
+ }
559
758
  function inspectSkillsInstaller() {
560
759
  const invocation = resolveCmdShimInvocation("npx", ["--version"], process.platform, process.env);
561
760
  const result = spawnSync(invocation.executable, invocation.args, {
@@ -0,0 +1,36 @@
1
+ import { type RecipePrimitive } from "./recipes.js";
2
+ export type CapabilityLayer = "Control" | "Knowledge" | "Data" | "Action" | "External";
3
+ export type CapabilityPosture = "read_only" | "workspace_write" | "paid_read" | "external_write" | "mixed";
4
+ export type CapabilityRouteCard = {
5
+ id: string;
6
+ layer: CapabilityLayer;
7
+ primitive: RecipePrimitive | null;
8
+ owns: string;
9
+ notFor: string;
10
+ execution: string;
11
+ posture: CapabilityPosture;
12
+ gatewayTools: readonly string[];
13
+ gatewayCommands: readonly string[];
14
+ skills: readonly string[];
15
+ endpointSections: readonly string[];
16
+ intentTerms: readonly string[];
17
+ negativeTerms?: readonly string[];
18
+ };
19
+ export type PrimitiveRouteCard = CapabilityRouteCard & {
20
+ primitive: RecipePrimitive;
21
+ };
22
+ export type CapabilityRouteMatch = {
23
+ card: CapabilityRouteCard;
24
+ recommendedTools: string[];
25
+ recommendedCommands: string[];
26
+ };
27
+ export type PrimitiveRouteMatch = CapabilityRouteMatch & {
28
+ card: PrimitiveRouteCard;
29
+ };
30
+ export declare const OXYGEN_CAPABILITY_ROUTES: readonly CapabilityRouteCard[];
31
+ export declare const OXYGEN_PRIMITIVE_ROUTES: readonly PrimitiveRouteCard[];
32
+ export declare function getCapabilityRoute(id: string): CapabilityRouteCard | null;
33
+ export declare function getCapabilityRouteMatch(id: string): CapabilityRouteMatch | null;
34
+ export declare function inferCapabilityRoute(query: string): CapabilityRouteMatch | null;
35
+ export declare function inferPrimitiveRoute(query: string): PrimitiveRouteMatch | null;
36
+ export declare function serializeCapabilityRoute(route: CapabilityRouteMatch | null): Record<string, unknown> | null;