@typeship-ax/cli 0.22.0 → 0.24.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/AGENTS.md +13 -9
- package/README.md +14 -27
- package/api.json +8898 -9026
- package/api.md +370 -328
- package/dist/arguments.d.ts +54 -0
- package/dist/arguments.d.ts.map +1 -0
- package/dist/arguments.js +265 -0
- package/dist/cli-agent.d.ts +73 -9
- package/dist/cli-agent.d.ts.map +1 -1
- package/dist/cli-agent.js +331 -44
- package/dist/cli.js +802 -291
- package/dist/core/http.d.ts +162 -19
- package/dist/core/http.d.ts.map +1 -1
- package/dist/core/http.js +381 -48
- package/dist/core/pagination.d.ts +42 -6
- package/dist/core/pagination.d.ts.map +1 -1
- package/dist/core/pagination.js +111 -17
- package/dist/credential-storage.d.ts +10 -3
- package/dist/credential-storage.d.ts.map +1 -1
- package/dist/credential-storage.js +15 -6
- package/dist/dates.d.ts +1 -1
- package/dist/dates.js +1 -1
- package/dist/errors.d.ts +20 -84
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +20 -108
- package/dist/fields.d.ts +36 -0
- package/dist/fields.d.ts.map +1 -0
- package/dist/fields.js +187 -0
- package/dist/index.d.ts +28 -18
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -25
- package/dist/named-credentials.d.ts +19 -0
- package/dist/named-credentials.d.ts.map +1 -1
- package/dist/named-credentials.js +81 -1
- package/dist/oauth-login.d.ts +8 -2
- package/dist/oauth-login.d.ts.map +1 -1
- package/dist/oauth-login.js +31 -19
- package/dist/oauth-request.d.ts +7 -1
- package/dist/oauth-request.d.ts.map +1 -1
- package/dist/oauth-request.js +26 -4
- package/dist/oauth-session.d.ts +13 -1
- package/dist/oauth-session.d.ts.map +1 -1
- package/dist/oauth-session.js +34 -18
- package/dist/ops.d.ts +58 -5
- package/dist/ops.d.ts.map +1 -1
- package/dist/ops.js +110 -41
- package/dist/polling-login.d.ts +8 -2
- package/dist/polling-login.d.ts.map +1 -1
- package/dist/polling-login.js +25 -11
- package/dist/resources/api-keys.d.ts +10 -7
- package/dist/resources/api-keys.d.ts.map +1 -1
- package/dist/resources/api-keys.js +10 -31
- package/dist/resources/deliveries.d.ts +89 -5
- package/dist/resources/deliveries.d.ts.map +1 -1
- package/dist/resources/deliveries.js +96 -19
- package/dist/resources/drafts.d.ts +16 -16
- package/dist/resources/drafts.d.ts.map +1 -1
- package/dist/resources/drafts.js +12 -65
- package/dist/resources/files.d.ts +4 -4
- package/dist/resources/files.d.ts.map +1 -1
- package/dist/resources/files.js +3 -12
- package/dist/resources/generations.d.ts +16 -16
- package/dist/resources/generations.d.ts.map +1 -1
- package/dist/resources/generations.js +23 -47
- package/dist/resources/organization.d.ts +4 -4
- package/dist/resources/organization.d.ts.map +1 -1
- package/dist/resources/organization.js +3 -10
- package/dist/resources/{generate.d.ts → packages.d.ts} +16 -16
- package/dist/resources/packages.d.ts.map +1 -0
- package/dist/resources/{generate.js → packages.js} +13 -29
- package/dist/resources/projects.d.ts +50 -50
- package/dist/resources/projects.d.ts.map +1 -1
- package/dist/resources/projects.js +60 -116
- package/dist/resources/releases.d.ts +22 -17
- package/dist/resources/releases.d.ts.map +1 -1
- package/dist/resources/releases.js +19 -40
- package/dist/resources/spec-revisions.d.ts +16 -7
- package/dist/resources/spec-revisions.d.ts.map +1 -1
- package/dist/resources/spec-revisions.js +7 -29
- package/dist/resources/specs.d.ts +7 -7
- package/dist/resources/specs.d.ts.map +1 -1
- package/dist/resources/specs.js +6 -34
- package/dist/resources/targets.d.ts +49 -49
- package/dist/resources/targets.d.ts.map +1 -1
- package/dist/resources/targets.js +59 -115
- package/dist/schemas.d.ts.map +1 -1
- package/dist/schemas.js +83 -81
- package/dist/search.d.ts +54 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +421 -0
- package/dist/table.d.ts +28 -0
- package/dist/table.d.ts.map +1 -0
- package/dist/table.js +167 -0
- package/dist/type-docs.d.ts +61 -0
- package/dist/type-docs.d.ts.map +1 -0
- package/dist/type-docs.js +174 -0
- package/dist/types.d.ts +499 -339
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +18 -18
- package/package.json +5 -2
- package/src/arguments.ts +254 -0
- package/src/cli-agent.ts +351 -46
- package/src/cli.ts +753 -266
- package/src/core/http.ts +457 -58
- package/src/core/pagination.ts +129 -18
- package/src/credential-storage.ts +16 -6
- package/src/dates.ts +1 -1
- package/src/errors.ts +46 -115
- package/src/fields.ts +167 -0
- package/src/index.ts +45 -28
- package/src/named-credentials.ts +66 -1
- package/src/oauth-login.ts +36 -21
- package/src/oauth-request.ts +32 -6
- package/src/oauth-session.ts +37 -19
- package/src/ops.ts +146 -45
- package/src/polling-login.ts +24 -11
- package/src/resources/api-keys.ts +34 -48
- package/src/resources/deliveries.ts +213 -32
- package/src/resources/drafts.ts +62 -109
- package/src/resources/files.ts +19 -20
- package/src/resources/generations.ts +61 -79
- package/src/resources/organization.ts +11 -16
- package/src/resources/{generate.ts → packages.ts} +43 -51
- package/src/resources/projects.ts +145 -200
- package/src/resources/releases.ts +50 -67
- package/src/resources/spec-revisions.ts +40 -49
- package/src/resources/specs.ts +39 -59
- package/src/resources/targets.ts +144 -194
- package/src/schemas.ts +83 -81
- package/src/search.ts +434 -0
- package/src/table.ts +167 -0
- package/src/type-docs.ts +205 -0
- package/src/types.ts +538 -357
- package/dist/console-login-check.d.ts +0 -21
- package/dist/console-login-check.d.ts.map +0 -1
- package/dist/console-login-check.js +0 -107
- package/dist/console-login-contract.d.ts +0 -45
- package/dist/console-login-contract.d.ts.map +0 -1
- package/dist/console-login-contract.js +0 -40
- package/dist/resources/generate.d.ts.map +0 -1
- package/dist/resources/publications.d.ts +0 -47
- package/dist/resources/publications.d.ts.map +0 -1
- package/dist/resources/publications.js +0 -70
- package/src/console-login-check.ts +0 -88
- package/src/console-login-contract.ts +0 -65
- package/src/resources/publications.ts +0 -140
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation search shared by the generated CLI (`docs search`) and MCP
|
|
3
|
+
* server (`search_docs`). Generated by Typeship — https://typeship.dev
|
|
4
|
+
*
|
|
5
|
+
* Agents describe a task ("assign issue", "list calls"), not an operation
|
|
6
|
+
* name, so ranking works on stems, weighs rare words over common ones
|
|
7
|
+
* (IDF), and reads the verb of the query against what each operation does:
|
|
8
|
+
* its HTTP method and whether its path names one item or a collection, or
|
|
9
|
+
* its GraphQL root field. GraphQL mutations are also indexed by their input
|
|
10
|
+
* fields, so "assign issue" finds issueUpdate through assigneeId.
|
|
11
|
+
* Deterministic and dependency-free.
|
|
12
|
+
*/
|
|
13
|
+
/** The parts of an operation search reads. The CLI and MCP operation
|
|
14
|
+
* specs both satisfy it. */
|
|
15
|
+
export interface SearchableOperation {
|
|
16
|
+
tool: string;
|
|
17
|
+
resource?: string;
|
|
18
|
+
method?: string;
|
|
19
|
+
httpMethod: string;
|
|
20
|
+
path: string;
|
|
21
|
+
summary?: string;
|
|
22
|
+
description?: string;
|
|
23
|
+
paginated?: boolean;
|
|
24
|
+
deprecated?: boolean;
|
|
25
|
+
graphql?: {
|
|
26
|
+
kind: string;
|
|
27
|
+
field?: string;
|
|
28
|
+
};
|
|
29
|
+
params: readonly {
|
|
30
|
+
name: string;
|
|
31
|
+
enum?: readonly string[];
|
|
32
|
+
description?: string;
|
|
33
|
+
}[];
|
|
34
|
+
inputSchema?: Record<string, unknown>;
|
|
35
|
+
outputSchema?: Record<string, unknown>;
|
|
36
|
+
}
|
|
37
|
+
/** Reference matches per search page. */
|
|
38
|
+
export declare const SEARCH_PAGE_SIZE = 10;
|
|
39
|
+
/** A light English stemmer: plurals, -ing/-ed, a few derivational
|
|
40
|
+
* suffixes, a trailing e. It only has to map a word and its common
|
|
41
|
+
* variants to one stem ("assignee", "assigned", "assigns" to "assign";
|
|
42
|
+
* "moderate", "moderations" to "moder"). */
|
|
43
|
+
export declare function stem(word: string): string;
|
|
44
|
+
export interface RankedOperation<T> {
|
|
45
|
+
op: T;
|
|
46
|
+
score: number;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Operations for a free-text query, best first. Weak matches (under a
|
|
50
|
+
* fifth of the best score, or matching only the query's verb) are left
|
|
51
|
+
* out, and deprecated operations follow every current one.
|
|
52
|
+
*/
|
|
53
|
+
export declare function rankOperations<T extends SearchableOperation>(ops: readonly T[], query: string): RankedOperation<T>[];
|
|
54
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH;4BAC4B;AAC5B,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,OAAO,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IAC3C,MAAM,EAAE,SAAS;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,EAAE,CAAC;IACpF,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACtC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACxC;AAED,yCAAyC;AACzC,eAAO,MAAM,gBAAgB,KAAK,CAAC;AAqDnC;;;4CAG4C;AAC5C,wBAAgB,IAAI,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CA2BzC;AAoPD,MAAM,WAAW,eAAe,CAAC,CAAC;IAChC,EAAE,EAAE,CAAC,CAAC;IACN,KAAK,EAAE,MAAM,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,mBAAmB,EAAE,GAAG,EAAE,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,CAAC,CAAC,CAAC,EAAE,CA+DpH"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Operation search shared by the generated CLI (`docs search`) and MCP
|
|
3
|
+
* server (`search_docs`). Generated by Typeship — https://typeship.dev
|
|
4
|
+
*
|
|
5
|
+
* Agents describe a task ("assign issue", "list calls"), not an operation
|
|
6
|
+
* name, so ranking works on stems, weighs rare words over common ones
|
|
7
|
+
* (IDF), and reads the verb of the query against what each operation does:
|
|
8
|
+
* its HTTP method and whether its path names one item or a collection, or
|
|
9
|
+
* its GraphQL root field. GraphQL mutations are also indexed by their input
|
|
10
|
+
* fields, so "assign issue" finds issueUpdate through assigneeId.
|
|
11
|
+
* Deterministic and dependency-free.
|
|
12
|
+
*/
|
|
13
|
+
/** Reference matches per search page. */
|
|
14
|
+
export const SEARCH_PAGE_SIZE = 10;
|
|
15
|
+
/** Query verbs and the operation kinds they ask for, best first. */
|
|
16
|
+
const VERBS = {
|
|
17
|
+
list: ["list"], all: ["list"], browse: ["list"], enumerate: ["list"],
|
|
18
|
+
search: ["search", "list"], find: ["search", "list", "get"], query: ["search", "list"], lookup: ["get", "search"],
|
|
19
|
+
get: ["get", "list"], release: ["delete"], fetch: ["get", "list"], retrieve: ["get", "list"], read: ["get", "list"], show: ["get", "list"],
|
|
20
|
+
view: ["get", "list"], check: ["get"], describe: ["get"], inspect: ["get"], status: ["get"],
|
|
21
|
+
create: ["create"], add: ["create", "update"], new: ["create"], make: ["create"], send: ["create"], post: ["create"],
|
|
22
|
+
submit: ["create"], open: ["create"], start: ["create", "update"], upload: ["create", "update"], generate: ["create"],
|
|
23
|
+
buy: ["create"], purchase: ["create"], place: ["create"], invite: ["create"], write: ["create", "update"],
|
|
24
|
+
publish: ["create", "update"], register: ["create"],
|
|
25
|
+
update: ["update"], change: ["update"], edit: ["update"], modify: ["update"], set: ["update", "create"],
|
|
26
|
+
assign: ["update", "create"], rename: ["update"], move: ["update"], close: ["update", "delete"], reopen: ["update"],
|
|
27
|
+
mark: ["update"], archive: ["delete", "update"], unarchive: ["update"], enable: ["update"], disable: ["update"],
|
|
28
|
+
pause: ["update"], resume: ["update"], replace: ["update"], patch: ["update"], merge: ["update", "create"],
|
|
29
|
+
save: ["create", "update"], follow: ["create", "update"], unfollow: ["delete", "update"], comment: ["create"], reply: ["create"], request: ["create"], hang: ["update", "delete"],
|
|
30
|
+
delete: ["delete"], remove: ["delete", "update"], destroy: ["delete"], erase: ["delete"], drop: ["delete"],
|
|
31
|
+
revoke: ["delete"], cancel: ["delete", "update"], unassign: ["update", "delete"], clear: ["delete"],
|
|
32
|
+
};
|
|
33
|
+
/** Words that name the caller: "who am I", "my issues", "me". */
|
|
34
|
+
const IDENTITY_WORDS = new Set(["who", "whoami", "me", "my", "mine", "myself", "self"]);
|
|
35
|
+
const IDENTITY_TERMS = ["me", "self", "whoami", "authenticated"];
|
|
36
|
+
/** An operation about the caller: GET /user, /me/…, a viewer or me root
|
|
37
|
+
* field, users_get_authenticated. */
|
|
38
|
+
function isIdentityOperation(op) {
|
|
39
|
+
if (op.graphql?.field)
|
|
40
|
+
return /^(viewer|me|currentUser|whoami)$/.test(op.graphql.field);
|
|
41
|
+
const nameWords = words([op.tool, op.method ?? ""].join(" "));
|
|
42
|
+
if (nameWords.some((w) => w === "me" || w === "whoami" || w === "authenticated" || w === "self"))
|
|
43
|
+
return true;
|
|
44
|
+
return /^(\/v\d+)?\/(me|user|self|whoami)(\/|$)/i.test(op.path) || /\/(users\/)?me(\/|$)/i.test(op.path);
|
|
45
|
+
}
|
|
46
|
+
/** Words that narrow a request rather than name the operation. */
|
|
47
|
+
const QUALIFIERS = new Set(["yesterday", "today", "tomorrow", "recent", "recently", "latest", "last", "week", "month", "year", "daily"]);
|
|
48
|
+
/** A few everyday words for what APIs call something else. */
|
|
49
|
+
const SYNONYMS = {
|
|
50
|
+
now: ["current"], sms: ["message"], mms: ["message"], text: ["message"], document: ["file"], kyc: ["identity", "verification"],
|
|
51
|
+
};
|
|
52
|
+
const STOPWORDS = new Set([
|
|
53
|
+
"a", "an", "the", "to", "of", "for", "in", "on", "at", "from", "by", "with", "and", "or", "is", "are", "am", "be",
|
|
54
|
+
"do", "does", "it", "its", "this", "that", "these", "those", "into", "onto", "up", "how", "what", "which", "can",
|
|
55
|
+
"please", "via", "using", "some", "any", "there", "i", "you", "your", "we", "our",
|
|
56
|
+
]);
|
|
57
|
+
/** Field weights: a word in the operation's name says most about it. */
|
|
58
|
+
const W_NAME = 10, W_SUMMARY = 6, W_PATH = 4, W_PARAM = 3, W_ENUM = 2, W_DESCRIPTION = 1.5, W_OUTPUT = 1;
|
|
59
|
+
/** A light English stemmer: plurals, -ing/-ed, a few derivational
|
|
60
|
+
* suffixes, a trailing e. It only has to map a word and its common
|
|
61
|
+
* variants to one stem ("assignee", "assigned", "assigns" to "assign";
|
|
62
|
+
* "moderate", "moderations" to "moder"). */
|
|
63
|
+
export function stem(word) {
|
|
64
|
+
let w = word.toLowerCase();
|
|
65
|
+
if (w.length <= 3)
|
|
66
|
+
return w;
|
|
67
|
+
if (w.endsWith("ies") && w.length > 4)
|
|
68
|
+
w = w.slice(0, -3) + "y";
|
|
69
|
+
else if (/(sses|xes|ches|shes|zes)$/.test(w))
|
|
70
|
+
w = w.slice(0, -2);
|
|
71
|
+
else if (w.endsWith("s") && !/(ss|us|is)$/.test(w))
|
|
72
|
+
w = w.slice(0, -1);
|
|
73
|
+
let stripped = false;
|
|
74
|
+
if (w.endsWith("ly") && w.length >= 6)
|
|
75
|
+
w = w.slice(0, -2);
|
|
76
|
+
for (const [suffix, min] of [["ing", 4], ["ed", 3]]) {
|
|
77
|
+
if (w.endsWith(suffix) && w.length - suffix.length >= min) {
|
|
78
|
+
w = w.slice(0, -suffix.length);
|
|
79
|
+
stripped = true;
|
|
80
|
+
break;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
if (stripped) {
|
|
84
|
+
if (/(at|bl|iz)$/.test(w))
|
|
85
|
+
w += "e";
|
|
86
|
+
else if (/([^aeioulsz])\1$/.test(w))
|
|
87
|
+
w = w.slice(0, -1);
|
|
88
|
+
}
|
|
89
|
+
for (const [suffix, min] of [["ations", 4], ["ation", 4], ["ments", 4], ["ment", 4], ["ption", 4], ["ions", 4], ["ion", 4], ["ers", 4], ["er", 4], ["ee", 4], ["ate", 4]]) {
|
|
90
|
+
if (w.endsWith(suffix) && w.length - suffix.length >= min) {
|
|
91
|
+
w = w.slice(0, -suffix.length);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (w.endsWith("e") && w.length >= 5)
|
|
96
|
+
w = w.slice(0, -1);
|
|
97
|
+
return w;
|
|
98
|
+
}
|
|
99
|
+
/** Lowercase words of two or more characters, with snake, kebab, camel and
|
|
100
|
+
* letter-digit seams split so "createAccount" and "accounts_create" share
|
|
101
|
+
* words. */
|
|
102
|
+
function words(text) {
|
|
103
|
+
return text.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([a-zA-Z])([0-9])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").toLowerCase().split(/[^a-z0-9]+/).filter((w) => w.length >= 2);
|
|
104
|
+
}
|
|
105
|
+
function stems(text) {
|
|
106
|
+
return words(text).filter((w) => !STOPWORDS.has(w)).map(stem);
|
|
107
|
+
}
|
|
108
|
+
// A server keeps one operation list, and a hosted one filters the same
|
|
109
|
+
// operation objects per request: cache by both.
|
|
110
|
+
const INDEXES = new WeakMap();
|
|
111
|
+
const INDEXED = new WeakMap();
|
|
112
|
+
const READ_ACTIONS = ["list", "get", "search"];
|
|
113
|
+
const WRITE_ACTIONS = ["create", "update", "delete"];
|
|
114
|
+
/** The first of `words` (in order) that names an allowed action. */
|
|
115
|
+
function actionIn(candidates, allowed) {
|
|
116
|
+
for (const word of candidates) {
|
|
117
|
+
const action = VERBS[word]?.[0];
|
|
118
|
+
if (action && allowed.includes(action))
|
|
119
|
+
return { action, word };
|
|
120
|
+
}
|
|
121
|
+
return undefined;
|
|
122
|
+
}
|
|
123
|
+
/** What an operation does, and the word of its name that says so: its
|
|
124
|
+
* name's verb when it has one, else its HTTP method and whether the path
|
|
125
|
+
* ends at one item or a collection. A read never creates, updates or
|
|
126
|
+
* deletes, whatever its name ("query comment" is a read of a comment). */
|
|
127
|
+
function operationAction(op) {
|
|
128
|
+
const collection = () => {
|
|
129
|
+
const segments = op.path.replace(/\.[a-z]+$/i, "").split("/").filter(Boolean);
|
|
130
|
+
const last = segments.at(-1) ?? "";
|
|
131
|
+
if (last.startsWith("{") || last.startsWith(":"))
|
|
132
|
+
return "get";
|
|
133
|
+
return op.paginated ? "list" : "get";
|
|
134
|
+
};
|
|
135
|
+
if (op.graphql?.field) {
|
|
136
|
+
// issueUpdate, createIssue: the verb may sit at either end.
|
|
137
|
+
const fieldWords = words(op.graphql.field);
|
|
138
|
+
const read = op.graphql.kind !== "mutation";
|
|
139
|
+
const found = actionIn([...fieldWords].reverse(), read ? READ_ACTIONS : WRITE_ACTIONS);
|
|
140
|
+
if (found)
|
|
141
|
+
return found;
|
|
142
|
+
return { action: read ? (op.paginated || /s$/.test(op.graphql.field) ? "list" : "get") : "update", word: null };
|
|
143
|
+
}
|
|
144
|
+
const verb = op.httpMethod.toUpperCase();
|
|
145
|
+
const read = verb === "GET" || verb === "HEAD";
|
|
146
|
+
const found = actionIn(words(op.method ?? ""), read ? READ_ACTIONS : [...WRITE_ACTIONS, "search"]);
|
|
147
|
+
if (found)
|
|
148
|
+
return found;
|
|
149
|
+
if (verb === "DELETE")
|
|
150
|
+
return { action: "delete", word: null };
|
|
151
|
+
if (verb === "PUT" || verb === "PATCH")
|
|
152
|
+
return { action: "update", word: null };
|
|
153
|
+
if (verb === "POST")
|
|
154
|
+
return { action: "create", word: null };
|
|
155
|
+
return { action: collection(), word: null };
|
|
156
|
+
}
|
|
157
|
+
/** Literal path words: no parameters, versions or date segments. */
|
|
158
|
+
function pathWords(path) {
|
|
159
|
+
return path.replace(/\.[a-z]+$/i, "").split("/").filter((s) => s && !/^[{:]/.test(s) && !/^v\d+$/i.test(s) && !/^\d/.test(s)).join(" ");
|
|
160
|
+
}
|
|
161
|
+
/** Property names and enum values of a schema, one level into objects,
|
|
162
|
+
* and one more for object-typed properties (a GraphQL input object). */
|
|
163
|
+
function schemaWords(schema, depth, names, enums) {
|
|
164
|
+
if (!schema || typeof schema !== "object" || depth < 0)
|
|
165
|
+
return;
|
|
166
|
+
const node = schema;
|
|
167
|
+
if (Array.isArray(node.enum))
|
|
168
|
+
for (const value of node.enum)
|
|
169
|
+
if (typeof value === "string")
|
|
170
|
+
enums.push(value);
|
|
171
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
172
|
+
if (Array.isArray(node[key]))
|
|
173
|
+
for (const part of node[key])
|
|
174
|
+
schemaWords(part, depth, names, enums);
|
|
175
|
+
}
|
|
176
|
+
if (node.items)
|
|
177
|
+
schemaWords(node.items, depth, names, enums);
|
|
178
|
+
const properties = node.properties;
|
|
179
|
+
if (properties && typeof properties === "object") {
|
|
180
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
181
|
+
names.push(name);
|
|
182
|
+
schemaWords(child, depth - 1, names, enums);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function outputWords(schema, depth, out) {
|
|
187
|
+
if (!schema || typeof schema !== "object" || depth < 0)
|
|
188
|
+
return;
|
|
189
|
+
const node = schema;
|
|
190
|
+
if (typeof node.description === "string")
|
|
191
|
+
out.push(node.description.slice(0, 200));
|
|
192
|
+
if (node.items)
|
|
193
|
+
outputWords(node.items, depth, out);
|
|
194
|
+
for (const key of ["anyOf", "oneOf", "allOf"]) {
|
|
195
|
+
if (Array.isArray(node[key]))
|
|
196
|
+
for (const part of node[key])
|
|
197
|
+
outputWords(part, depth, out);
|
|
198
|
+
}
|
|
199
|
+
const properties = node.properties;
|
|
200
|
+
if (properties && typeof properties === "object") {
|
|
201
|
+
for (const [name, child] of Object.entries(properties)) {
|
|
202
|
+
out.push(name);
|
|
203
|
+
outputWords(child, depth - 1, out);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
function firstSentence(text) {
|
|
208
|
+
return /^(.{1,200}?[.!?])(\s|$)/s.exec(text.trim())?.[1] ?? "";
|
|
209
|
+
}
|
|
210
|
+
function indexOperation(op) {
|
|
211
|
+
const weights = new Map();
|
|
212
|
+
const add = (text, weight) => {
|
|
213
|
+
for (const s of new Set(stems(text))) {
|
|
214
|
+
const entry = weights.get(s);
|
|
215
|
+
if (!entry)
|
|
216
|
+
weights.set(s, { best: weight, rest: 0 });
|
|
217
|
+
else if (weight > entry.best) {
|
|
218
|
+
entry.rest += entry.best;
|
|
219
|
+
entry.best = weight;
|
|
220
|
+
}
|
|
221
|
+
else
|
|
222
|
+
entry.rest += weight;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
// GraphQL tools are named query_<field> / mutation_<field>; the prefix is
|
|
226
|
+
// the kind, not a word a task would use.
|
|
227
|
+
const name = op.graphql?.field
|
|
228
|
+
? [op.graphql.field]
|
|
229
|
+
: [op.tool, op.resource ?? "", op.method ?? ""];
|
|
230
|
+
add(name.join(" "), W_NAME);
|
|
231
|
+
// Without a summary, the description's first sentence plays its part.
|
|
232
|
+
const summary = op.summary?.trim() || firstSentence(op.description ?? "");
|
|
233
|
+
add(summary, W_SUMMARY);
|
|
234
|
+
add(pathWords(op.path), W_PATH);
|
|
235
|
+
const paramNames = [];
|
|
236
|
+
const enumValues = [];
|
|
237
|
+
const paramText = [];
|
|
238
|
+
for (const param of op.params) {
|
|
239
|
+
paramNames.push(param.name);
|
|
240
|
+
if (param.enum)
|
|
241
|
+
enumValues.push(...param.enum);
|
|
242
|
+
if (param.description)
|
|
243
|
+
paramText.push(param.description.split("\n")[0].slice(0, 200));
|
|
244
|
+
}
|
|
245
|
+
const properties = (op.inputSchema?.properties ?? {});
|
|
246
|
+
for (const child of Object.values(properties))
|
|
247
|
+
schemaWords(child, 1, paramNames, enumValues);
|
|
248
|
+
add(paramNames.join(" "), W_PARAM);
|
|
249
|
+
add(enumValues.join(" "), W_ENUM);
|
|
250
|
+
add(op.description ?? "", W_DESCRIPTION);
|
|
251
|
+
add(paramText.join(" "), W_OUTPUT);
|
|
252
|
+
const output = [];
|
|
253
|
+
outputWords(op.outputSchema, 2, output);
|
|
254
|
+
add(output.join(" "), W_OUTPUT);
|
|
255
|
+
const { action, word } = operationAction(op);
|
|
256
|
+
const actionStem = word === null ? null : stem(word);
|
|
257
|
+
const nameStems = [...new Set(stems(name.join(" ")))].filter((s) => s !== actionStem);
|
|
258
|
+
return { weights, nameStems, action, actionStem, identity: isIdentityOperation(op), summary: stems(summary).join(" "), deprecated: op.deprecated === true || /^deprecated\b/i.test(op.summary ?? "") };
|
|
259
|
+
}
|
|
260
|
+
function indexFor(ops) {
|
|
261
|
+
const cached = INDEXES.get(ops);
|
|
262
|
+
if (cached)
|
|
263
|
+
return cached;
|
|
264
|
+
const docs = ops.map((op) => {
|
|
265
|
+
let doc = INDEXED.get(op);
|
|
266
|
+
if (!doc)
|
|
267
|
+
INDEXED.set(op, doc = indexOperation(op));
|
|
268
|
+
return doc;
|
|
269
|
+
});
|
|
270
|
+
const df = new Map();
|
|
271
|
+
for (const doc of docs)
|
|
272
|
+
for (const s of doc.weights.keys())
|
|
273
|
+
df.set(s, (df.get(s) ?? 0) + 1);
|
|
274
|
+
const index = { docs, df, vocabulary: [...df.keys()] };
|
|
275
|
+
INDEXES.set(ops, index);
|
|
276
|
+
return index;
|
|
277
|
+
}
|
|
278
|
+
/** A query stem's matches in the vocabulary: itself, and longer or
|
|
279
|
+
* shorter forms sharing a long prefix ("transcrib" and "transcript"),
|
|
280
|
+
* which count for less. */
|
|
281
|
+
function variants(term, index) {
|
|
282
|
+
const out = new Map();
|
|
283
|
+
if (index.df.has(term))
|
|
284
|
+
out.set(term, 1);
|
|
285
|
+
if (term.length < 5)
|
|
286
|
+
return out;
|
|
287
|
+
for (const word of index.vocabulary) {
|
|
288
|
+
if (word === term || word.length < 5)
|
|
289
|
+
continue;
|
|
290
|
+
let common = 0;
|
|
291
|
+
while (common < word.length && common < term.length && word[common] === term[common])
|
|
292
|
+
common++;
|
|
293
|
+
if (common >= 6 && Math.max(word.length, term.length) - common <= 3)
|
|
294
|
+
out.set(word, 0.6);
|
|
295
|
+
}
|
|
296
|
+
return out;
|
|
297
|
+
}
|
|
298
|
+
function parseQuery(query, index) {
|
|
299
|
+
const raw = words(query);
|
|
300
|
+
let verb = null;
|
|
301
|
+
let identity = false;
|
|
302
|
+
const terms = [];
|
|
303
|
+
const seen = new Set();
|
|
304
|
+
for (const word of raw) {
|
|
305
|
+
if (IDENTITY_WORDS.has(word)) {
|
|
306
|
+
identity = true;
|
|
307
|
+
continue;
|
|
308
|
+
}
|
|
309
|
+
if (STOPWORDS.has(word))
|
|
310
|
+
continue;
|
|
311
|
+
const s = stem(word);
|
|
312
|
+
if (seen.has(s))
|
|
313
|
+
continue;
|
|
314
|
+
seen.add(s);
|
|
315
|
+
const actions = VERBS[word] ?? VERBS[s] ?? null;
|
|
316
|
+
const isVerb = actions !== null && actions.length > 0 && verb === null;
|
|
317
|
+
if (isVerb)
|
|
318
|
+
verb = actions;
|
|
319
|
+
const matches = variants(s, index);
|
|
320
|
+
for (const synonym of SYNONYMS[word] ?? [])
|
|
321
|
+
for (const [v, strength] of variants(stem(synonym), index))
|
|
322
|
+
matches.set(v, Math.max(matches.get(v) ?? 0, strength * 0.8));
|
|
323
|
+
// The verb is scored against what the operation does, so its word
|
|
324
|
+
// counts for little; the first noun is usually the object of the task.
|
|
325
|
+
const qualifier = QUALIFIERS.has(word);
|
|
326
|
+
const primary = !isVerb && !qualifier && !terms.some((t) => t.primary);
|
|
327
|
+
const weight = isVerb || qualifier ? 0.3 : primary ? 1.3 : 1;
|
|
328
|
+
terms.push({ matches, weight, verb: isVerb ? actions : null, primary, stem: s });
|
|
329
|
+
}
|
|
330
|
+
if (identity) {
|
|
331
|
+
const matches = new Map();
|
|
332
|
+
for (const t of IDENTITY_TERMS)
|
|
333
|
+
for (const [s, strength] of variants(stem(t), index))
|
|
334
|
+
matches.set(s, Math.max(matches.get(s) ?? 0, strength));
|
|
335
|
+
terms.push({ matches, weight: 0.8, verb: null, primary: false, stem: "@me" });
|
|
336
|
+
}
|
|
337
|
+
return { terms, verb };
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Operations for a free-text query, best first. Weak matches (under a
|
|
341
|
+
* fifth of the best score, or matching only the query's verb) are left
|
|
342
|
+
* out, and deprecated operations follow every current one.
|
|
343
|
+
*/
|
|
344
|
+
export function rankOperations(ops, query) {
|
|
345
|
+
const index = indexFor(ops);
|
|
346
|
+
const { terms, verb } = parseQuery(query, index);
|
|
347
|
+
if (terms.length === 0)
|
|
348
|
+
return [];
|
|
349
|
+
const idf = (s) => Math.log(1 + index.docs.length / (index.df.get(s) ?? index.docs.length));
|
|
350
|
+
const nonVerbTerms = terms.filter((t) => t.verb === null).length;
|
|
351
|
+
const primary = terms.find((t) => t.primary);
|
|
352
|
+
const verbStem = terms.find((t) => t.verb !== null)?.stem ?? null;
|
|
353
|
+
const identityTerm = terms.find((t) => t.stem === "@me");
|
|
354
|
+
const identityIdf = Math.log(1 + index.docs.length / Math.max(1, index.docs.filter((d) => d.identity).length));
|
|
355
|
+
const scored = [];
|
|
356
|
+
const phrase = words(query).filter((w) => !STOPWORDS.has(w)).map(stem).join(" ");
|
|
357
|
+
index.docs.forEach((doc, position) => {
|
|
358
|
+
let score = 0;
|
|
359
|
+
let matchedNonVerb = 0;
|
|
360
|
+
let primaryInName = false;
|
|
361
|
+
let verbInName = false;
|
|
362
|
+
const covered = new Set();
|
|
363
|
+
for (const term of terms) {
|
|
364
|
+
let best = term === identityTerm && doc.identity ? W_NAME * identityIdf : 0;
|
|
365
|
+
for (const [s, strength] of term.matches) {
|
|
366
|
+
const entry = doc.weights.get(s);
|
|
367
|
+
if (!entry)
|
|
368
|
+
continue;
|
|
369
|
+
const value = (entry.best + 0.1 * Math.min(entry.rest, 10)) * strength * idf(s);
|
|
370
|
+
if (value > best)
|
|
371
|
+
best = value;
|
|
372
|
+
if (doc.nameStems.includes(s)) {
|
|
373
|
+
covered.add(s);
|
|
374
|
+
if (term === primary)
|
|
375
|
+
primaryInName = true;
|
|
376
|
+
// The query's verb as a noun of the name: upload_url for "upload".
|
|
377
|
+
if (term.verb !== null)
|
|
378
|
+
verbInName = true;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
if (best > 0 && term.verb === null)
|
|
382
|
+
matchedNonVerb++;
|
|
383
|
+
score += best * term.weight;
|
|
384
|
+
}
|
|
385
|
+
if (score === 0)
|
|
386
|
+
return;
|
|
387
|
+
if (nonVerbTerms > 0 && matchedNonVerb === 0)
|
|
388
|
+
return;
|
|
389
|
+
// How much of the operation's name the query accounts for: call_list
|
|
390
|
+
// for "list calls" over sip_auth_calls_credential_list_mapping_delete.
|
|
391
|
+
const coverage = doc.nameStems.length === 0 ? 1 : covered.size / doc.nameStems.length;
|
|
392
|
+
score *= 0.6 + 0.8 * coverage;
|
|
393
|
+
// The query's verb against what the operation does.
|
|
394
|
+
if (verb) {
|
|
395
|
+
const rank = verb.indexOf(doc.action);
|
|
396
|
+
score *= rank === 0 ? 1.5 : rank > 0 ? 1.2 : doc.action === "search" && verb.includes("list") ? 1.2 : verbInName ? 1 : 0.55;
|
|
397
|
+
// The very word: issue_add_label for "add", over a generic create.
|
|
398
|
+
if (verbStem !== null && doc.actionStem === verbStem)
|
|
399
|
+
score *= 1.2;
|
|
400
|
+
}
|
|
401
|
+
// Every query word matched: a stronger signal than any single word.
|
|
402
|
+
if (matchedNonVerb === nonVerbTerms && nonVerbTerms > 1)
|
|
403
|
+
score *= 1.2;
|
|
404
|
+
if (phrase.includes(" ") && doc.nameStems.join(" ").includes(phrase))
|
|
405
|
+
score *= 1.2;
|
|
406
|
+
if (phrase.includes(" ") && doc.summary.includes(phrase))
|
|
407
|
+
score *= 1.3;
|
|
408
|
+
// The operation is named for the task's object: issue_update, not
|
|
409
|
+
// workflow_state_update, for "change issue state".
|
|
410
|
+
if (primaryInName)
|
|
411
|
+
score *= 1.3;
|
|
412
|
+
scored.push({ op: ops[position], score, deprecated: doc.deprecated });
|
|
413
|
+
});
|
|
414
|
+
if (scored.length === 0)
|
|
415
|
+
return [];
|
|
416
|
+
const top = Math.max(...scored.map((s) => s.score));
|
|
417
|
+
return scored
|
|
418
|
+
.filter((s) => s.score >= top * 0.2)
|
|
419
|
+
.sort((a, b) => Number(a.deprecated) - Number(b.deprecated) || b.score - a.score || a.op.tool.localeCompare(b.op.tool))
|
|
420
|
+
.map(({ op, score }) => ({ op, score: Math.round(score * 100) / 100 }));
|
|
421
|
+
}
|
package/dist/table.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--format table` for the generated CLI: an API result as text for a
|
|
3
|
+
* person at a terminal. Generated by Typeship — https://typeship.dev
|
|
4
|
+
*
|
|
5
|
+
* JSON stays the default and the automation format; this view is opt-in and
|
|
6
|
+
* lossy by design. Lists become columns chosen by name (identifiers, then
|
|
7
|
+
* names, then status-like fields) while they fit the terminal width; a single
|
|
8
|
+
* object becomes one "field value" line per field in the same order. Cells longer than
|
|
9
|
+
* MAX_CELL characters end in "…", and the footer names every column or field
|
|
10
|
+
* that was left out. Pure: no I/O, so tests render without a terminal.
|
|
11
|
+
*/
|
|
12
|
+
/** The longest cell before it is cut with "…". */
|
|
13
|
+
export declare const MAX_CELL = 40;
|
|
14
|
+
/** A single object shows at most this many (flattened) fields. */
|
|
15
|
+
export declare const MAX_FIELDS = 60;
|
|
16
|
+
export interface TableOptions {
|
|
17
|
+
/** Terminal width in columns; lists add columns until it is used. */
|
|
18
|
+
width?: number;
|
|
19
|
+
/** Wraps header text, e.g. in bold when color is on. */
|
|
20
|
+
heading?: (text: string) => string;
|
|
21
|
+
/** The array property that holds a collection response's items ({data: [...]}). */
|
|
22
|
+
collectionField?: string | null;
|
|
23
|
+
/** The command's resource (shipments), so shipment_id is recognized as the row's identifier. */
|
|
24
|
+
resource?: string;
|
|
25
|
+
}
|
|
26
|
+
/** The text --format table prints for a result that would otherwise print as JSON. */
|
|
27
|
+
export declare function renderTable(value: unknown, options?: TableOptions): string;
|
|
28
|
+
//# sourceMappingURL=table.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../src/table.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,kDAAkD;AAClD,eAAO,MAAM,QAAQ,KAAK,CAAC;AAC3B,kEAAkE;AAClE,eAAO,MAAM,UAAU,KAAK,CAAC;AAE7B,MAAM,WAAW,YAAY;IAC3B,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;IACnC,mFAAmF;IACnF,eAAe,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,gGAAgG;IAChG,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAsID,sFAAsF;AACtF,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,EAAE,OAAO,GAAE,YAAiB,GAAG,MAAM,CAK9E"}
|