@opengeni/ogtool 0.3.26 → 0.3.31-canary.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.
@@ -0,0 +1,12 @@
1
+ import type { AttemptToolCatalog, AttemptToolCatalogEntry } from "@opengeni/codemode";
2
+ export declare function shortDescription(entry: AttemptToolCatalogEntry): string;
3
+ export type ListOptions = {
4
+ full: boolean;
5
+ json: boolean;
6
+ query: string;
7
+ limit: number;
8
+ offset: number;
9
+ };
10
+ export declare function parseListOptions(args: string[]): ListOptions;
11
+ /** Render first, then emit: the bound includes metadata, escaping, hints, and newline. */
12
+ export declare function compactOutput(catalog: AttemptToolCatalog, options: ListOptions): string;
package/dist/cli.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  import { type AttemptToolCatalog, type AttemptToolCatalogEntry } from "@opengeni/codemode";
2
2
  export declare function resolveTool(catalog: AttemptToolCatalog, name: string): AttemptToolCatalogEntry;
3
3
  export declare function listProjection(catalog: AttemptToolCatalog): Record<string, unknown>;
4
+ export declare function showOutput(catalog: AttemptToolCatalog, name: string): string;
4
5
  export declare function parseToolArguments(raw: string | undefined): Record<string, unknown>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/ogtool",
3
- "version": "0.3.26",
3
+ "version": "0.3.31-canary.0",
4
4
  "description": "Codemode CLI and typed client entrypoint for an OpenGeni execution attempt.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -37,7 +37,7 @@
37
37
  "prepublishOnly": "bash ../../scripts/prepublish-guard"
38
38
  },
39
39
  "dependencies": {
40
- "@opengeni/codemode": "^0.4.22"
40
+ "@opengeni/codemode": "^0.4.27-canary.0"
41
41
  },
