@ishlabs/cli 0.8.5 → 0.10.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 +55 -6
- package/dist/auth.d.ts +23 -4
- package/dist/auth.js +165 -39
- package/dist/commands/ask.d.ts +12 -0
- package/dist/commands/ask.js +127 -2
- package/dist/commands/chat.d.ts +17 -0
- package/dist/commands/chat.js +589 -0
- package/dist/commands/iteration.js +232 -13
- package/dist/commands/secret.d.ts +20 -0
- package/dist/commands/secret.js +246 -0
- package/dist/commands/source.js +24 -2
- package/dist/commands/study-run.d.ts +38 -0
- package/dist/commands/study-run.js +199 -80
- package/dist/commands/study-tester.js +17 -2
- package/dist/commands/study.js +311 -39
- package/dist/commands/workspace.js +81 -0
- package/dist/config.d.ts +7 -0
- package/dist/connect.d.ts +3 -0
- package/dist/connect.js +359 -24
- package/dist/index.js +67 -9
- package/dist/lib/alias-hydrate.d.ts +42 -0
- package/dist/lib/alias-hydrate.js +175 -0
- package/dist/lib/alias-store.d.ts +1 -0
- package/dist/lib/alias-store.js +28 -1
- package/dist/lib/auth.js +11 -3
- package/dist/lib/chat-endpoint-formatters.d.ts +39 -0
- package/dist/lib/chat-endpoint-formatters.js +104 -0
- package/dist/lib/command-helpers.d.ts +18 -0
- package/dist/lib/command-helpers.js +188 -53
- package/dist/lib/docs.js +662 -34
- package/dist/lib/modality.d.ts +42 -0
- package/dist/lib/modality.js +192 -0
- package/dist/lib/output.d.ts +41 -0
- package/dist/lib/output.js +453 -19
- package/dist/lib/paths.d.ts +1 -0
- package/dist/lib/paths.js +3 -0
- package/dist/lib/skill-content.js +183 -13
- package/dist/lib/types.d.ts +15 -0
- package/package.json +3 -3
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared modality helpers.
|
|
3
|
+
*
|
|
4
|
+
* Centralises the modality-aware predicates and validators that used to
|
|
5
|
+
* live alongside individual command files. Keeping them here means a new
|
|
6
|
+
* modality (or a tweak to an existing one) only needs to land in one place
|
|
7
|
+
* and the call sites (`study run`, `iteration update`, ...) pick up the
|
|
8
|
+
* change for free.
|
|
9
|
+
*/
|
|
10
|
+
export type Modality = "interactive" | "video" | "audio" | "text" | "image" | "document" | "chat";
|
|
11
|
+
/** True for the media-bucket modalities that share batch + content semantics. */
|
|
12
|
+
export declare function isMediaModality(modality: string | undefined): boolean;
|
|
13
|
+
/** True for chat modality. Wrapped as a helper so callers don't sprinkle string literals. */
|
|
14
|
+
export declare function isChatModality(modality: string | undefined): boolean;
|
|
15
|
+
/**
|
|
16
|
+
* Pattern F (Issue #9): an iteration without content is a footgun.
|
|
17
|
+
* `ish study generate` auto-creates an empty iteration "A" that becomes
|
|
18
|
+
* `study run`'s default target unless `--iteration` is explicit; agents
|
|
19
|
+
* silently dispatch against it. This predicate is the gate that lets
|
|
20
|
+
* study run refuse with a clear suggestion instead of silent failure.
|
|
21
|
+
*/
|
|
22
|
+
export declare function iterationHasContent(details: unknown, modality: string | undefined): boolean;
|
|
23
|
+
/** The flag fragment a user should pass to populate content for a given modality. */
|
|
24
|
+
export declare function describeRequiredContentFlag(modality: string): string;
|
|
25
|
+
export type DetailsValidation = {
|
|
26
|
+
ok: true;
|
|
27
|
+
} | {
|
|
28
|
+
ok: false;
|
|
29
|
+
reason: string;
|
|
30
|
+
suggestion: string;
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Detect modality/details mismatch on `iteration update --details-json`.
|
|
34
|
+
*
|
|
35
|
+
* Catches the silent-corruption footgun where an agent passes
|
|
36
|
+
* interactive-shape details (`{"url":"..."}`) to a chat iteration (or
|
|
37
|
+
* vice versa). Returns `{ ok: true }` for unknown modalities so we don't
|
|
38
|
+
* reject things we don't yet understand. Real shape validation (Zod,
|
|
39
|
+
* etc.) lives in the backend; this is a quick sanity check at the CLI
|
|
40
|
+
* boundary.
|
|
41
|
+
*/
|
|
42
|
+
export declare function validateIterationDetails(modality: string | undefined, details: unknown): DetailsValidation;
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared modality helpers.
|
|
3
|
+
*
|
|
4
|
+
* Centralises the modality-aware predicates and validators that used to
|
|
5
|
+
* live alongside individual command files. Keeping them here means a new
|
|
6
|
+
* modality (or a tweak to an existing one) only needs to land in one place
|
|
7
|
+
* and the call sites (`study run`, `iteration update`, ...) pick up the
|
|
8
|
+
* change for free.
|
|
9
|
+
*/
|
|
10
|
+
import { MEDIA_MODALITIES } from "./types.js";
|
|
11
|
+
/** True for the media-bucket modalities that share batch + content semantics. */
|
|
12
|
+
export function isMediaModality(modality) {
|
|
13
|
+
return !!modality && MEDIA_MODALITIES.includes(modality);
|
|
14
|
+
}
|
|
15
|
+
/** True for chat modality. Wrapped as a helper so callers don't sprinkle string literals. */
|
|
16
|
+
export function isChatModality(modality) {
|
|
17
|
+
return modality === "chat";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Pattern F (Issue #9): an iteration without content is a footgun.
|
|
21
|
+
* `ish study generate` auto-creates an empty iteration "A" that becomes
|
|
22
|
+
* `study run`'s default target unless `--iteration` is explicit; agents
|
|
23
|
+
* silently dispatch against it. This predicate is the gate that lets
|
|
24
|
+
* study run refuse with a clear suggestion instead of silent failure.
|
|
25
|
+
*/
|
|
26
|
+
export function iterationHasContent(details, modality) {
|
|
27
|
+
if (!details || typeof details !== "object")
|
|
28
|
+
return false;
|
|
29
|
+
const d = details;
|
|
30
|
+
if (modality === "text") {
|
|
31
|
+
return typeof d.content_text === "string" && d.content_text.length > 0;
|
|
32
|
+
}
|
|
33
|
+
if (modality === "video" || modality === "audio" || modality === "document") {
|
|
34
|
+
return typeof d.content_url === "string" && d.content_url.length > 0;
|
|
35
|
+
}
|
|
36
|
+
if (modality === "image") {
|
|
37
|
+
const urls = d.image_urls;
|
|
38
|
+
if (Array.isArray(urls))
|
|
39
|
+
return urls.length > 0;
|
|
40
|
+
if (typeof urls === "string")
|
|
41
|
+
return urls.length > 0;
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
if (modality === "chat") {
|
|
45
|
+
// A chat iteration carries either an inline endpoint config or a
|
|
46
|
+
// pointer to a saved chatbot_endpoints row.
|
|
47
|
+
const hasEndpoint = !!d.endpoint && typeof d.endpoint === "object";
|
|
48
|
+
const hasEndpointId = typeof d.chatbot_endpoint_id === "string"
|
|
49
|
+
&& d.chatbot_endpoint_id.length > 0;
|
|
50
|
+
return hasEndpoint || hasEndpointId;
|
|
51
|
+
}
|
|
52
|
+
// interactive (default)
|
|
53
|
+
return typeof d.url === "string" && d.url.length > 0;
|
|
54
|
+
}
|
|
55
|
+
/** The flag fragment a user should pass to populate content for a given modality. */
|
|
56
|
+
export function describeRequiredContentFlag(modality) {
|
|
57
|
+
if (modality === "text")
|
|
58
|
+
return "--content-text <text-or-@file>";
|
|
59
|
+
if (modality === "video" || modality === "audio" || modality === "document") {
|
|
60
|
+
return "--content-url <url-or-file>";
|
|
61
|
+
}
|
|
62
|
+
if (modality === "image")
|
|
63
|
+
return "--image-urls <comma-separated>";
|
|
64
|
+
if (modality === "chat")
|
|
65
|
+
return "--endpoint <id> | --endpoint-config <file>";
|
|
66
|
+
return "--url <url>";
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Detect modality/details mismatch on `iteration update --details-json`.
|
|
70
|
+
*
|
|
71
|
+
* Catches the silent-corruption footgun where an agent passes
|
|
72
|
+
* interactive-shape details (`{"url":"..."}`) to a chat iteration (or
|
|
73
|
+
* vice versa). Returns `{ ok: true }` for unknown modalities so we don't
|
|
74
|
+
* reject things we don't yet understand. Real shape validation (Zod,
|
|
75
|
+
* etc.) lives in the backend; this is a quick sanity check at the CLI
|
|
76
|
+
* boundary.
|
|
77
|
+
*/
|
|
78
|
+
export function validateIterationDetails(modality, details) {
|
|
79
|
+
if (!details || typeof details !== "object" || Array.isArray(details)) {
|
|
80
|
+
return { ok: true };
|
|
81
|
+
}
|
|
82
|
+
const d = details;
|
|
83
|
+
const hasUrl = typeof d.url === "string" && d.url.length > 0;
|
|
84
|
+
const hasEndpointObj = !!d.endpoint && typeof d.endpoint === "object";
|
|
85
|
+
const hasEndpointId = typeof d.chatbot_endpoint_id === "string"
|
|
86
|
+
&& d.chatbot_endpoint_id.length > 0;
|
|
87
|
+
const hasContentText = typeof d.content_text === "string" && d.content_text.length > 0;
|
|
88
|
+
const hasContentUrl = typeof d.content_url === "string" && d.content_url.length > 0;
|
|
89
|
+
const imageUrls = d.image_urls;
|
|
90
|
+
const hasImageUrls = Array.isArray(imageUrls)
|
|
91
|
+
? imageUrls.length > 0
|
|
92
|
+
: typeof imageUrls === "string" && imageUrls.length > 0;
|
|
93
|
+
if (modality === "chat") {
|
|
94
|
+
if (hasEndpointObj || hasEndpointId)
|
|
95
|
+
return { ok: true };
|
|
96
|
+
if (hasUrl) {
|
|
97
|
+
return {
|
|
98
|
+
ok: false,
|
|
99
|
+
reason: "interactive-shape details on a chat iteration",
|
|
100
|
+
suggestion: "use --details-json with .endpoint or .chatbot_endpoint_id, or use `ish iteration update` with --endpoint <id>",
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
if (hasContentText || hasContentUrl || hasImageUrls) {
|
|
104
|
+
return {
|
|
105
|
+
ok: false,
|
|
106
|
+
reason: "media-shape details on a chat iteration",
|
|
107
|
+
suggestion: "chat iterations require .endpoint (object) or .chatbot_endpoint_id (string); pass --endpoint <id> or --endpoint-config <file>",
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
// No recognised content keys at all. Don't reject — agent might be
|
|
111
|
+
// updating ancillary fields we don't model here.
|
|
112
|
+
return { ok: true };
|
|
113
|
+
}
|
|
114
|
+
if (modality === "interactive") {
|
|
115
|
+
if (hasUrl)
|
|
116
|
+
return { ok: true };
|
|
117
|
+
if (hasEndpointObj || hasEndpointId) {
|
|
118
|
+
return {
|
|
119
|
+
ok: false,
|
|
120
|
+
reason: "chat-shape details on an interactive iteration",
|
|
121
|
+
suggestion: "use --details-json with .url for interactive iterations, or change the study modality to chat",
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (hasContentText || hasContentUrl || hasImageUrls) {
|
|
125
|
+
return {
|
|
126
|
+
ok: false,
|
|
127
|
+
reason: "media-shape details on an interactive iteration",
|
|
128
|
+
suggestion: "interactive iterations require .url; pass --url <url> or --details-json '{\"url\":\"...\"}'",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return { ok: true };
|
|
132
|
+
}
|
|
133
|
+
if (modality === "text") {
|
|
134
|
+
if (hasContentText)
|
|
135
|
+
return { ok: true };
|
|
136
|
+
if (hasUrl) {
|
|
137
|
+
return {
|
|
138
|
+
ok: false,
|
|
139
|
+
reason: "interactive-shape details on a text iteration",
|
|
140
|
+
suggestion: "text iterations require .content_text; pass --content-text <text-or-@file>",
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
if (hasEndpointObj || hasEndpointId) {
|
|
144
|
+
return {
|
|
145
|
+
ok: false,
|
|
146
|
+
reason: "chat-shape details on a text iteration",
|
|
147
|
+
suggestion: "text iterations require .content_text; pass --content-text <text-or-@file>",
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
return { ok: true };
|
|
151
|
+
}
|
|
152
|
+
if (modality === "video" || modality === "audio" || modality === "document") {
|
|
153
|
+
if (hasContentUrl)
|
|
154
|
+
return { ok: true };
|
|
155
|
+
if (hasUrl) {
|
|
156
|
+
return {
|
|
157
|
+
ok: false,
|
|
158
|
+
reason: `interactive-shape details on a ${modality} iteration`,
|
|
159
|
+
suggestion: `${modality} iterations require .content_url; pass --content-url <url-or-file>`,
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
if (hasEndpointObj || hasEndpointId) {
|
|
163
|
+
return {
|
|
164
|
+
ok: false,
|
|
165
|
+
reason: `chat-shape details on a ${modality} iteration`,
|
|
166
|
+
suggestion: `${modality} iterations require .content_url; pass --content-url <url-or-file>`,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
return { ok: true };
|
|
170
|
+
}
|
|
171
|
+
if (modality === "image") {
|
|
172
|
+
if (hasImageUrls)
|
|
173
|
+
return { ok: true };
|
|
174
|
+
if (hasUrl) {
|
|
175
|
+
return {
|
|
176
|
+
ok: false,
|
|
177
|
+
reason: "interactive-shape details on an image iteration",
|
|
178
|
+
suggestion: "image iterations require .image_urls (array or comma-separated string); pass --image-urls <urls>",
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (hasEndpointObj || hasEndpointId) {
|
|
182
|
+
return {
|
|
183
|
+
ok: false,
|
|
184
|
+
reason: "chat-shape details on an image iteration",
|
|
185
|
+
suggestion: "image iterations require .image_urls; pass --image-urls <urls>",
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
return { ok: true };
|
|
189
|
+
}
|
|
190
|
+
// Unknown modality — don't reject, let the backend decide.
|
|
191
|
+
return { ok: true };
|
|
192
|
+
}
|
package/dist/lib/output.d.ts
CHANGED
|
@@ -49,6 +49,30 @@ export declare function formatSiteAccessStatus(summary: import("./site-access.js
|
|
|
49
49
|
export declare function formatStudyList(studies: Record<string, unknown>[], json: boolean): void;
|
|
50
50
|
export declare function formatStudyDetail(study: Record<string, unknown>, json: boolean, options?: OutputOptions): void;
|
|
51
51
|
export declare function formatStudyResults(study: Record<string, unknown>, json: boolean): void;
|
|
52
|
+
/**
|
|
53
|
+
* `study results --summary` projection. Drops interview_answers + per-tester
|
|
54
|
+
* interaction breakdowns; keeps headline counters, sentiment histogram, and a
|
|
55
|
+
* per-tester {alias, status, sentiment, comment} row. Useful for agents that
|
|
56
|
+
* need to branch on outcome without paying for the full envelope.
|
|
57
|
+
*/
|
|
58
|
+
export declare function buildStudyResultsSummary(study: Record<string, unknown>): Record<string, unknown>;
|
|
59
|
+
/**
|
|
60
|
+
* `study results --transcript <tester_id>` projection. Mirrors the schema
|
|
61
|
+
* MCP's `get_chat_transcript` returns (`src/ish_mcp/projections.py:
|
|
62
|
+
* build_chat_transcript`) so callers see the same shape regardless of
|
|
63
|
+
* surface. Tester turns whose action carries no text (e.g. select_option)
|
|
64
|
+
* surface `text: null`; intent lives on `action_type` + `option_label`.
|
|
65
|
+
* Bot turns with a `bot_reply.failure` block surface `failure` and
|
|
66
|
+
* `text: null` and don't count toward `unique_bot_replies`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function buildChatTranscript(tester: Record<string, unknown>): Record<string, unknown>;
|
|
69
|
+
/**
|
|
70
|
+
* `study tester --summary` projection. Drops the action timeline; keeps the
|
|
71
|
+
* headline (alias, status, sentiment, comment, error_message). Useful for
|
|
72
|
+
* the common "did this tester finish, what did they say" check that's
|
|
73
|
+
* currently buried under the full interactions array.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildTesterSummary(tester: Record<string, unknown>): Record<string, unknown>;
|
|
52
76
|
export declare function formatIterationList(iterations: Record<string, unknown>[], json: boolean): void;
|
|
53
77
|
export declare function formatTesterDetail(tester: Record<string, unknown>, json: boolean): void;
|
|
54
78
|
export declare function formatTesterProfileList(profiles: unknown, json: boolean, limit?: number): void;
|
|
@@ -58,5 +82,22 @@ export declare function formatSimulationPoll(results: Record<string, unknown>[],
|
|
|
58
82
|
export declare function formatAskList(asks: Record<string, unknown>[], json: boolean): void;
|
|
59
83
|
export declare function formatAskDetail(ask: Record<string, unknown>, json: boolean): void;
|
|
60
84
|
export declare function formatRoundDetail(round: Record<string, unknown>, json: boolean): void;
|
|
85
|
+
/**
|
|
86
|
+
* Derive a coarse confidence label from sample size + tied-ness + error mix.
|
|
87
|
+
*
|
|
88
|
+
* Rules (lowest wins):
|
|
89
|
+
* - low: n < 3 OR tied OR any errored response (we have visible failures)
|
|
90
|
+
* - medium: 3 <= n < 10 (small sample but clean)
|
|
91
|
+
* - high: n >= 10 AND no errored responses AND not tied
|
|
92
|
+
*
|
|
93
|
+
* Tuned for the typical 5-tester ask: a clean 5/5 lands at "medium" (you
|
|
94
|
+
* can probably trust the lean), 1/5 with no errors lands at "low" (you
|
|
95
|
+
* need more data), 5/5 with a tie lands at "low" (no winner to call).
|
|
96
|
+
*/
|
|
97
|
+
export declare function deriveWinnerConfidence(args: {
|
|
98
|
+
n: number;
|
|
99
|
+
errored: number;
|
|
100
|
+
tied: boolean;
|
|
101
|
+
}): "low" | "medium" | "high";
|
|
61
102
|
export declare function formatAskResults(ask: Record<string, unknown>, json: boolean, roundFilter?: number): void;
|
|
62
103
|
export declare function formatConfigList(configs: Record<string, unknown>[], json: boolean): void;
|