42
42
  "engines": {
43
43
  "node": ">=18"
@@ -0,0 +1,111 @@
1
+ import type { AttemptToolCatalog, AttemptToolCatalogEntry } from "@opengeni/codemode";
2
+
3
+ const DESCRIPTION_MAX_CHARS = 160;
4
+ const LIST_MAX_BYTES = 16 * 1024;
5
+
6
+ function normalizedDescription(entry: AttemptToolCatalogEntry): string {
7
+ return (entry.description || entry.title || "")
8
+ .split(/\p{White_Space}+/u)
9
+ .filter(Boolean)
10
+ .join(" ");
11
+ }
12
+
13
+ export function shortDescription(entry: AttemptToolCatalogEntry): string {
14
+ const text = normalizedDescription(entry);
15
+ const characters = Array.from(text);
16
+ return characters.length <= DESCRIPTION_MAX_CHARS
17
+ ? text
18
+ : `${characters.slice(0, DESCRIPTION_MAX_CHARS - 1).join("")}…`;
19
+ }
20
+
21
+ /** Terminal-only presentation: never mutate catalog, query, or JSON content. */
22
+ function terminalDescription(text: string): string {
23
+ return text.replace(
24
+ /[\u0000-\u001f\u007f-\u009f]/gu,
25
+ (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`,
26
+ );
27
+ }
28
+
29
+ export type ListOptions = {
30
+ full: boolean;
31
+ json: boolean;
32
+ query: string;
33
+ limit: number;
34
+ offset: number;
35
+ };
36
+
37
+ export function parseListOptions(args: string[]): ListOptions {
38
+ const options: ListOptions = { full: false, json: false, query: "", limit: 50, offset: 0 };
39
+ const seen = new Set<string>();
40
+ const invalid = () =>
41
+ new Error(
42
+ "usage: ogtool list [--full | --json] [--query <substring>] [--limit <1..100>] [--offset <nonnegative integer>]",
43
+ );
44
+ for (let index = 0; index < args.length; index++) {
45
+ const argument = args[index]!;
46
+ const equals = argument.indexOf("=");
47
+ const flag = equals < 0 ? argument : argument.slice(0, equals);
48
+ if (seen.has(flag)) throw invalid();
49
+ seen.add(flag);
50
+ if (flag === "--full" || flag === "--json") {
51
+ if (equals >= 0) throw invalid();
52
+ options[flag === "--full" ? "full" : "json"] = true;
53
+ } else if (flag === "--query" || flag === "--limit" || flag === "--offset") {
54
+ const value = equals < 0 ? args[++index] : argument.slice(equals + 1);
55
+ if (value === undefined || (equals < 0 && value.startsWith("-"))) throw invalid();
56
+ if (flag === "--query") options.query = value;
57
+ else {
58
+ if (!/^[0-9]+$/u.test(value) || !Number.isSafeInteger(Number(value))) throw invalid();
59
+ const number = Number(value);
60
+ if (flag === "--limit" && (number < 1 || number > 100)) throw invalid();
61
+ options[flag === "--limit" ? "limit" : "offset"] = number;
62
+ }
63
+ } else throw invalid();
64
+ }
65
+ if (options.full && seen.size !== 1) throw invalid();
66
+ return options;
67
+ }
68
+
69
+ /** Render first, then emit: the bound includes metadata, escaping, hints, and newline. */
70
+ export function compactOutput(catalog: AttemptToolCatalog, options: ListOptions): string {
71
+ const matches = catalog.entries.filter(
72
+ (entry) =>
73
+ entry.codemodePath.join(".").includes(options.query) ||
74
+ normalizedDescription(entry).includes(options.query),
75
+ );
76
+ const tools = matches.slice(options.offset, options.offset + options.limit).map((entry) => ({
77
+ path: entry.codemodePath.join("."),
78
+ description: shortDescription(entry),
79
+ }));
80
+ const textLines = options.json
81
+ ? []
82
+ : tools.map(
83
+ (tool) =>
84
+ `${tool.path}${tool.description ? ` — ${terminalDescription(tool.description)}` : ""}\n`,
85
+ );
86
+ for (;;) {
87
+ const nextOffset =
88
+ options.offset + tools.length < matches.length ? options.offset + tools.length : null;
89
+ const page = {
90
+ catalogDigest: catalog.digest,
91
+ total: matches.length,
92
+ offset: options.offset,
93
+ nextOffset,
94
+ tools,
95
+ };
96
+ const output = options.json
97
+ ? `${JSON.stringify(page)}\n`
98
+ : textLines.slice(0, tools.length).join("") +
99
+ `# total: ${page.total}; offset: ${page.offset}; nextOffset: ${nextOffset ?? "none"}\n` +
100
+ (nextOffset === null
101
+ ? ""
102
+ : `# Continue with --offset ${nextOffset} (keep the same --query and --limit).\n`);
103
+ if (Buffer.byteLength(output, "utf8") <= LIST_MAX_BYTES) return output;
104
+ if (tools.length <= 1) {
105
+ throw new Error(
106
+ "One tool exceeds the 16384-byte compact page limit; use list --full redirected to a file",
107
+ );
108
+ }
109
+ tools.pop();
110
+ }
111
+ }
package/src/cli.ts CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  type AttemptToolCatalogEntry,
8
8
  } from "@opengeni/codemode";
9
9
  import packageManifest from "../package.json" with { type: "json" };
10
+ import { compactOutput, parseListOptions } from "./catalog-discovery";
10
11
 
11
12
  const PACKAGE_NAME = "@opengeni/ogtool";
12
13
  const VERSION = packageManifest.version;
@@ -14,7 +15,9 @@ const VERSION = packageManifest.version;
14
15
  function usage(exitCode = 1): void {
15
16
  const output = [
16
17
  "usage:",
17
- " ogtool list",
18
+ " ogtool list [--full | --json]",
19
+ " [--query <substring>] [--limit <1..100>] [--offset <nonnegative integer>]",
20
+ " ogtool show <tool-path-or-model-name>",
18
21
  " ogtool call <tool-path-or-model-name> [json-object]",
19
22
  " ogtool declarations [output-file]",
20
23
  " ogtool doctor",
@@ -148,6 +151,20 @@ export function listProjection(catalog: AttemptToolCatalog): Record<string, unkn
148
151
  };
149
152
  }
150
153
 
154
+ const SHOW_MAX_BYTES = 64 * 1024;
155
+
156
+ export function showOutput(catalog: AttemptToolCatalog, name: string): string {
157
+ const entry = resolveTool(catalog, name);
158
+ const projection = listProjection({ ...catalog, entries: [entry] });
159
+ const output = JSON.stringify((projection.tools as unknown[])[0], null, 2);
160
+ if (Buffer.byteLength(output, "utf8") + 1 > SHOW_MAX_BYTES) {
161
+ throw new Error(
162
+ "Tool details exceed 65536 bytes; use list --full or declarations <output-file>",
163
+ );
164
+ }
165
+ return `${output}\n`;
166
+ }
167
+
151
168
  export function parseToolArguments(raw: string | undefined): Record<string, unknown> {
152
169
  if (raw === undefined) return {};
153
170
  let value: unknown;
@@ -172,9 +189,30 @@ async function main(): Promise<void> {
172
189
  if (command === "doctor") return doctor();
173
190
  if (!command) return usage();
174
191
 
192
+ const args = process.argv.slice(3);
193
+ if (
194
+ (command === "list" || command === "show") &&
195
+ args.length === 1 &&
196
+ (args[0] === "--help" || args[0] === "-h")
197
+ )
198
+ return usage(0);
199
+ const listOptions = command === "list" ? parseListOptions(args) : null;
200
+ if (command === "show" && (args.length !== 1 || !args[0] || args[0].startsWith("-"))) {
201
+ throw new Error("usage: ogtool show <tool-path-or-model-name>");
202
+ }
203
+
175
204
  const client = configuredClient();
176
205
  if (command === "list") {
177
- process.stdout.write(`${JSON.stringify(listProjection(await client.catalog()), null, 2)}\n`);
206
+ const catalog = await client.catalog();
207
+ if (listOptions!.full) {
208
+ process.stdout.write(`${JSON.stringify(listProjection(catalog), null, 2)}\n`);
209
+ } else {
210
+ process.stdout.write(compactOutput(catalog, listOptions!));
211
+ }
212
+ return;
213
+ }
214
+ if (command === "show") {
215
+ process.stdout.write(showOutput(await client.catalog(), args[0]!));
178
216
  return;
179
217
  }
180
218
  if (command === "call") {