@ishlabs/cli 0.19.0 → 0.20.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/dist/commands/ask.js +26 -2
- package/dist/commands/config.js +9 -1
- package/dist/commands/docs.js +6 -7
- package/dist/commands/person.js +123 -9
- package/dist/commands/secret.js +25 -2
- package/dist/commands/source.d.ts +1 -1
- package/dist/commands/source.js +10 -6
- package/dist/commands/study.js +19 -0
- package/dist/commands/workspace.js +41 -6
- package/dist/index.js +227 -44
- package/dist/lib/alias-store.js +23 -4
- package/dist/lib/auth.js +22 -4
- package/dist/lib/baggage.d.ts +15 -6
- package/dist/lib/baggage.js +14 -8
- package/dist/lib/command-helpers.d.ts +1 -0
- package/dist/lib/command-helpers.js +79 -7
- package/dist/lib/docs.js +211 -21
- package/dist/lib/output.js +61 -17
- package/dist/lib/profile-sources.js +18 -0
- package/dist/lib/skill-content.js +10 -2
- package/dist/upgrade.js +9 -2
- package/package.json +1 -1
package/dist/commands/ask.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ish ask — Create and run asks (multi-round surveys with variants).
|
|
3
3
|
*/
|
|
4
|
-
import { withClient, getWebUrl, terminalLink, resolveWorkspace, resolveAsk, collectRepeatable, parseWaitTimeout, resolvePersonIds, addPersonFilterFlags, } from "../lib/command-helpers.js";
|
|
4
|
+
import { withClient, getWebUrl, terminalLink, resolveWorkspace, resolveAsk, collectRepeatable, parseWaitTimeout, resolvePersonIds, addPersonFilterFlags, confirmDestructive, } from "../lib/command-helpers.js";
|
|
5
5
|
import { resolveId, tagAlias, ALIAS_PREFIX } from "../lib/alias-store.js";
|
|
6
6
|
import { loadConfig, saveConfig } from "../config.js";
|
|
7
7
|
import { formatAskList, formatAskDetail, formatRoundDetail, formatAskResults, output, } from "../lib/output.js";
|
|
@@ -927,9 +927,33 @@ Examples:
|
|
|
927
927
|
.description("Delete an ask and all its rounds, responses, and participants")
|
|
928
928
|
.argument("[id]", "Ask alias or UUID (defaults to active ask)")
|
|
929
929
|
.option("--ask <id>", "Ask ID; alternative to positional argument")
|
|
930
|
-
.
|
|
930
|
+
.option("-y, --yes", "Skip confirmation prompt (required in --json or non-TTY contexts)")
|
|
931
|
+
.addHelpText("after", `
|
|
932
|
+
Examples:
|
|
933
|
+
$ ish ask delete a-6ec # interactive — prompts for confirmation
|
|
934
|
+
$ ish ask delete a-6ec --yes # non-interactive
|
|
935
|
+
$ ish ask delete a-6ec --json --yes`)
|
|
931
936
|
.action(async (id, opts, cmd) => {
|
|
932
937
|
await withClient(cmd, async (client, globals) => {
|
|
938
|
+
// Code-review #5: when neither positional id nor --ask is provided,
|
|
939
|
+
// we fall back to the active ask in config. If config has no active
|
|
940
|
+
// ask either, the prompt would say "Delete the active ask..." and
|
|
941
|
+
// then error AFTER confirmation with "No active ask" — confusing.
|
|
942
|
+
// Detect the empty-ref case BEFORE the prompt so the user sees
|
|
943
|
+
// the actionable error first.
|
|
944
|
+
const ref = id ?? opts.ask;
|
|
945
|
+
if (!ref) {
|
|
946
|
+
const config = loadConfig();
|
|
947
|
+
if (!config.ask) {
|
|
948
|
+
throw new Error("No active ask to delete. Pass an ask id (positional or --ask <id>), or run `ish ask use <id>` to set one first.");
|
|
949
|
+
}
|
|
950
|
+
}
|
|
951
|
+
// Pattern G follow-up: confirm BEFORE resolving so a typo'd alias
|
|
952
|
+
// (e.g. `a-fff`) doesn't bypass the guard. Show the raw input in
|
|
953
|
+
// the prompt; the alias resolution happens after the user
|
|
954
|
+
// confirms.
|
|
955
|
+
const displayRef = ref ?? "active ask";
|
|
956
|
+
await confirmDestructive(`Delete ${displayRef === "active ask" ? "the active ask" : `ask ${displayRef}`} (and all its rounds, responses, participants)? This cannot be undone.`, { yes: opts.yes, json: globals.json });
|
|
933
957
|
const aid = resolveAsk(pickAskRef(id, opts.ask));
|
|
934
958
|
await client.del(`/asks/${aid}`);
|
|
935
959
|
// If we just deleted the active ask, clear it from config.
|
package/dist/commands/config.js
CHANGED
|
@@ -12,7 +12,15 @@ export function registerConfigCommands(program) {
|
|
|
12
12
|
A simulation config tunes how participants behave during a run (model, timing, retries, etc.).
|
|
13
13
|
Pass \`--config <id>\` to \`ish study run\` to override the default for one dispatch.
|
|
14
14
|
|
|
15
|
-
Configs are global — not scoped to a workspace.
|
|
15
|
+
Configs are global — not scoped to a workspace. Passing \`--workspace\` to a
|
|
16
|
+
\`config\` command is silently ignored (the global flag is parsed; the value
|
|
17
|
+
has no effect on which configs are returned). Identify configs by alias
|
|
18
|
+
(\`c-...\`) or UUID.
|
|
19
|
+
|
|
20
|
+
Note: listing simulation configs requires admin privileges. Non-admins get a
|
|
21
|
+
\`forbidden\` (403) envelope on \`ish config list\`. If you need a specific
|
|
22
|
+
config, pass it directly via \`ish study run --config <id>\`; the run-time
|
|
23
|
+
override doesn't require admin access.
|
|
16
24
|
|
|
17
25
|
Run \`ish docs overview\` for the full mental model.`);
|
|
18
26
|
config
|
package/dist/commands/docs.js
CHANGED
|
@@ -85,13 +85,12 @@ export function registerDocsCommands(program) {
|
|
|
85
85
|
const docs = program
|
|
86
86
|
.command("docs")
|
|
87
87
|
.description("Offline docs for agents — mental model, concept pages, search")
|
|
88
|
-
.addHelpText("after",
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
Examples:
|
|
88
|
+
.addHelpText("after",
|
|
89
|
+
// ISSUE-013: dropped the manual "Subcommands:" block — Commander's
|
|
90
|
+
// auto-generated "Commands:" section already lists the verbs. Keeping
|
|
91
|
+
// just Examples avoids the near-duplicate that was visible on
|
|
92
|
+
// `ish docs --help`.
|
|
93
|
+
`\nExamples:
|
|
95
94
|
$ ish docs # overview
|
|
96
95
|
$ ish docs list
|
|
97
96
|
$ ish docs get-page concepts/study
|
package/dist/commands/person.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ish person — Manage people, generation, and source uploads.
|
|
3
3
|
*/
|
|
4
4
|
import fs from "node:fs";
|
|
5
|
-
import { withClient, readJsonFileOrStdin, resolveWorkspace } from "../lib/command-helpers.js";
|
|
5
|
+
import { withClient, readJsonFileOrStdin, resolveWorkspace, confirmDestructive, normalizeVisibility } from "../lib/command-helpers.js";
|
|
6
6
|
import { resolveId, tagAlias, ALIAS_PREFIX } from "../lib/alias-store.js";
|
|
7
7
|
import { formatPersonList, output, } from "../lib/output.js";
|
|
8
8
|
import { resolveTextContent } from "../lib/upload.js";
|
|
@@ -37,6 +37,12 @@ Concept pages: ish docs get-page concepts/person
|
|
|
37
37
|
.option("--country <country>", "Filter by country code, e.g. US (repeatable)", collect, [])
|
|
38
38
|
.option("--min-age <n>", "Minimum age")
|
|
39
39
|
.option("--max-age <n>", "Maximum age")
|
|
40
|
+
// ISSUE-032: asymmetric — `ask create` and `study run` accept
|
|
41
|
+
// `--visibility workspace|shared|platform` but `person list` didn't,
|
|
42
|
+
// so a user looking for "people scoped to MY workspace" had to
|
|
43
|
+
// download all 13k+ platform people and grep client-side. Same
|
|
44
|
+
// normalizer + flag wording as the sibling commands.
|
|
45
|
+
.option("--visibility <v>", "Filter by visibility: workspace | shared | platform (legacy 'private'/'public' accepted as aliases)")
|
|
40
46
|
.option("--limit <n>", "Max results (default 50)", "50")
|
|
41
47
|
.option("--offset <n>", "Offset for pagination", "0")
|
|
42
48
|
.addHelpText("after", `
|
|
@@ -47,6 +53,8 @@ Examples:
|
|
|
47
53
|
$ ish person list --occupation founder --occupation designer
|
|
48
54
|
$ ish person list --gender female --gender male --country US --country GB
|
|
49
55
|
$ ish person list --type all --json
|
|
56
|
+
$ ish person list --visibility workspace # only workspace-scoped people
|
|
57
|
+
$ ish person list --visibility platform --country US # platform pool by country
|
|
50
58
|
|
|
51
59
|
# Pagination: default --limit is 50, iterate with --offset.
|
|
52
60
|
$ ish person list --limit 100
|
|
@@ -75,6 +83,9 @@ Examples:
|
|
|
75
83
|
params.min_age = opts.minAge;
|
|
76
84
|
if (opts.maxAge)
|
|
77
85
|
params.max_age = opts.maxAge;
|
|
86
|
+
const normVis = normalizeVisibility(opts.visibility);
|
|
87
|
+
if (normVis)
|
|
88
|
+
params.visibility = normVis;
|
|
78
89
|
const data = await client.get("/people", params);
|
|
79
90
|
formatPersonList(data, globals.json, parseInt(opts.limit, 10));
|
|
80
91
|
// Pattern H1: when there's more data, surface a stderr hint so agents
|
|
@@ -104,13 +115,110 @@ Examples:
|
|
|
104
115
|
});
|
|
105
116
|
person
|
|
106
117
|
.command("create")
|
|
107
|
-
.description("Create a person from an exact JSON spec (no LLM)")
|
|
108
|
-
|
|
118
|
+
.description("Create a person from an exact JSON spec (no LLM). Accepts inline flags or --file.")
|
|
119
|
+
// ISSUE-033: previously --file was the ONLY accepted input — asymmetric
|
|
120
|
+
// with `person update` which has a rich inline-flag surface. First-time
|
|
121
|
+
// users reading `person update --help` reasonably expected the same on
|
|
122
|
+
// create. Now mirrored: --file is optional, inline flags compose into
|
|
123
|
+
// the body, inline flags override --file values.
|
|
124
|
+
.option("--file <path>", "JSON file with person data (or '-' for stdin). Escape hatch for fields not covered by inline flags.")
|
|
125
|
+
.option("--name <text>", "Person name (required if --file omitted)")
|
|
126
|
+
.option("--type <value>", "Person type: ai | human (default: ai when --file omitted)")
|
|
127
|
+
.option("--description <text>", "Person description")
|
|
128
|
+
.option("--bio <text>", "Person bio")
|
|
129
|
+
.option("--occupation <text>", "Occupation")
|
|
130
|
+
.option("--country <code>", "Country code, e.g. US")
|
|
131
|
+
.option("--gender <g>", "Gender, e.g. female")
|
|
132
|
+
.option("--date-of-birth <YYYY-MM-DD>", "Date of birth")
|
|
133
|
+
.option("--education-level <value>", `Education level. One of: ${EDUCATION_LEVELS.join(", ")}`)
|
|
134
|
+
.option("--household <value>", `Household composition (MECE). One of: ${HOUSEHOLDS.join(", ")}. A couple raising children is couple_with_kids, not couple_no_kids; "single" means lives alone with no partner, roommates, parents, or children in the household.`)
|
|
135
|
+
.option("--locale-type <value>", `Self-described neighborhood type. One of: ${LOCALE_TYPES.join(", ")}`)
|
|
136
|
+
.option("--income-level <value>", `Self-identified relative socioeconomic position. One of: ${INCOME_LEVELS.join(", ")}`)
|
|
137
|
+
.option("--employment-status <value>", `Primary daytime activity / labor-force status. One of: ${EMPLOYMENT_STATUSES.join(", ")}`)
|
|
138
|
+
.option("--accessibility-profile <json-or-path>", "AccessibilityProfile v1.0 as an inline JSON string OR a path to a JSON file. Empty object {} is the canonical default. Validated client-side against the spec before submit.")
|
|
109
139
|
.option("--workspace <id>", "Workspace (product) ID; falls back to active workspace")
|
|
110
|
-
.addHelpText("after",
|
|
140
|
+
.addHelpText("after", `
|
|
141
|
+
Examples:
|
|
142
|
+
# Inline (mirrors person update):
|
|
143
|
+
$ ish person create --name "Alice" --type ai --country US
|
|
144
|
+
$ ish person create --name "Bob" --gender male --country GB --occupation founder \\
|
|
145
|
+
--household single --locale-type urban --income-level middle \\
|
|
146
|
+
--employment-status employed_full_time --bio "..."
|
|
147
|
+
|
|
148
|
+
# From file:
|
|
149
|
+
$ ish person create --file profile.json
|
|
150
|
+
|
|
151
|
+
# From stdin:
|
|
152
|
+
$ cat profile.json | ish person create --file -
|
|
153
|
+
|
|
154
|
+
# Compose file + inline (inline overrides):
|
|
155
|
+
$ ish person create --file profile.json --bio "Updated bio for this run"
|
|
156
|
+
|
|
157
|
+
Inline flags compose into the create body. --file is an escape hatch for
|
|
158
|
+
fields not covered by inline flags. When both are provided, inline flags
|
|
159
|
+
override values from --file.
|
|
160
|
+
|
|
161
|
+
Minimum required (when --file omitted): --name (--type defaults to "ai").
|
|
162
|
+
|
|
163
|
+
Household MECE rule: a couple raising children is \`couple_with_kids\`, not
|
|
164
|
+
\`couple_no_kids\`. \`single\` means lives alone with no partner, roommates,
|
|
165
|
+
parents, or children sharing the household.
|
|
166
|
+
|
|
167
|
+
\`--accessibility-profile\` accepts either an inline JSON string OR a path to
|
|
168
|
+
a JSON file. Schema: https://ishlabs.io/spec/accessibility-profile-schema.v1.json`)
|
|
111
169
|
.action(async (opts, cmd) => {
|
|
112
170
|
await withClient(cmd, async (client, globals) => {
|
|
113
|
-
|
|
171
|
+
let body = {};
|
|
172
|
+
if (opts.file) {
|
|
173
|
+
body = (await readJsonFileOrStdin(opts.file));
|
|
174
|
+
}
|
|
175
|
+
// Inline flags compose / override the file body (mirrors person update).
|
|
176
|
+
if (opts.name !== undefined)
|
|
177
|
+
body.name = opts.name;
|
|
178
|
+
if (opts.type !== undefined)
|
|
179
|
+
body.type = opts.type;
|
|
180
|
+
if (opts.description !== undefined)
|
|
181
|
+
body.description = opts.description;
|
|
182
|
+
if (opts.bio !== undefined)
|
|
183
|
+
body.bio = opts.bio;
|
|
184
|
+
if (opts.occupation !== undefined)
|
|
185
|
+
body.occupation = opts.occupation;
|
|
186
|
+
if (opts.country !== undefined)
|
|
187
|
+
body.country = opts.country;
|
|
188
|
+
if (opts.gender !== undefined)
|
|
189
|
+
body.gender = opts.gender;
|
|
190
|
+
if (opts.dateOfBirth !== undefined)
|
|
191
|
+
body.date_of_birth = opts.dateOfBirth;
|
|
192
|
+
if (opts.educationLevel !== undefined) {
|
|
193
|
+
body.education_level = assertEnumValue(opts.educationLevel, EDUCATION_LEVELS, "--education-level");
|
|
194
|
+
}
|
|
195
|
+
if (opts.household !== undefined) {
|
|
196
|
+
body.household = assertEnumValue(opts.household, HOUSEHOLDS, "--household");
|
|
197
|
+
}
|
|
198
|
+
if (opts.localeType !== undefined) {
|
|
199
|
+
body.locale_type = assertEnumValue(opts.localeType, LOCALE_TYPES, "--locale-type");
|
|
200
|
+
}
|
|
201
|
+
if (opts.incomeLevel !== undefined) {
|
|
202
|
+
body.income_level = assertEnumValue(opts.incomeLevel, INCOME_LEVELS, "--income-level");
|
|
203
|
+
}
|
|
204
|
+
if (opts.employmentStatus !== undefined) {
|
|
205
|
+
body.employment_status = assertEnumValue(opts.employmentStatus, EMPLOYMENT_STATUSES, "--employment-status");
|
|
206
|
+
}
|
|
207
|
+
if (opts.accessibilityProfile !== undefined) {
|
|
208
|
+
body.accessibility_profile = parseAccessibilityProfileFlag(opts.accessibilityProfile);
|
|
209
|
+
}
|
|
210
|
+
// Type default — keeps the inline-only path ergonomic without
|
|
211
|
+
// requiring `--type ai` on every minimal create.
|
|
212
|
+
if (body.name && body.type === undefined) {
|
|
213
|
+
body.type = "ai";
|
|
214
|
+
}
|
|
215
|
+
// Without --file AND with no inline flags, the body has only
|
|
216
|
+
// product_id — that would 422 server-side with no useful guidance.
|
|
217
|
+
// Provide a clear local error first.
|
|
218
|
+
if (!opts.file && body.name === undefined) {
|
|
219
|
+
throw new Error("Nothing to create. Provide --file <path> or at least --name (plus optional inline flags). " +
|
|
220
|
+
"Run `ish person create --help` for the full inline-flag set.");
|
|
221
|
+
}
|
|
114
222
|
if (opts.workspace || body.product_id == null) {
|
|
115
223
|
body.product_id = resolveWorkspace(opts.workspace);
|
|
116
224
|
}
|
|
@@ -135,7 +243,7 @@ Examples:
|
|
|
135
243
|
.option("--no-scenarios", "Skip fetching the evidence-grounded scenarios for each generated person")
|
|
136
244
|
.option("--no-wait", "Don't poll source-processing status. Only relevant when --source is a local path (paths get auto-uploaded and processed).")
|
|
137
245
|
.option("--source-timeout <seconds>", "Source-processing poll timeout in seconds. Only relevant when --source is a local path.", "300")
|
|
138
|
-
.option("--timeout <seconds>", "Generation-job poll timeout in seconds (default
|
|
246
|
+
.option("--timeout <seconds>", "Generation-job poll timeout in seconds (default 180; pass a larger value for long-running jobs). The job keeps running server-side past this; re-poll later or re-run with a longer --timeout — don't re-enqueue, that would duplicate the work.", "180")
|
|
139
247
|
.addHelpText("after", `
|
|
140
248
|
Generation is an async job: ish enqueues it, polls until the people plus
|
|
141
249
|
their evidence-grounded scenarios are ready (~30-60s), then prints them. The
|
|
@@ -428,12 +536,18 @@ Schema: https://ishlabs.io/spec/accessibility-profile-schema.v1.json`)
|
|
|
428
536
|
.description("Delete a person")
|
|
429
537
|
.argument("<id>", "Person ID")
|
|
430
538
|
.option("--workspace <id>", "Workspace ID; accepted for consistency (workspace is inferred from the person)")
|
|
431
|
-
.
|
|
432
|
-
.
|
|
539
|
+
.option("-y, --yes", "Skip confirmation prompt (required in --json or non-TTY contexts)")
|
|
540
|
+
.addHelpText("after", `
|
|
541
|
+
Examples:
|
|
542
|
+
$ ish person delete <id> # interactive — prompts for confirmation
|
|
543
|
+
$ ish person delete <id> --yes # non-interactive
|
|
544
|
+
$ ish person delete <id> --json --yes`)
|
|
545
|
+
.action(async (id, opts, cmd) => {
|
|
433
546
|
await withClient(cmd, async (client, globals) => {
|
|
434
547
|
const rid = resolveId(id);
|
|
548
|
+
await confirmDestructive(`Delete person ${tagAlias(ALIAS_PREFIX.person, rid)}? This cannot be undone.`, { yes: opts.yes, json: globals.json });
|
|
435
549
|
await client.del(`/people/${rid}`);
|
|
436
|
-
output({ id: rid, alias: tagAlias(ALIAS_PREFIX.person, rid), message: "
|
|
550
|
+
output({ id: rid, alias: tagAlias(ALIAS_PREFIX.person, rid), message: "Person deleted" }, globals.json, { writePath: true });
|
|
437
551
|
});
|
|
438
552
|
});
|
|
439
553
|
person
|
package/dist/commands/secret.js
CHANGED
|
@@ -16,7 +16,7 @@
|
|
|
16
16
|
* of shell history.
|
|
17
17
|
* - No interactive prompts (CLI is for autonomous agents).
|
|
18
18
|
*/
|
|
19
|
-
import { withClient, resolveWorkspace, readFileOrStdin } from "../lib/command-helpers.js";
|
|
19
|
+
import { withClient, resolveWorkspace, readFileOrStdin, confirmDestructive } from "../lib/command-helpers.js";
|
|
20
20
|
import { output } from "../lib/output.js";
|
|
21
21
|
const RESERVED_SITE_ACCESS_KEYS = new Set([
|
|
22
22
|
"BASIC_AUTH_USERNAME",
|
|
@@ -127,6 +127,8 @@ Examples:
|
|
|
127
127
|
.option("--workspace <id>", "Workspace ID; defaults to active workspace")
|
|
128
128
|
.addHelpText("after", `
|
|
129
129
|
Pick exactly one of: positional <value>, --value-file <path>, --value-stdin.
|
|
130
|
+
Bare \`-\` as the positional value is accepted as an alias for --value-stdin
|
|
131
|
+
(matches the \`--value-file -\` convention).
|
|
130
132
|
Stdin and --value-file are preferred for real keys so the value never lands
|
|
131
133
|
in shell history.
|
|
132
134
|
|
|
@@ -138,6 +140,16 @@ Examples:
|
|
|
138
140
|
.action(async (key, value, opts, cmd) => {
|
|
139
141
|
await withClient(cmd, async (client, globals) => {
|
|
140
142
|
validateKey(key);
|
|
143
|
+
// ISSUE-019: bare `-` as the positional value is a common Unix
|
|
144
|
+
// shorthand for "read from stdin" (matches `--value-file -`'s
|
|
145
|
+
// existing convention). Previously bare `-` was silently stored
|
|
146
|
+
// as the literal string "-" — undocumented + surprising. Treat
|
|
147
|
+
// it as an alias for --value-stdin so the convention matches
|
|
148
|
+
// user expectation. Documented in --help below.
|
|
149
|
+
if (value === "-") {
|
|
150
|
+
value = undefined;
|
|
151
|
+
opts.valueStdin = true;
|
|
152
|
+
}
|
|
141
153
|
// Exactly one input source.
|
|
142
154
|
const sources = [
|
|
143
155
|
value !== undefined,
|
|
@@ -212,7 +224,12 @@ Examples:
|
|
|
212
224
|
.description("Delete a workspace secret by key")
|
|
213
225
|
.argument("<key>", "Secret key to delete")
|
|
214
226
|
.option("--workspace <id>", "Workspace ID; defaults to active workspace")
|
|
215
|
-
.
|
|
227
|
+
.option("-y, --yes", "Skip confirmation prompt (required in --json or non-TTY contexts)")
|
|
228
|
+
.addHelpText("after", `
|
|
229
|
+
Examples:
|
|
230
|
+
$ ish secret delete GROQ_API_KEY # interactive — prompts for confirmation
|
|
231
|
+
$ ish secret delete GROQ_API_KEY --yes # non-interactive
|
|
232
|
+
$ ish secret delete GROQ_API_KEY --json --yes`)
|
|
216
233
|
.action(async (key, opts, cmd) => {
|
|
217
234
|
await withClient(cmd, async (client, globals) => {
|
|
218
235
|
if (!KEY_PATTERN.test(key)) {
|
|
@@ -220,6 +237,12 @@ Examples:
|
|
|
220
237
|
err.name = "ValidationError";
|
|
221
238
|
throw err;
|
|
222
239
|
}
|
|
240
|
+
// Pattern G follow-up: the original Pattern G fix covered
|
|
241
|
+
// workspace/person/source delete (commit 891b22d), then a
|
|
242
|
+
// follow-up added ask delete. `secret delete` was the seventh
|
|
243
|
+
// destructive op and slipped through; rolling the guard in
|
|
244
|
+
// for full consistency.
|
|
245
|
+
await confirmDestructive(`Delete secret "${key}"? This cannot be undone.`, { yes: opts.yes, json: globals.json });
|
|
223
246
|
const wid = resolveWorkspace(opts.workspace);
|
|
224
247
|
const all = await client.get(`/products/${wid}/secrets`);
|
|
225
248
|
const match = all.find((s) => s.key === key);
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ish source — Upload and inspect participant attachments used as generation inputs.
|
|
3
3
|
*
|
|
4
4
|
* Attachments (transcripts, audio, images, PDFs) are inputs to `ish person
|
|
5
|
-
* generate`. For one-shot generation, `
|
|
5
|
+
* generate`. For one-shot generation, `ish person generate --source <path>`
|
|
6
6
|
* auto-uploads. Use these commands to upload once and reuse across multiple
|
|
7
7
|
* generation runs, or to inspect processing status.
|
|
8
8
|
*
|
package/dist/commands/source.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* ish source — Upload and inspect participant attachments used as generation inputs.
|
|
3
3
|
*
|
|
4
4
|
* Attachments (transcripts, audio, images, PDFs) are inputs to `ish person
|
|
5
|
-
* generate`. For one-shot generation, `
|
|
5
|
+
* generate`. For one-shot generation, `ish person generate --source <path>`
|
|
6
6
|
* auto-uploads. Use these commands to upload once and reuse across multiple
|
|
7
7
|
* generation runs, or to inspect processing status.
|
|
8
8
|
*
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* existing scripts and agents; internally we call the unified
|
|
11
11
|
* /people/attachments/* endpoint family (see ADR-0034).
|
|
12
12
|
*/
|
|
13
|
-
import { withClient, resolveWorkspace } from "../lib/command-helpers.js";
|
|
13
|
+
import { withClient, resolveWorkspace, confirmDestructive } from "../lib/command-helpers.js";
|
|
14
14
|
import { resolveId, tagAlias, ALIAS_PREFIX } from "../lib/alias-store.js";
|
|
15
15
|
import { normalizeEnumValue } from "../lib/enums.js";
|
|
16
16
|
import { formatAttachment, output } from "../lib/output.js";
|
|
@@ -24,7 +24,7 @@ export function registerSourceCommands(program) {
|
|
|
24
24
|
A source — internally a participant attachment — is an input to \`ish person generate\`:
|
|
25
25
|
transcript, audio, image, or PDF. Use \`source upload\` when you want to reuse the
|
|
26
26
|
same attachment across multiple generation runs; otherwise pass a local path directly
|
|
27
|
-
to \`
|
|
27
|
+
to \`person generate --source\` and it auto-uploads.
|
|
28
28
|
|
|
29
29
|
Concept pages: ish docs get-page concepts/source
|
|
30
30
|
ish docs get-page concepts/profile`);
|
|
@@ -86,21 +86,25 @@ Examples:
|
|
|
86
86
|
.command("delete")
|
|
87
87
|
.description("Delete a participant attachment plus its uploaded file")
|
|
88
88
|
.argument("<id>", "Attachment ID or alias")
|
|
89
|
+
.option("-y, --yes", "Skip confirmation prompt (required in --json or non-TTY contexts)")
|
|
89
90
|
.addHelpText("after", `
|
|
90
91
|
The backend ref-counts attachment deletes: the file row + storage object are
|
|
91
92
|
removed only when no profile mappings remain. Profiles already generated from
|
|
92
93
|
this attachment keep their seed mapping until they themselves are deleted.
|
|
93
94
|
|
|
94
95
|
Examples:
|
|
95
|
-
$ ish source delete ps-3a4
|
|
96
|
-
|
|
96
|
+
$ ish source delete ps-3a4 # interactive — prompts for confirmation
|
|
97
|
+
$ ish source delete ps-3a4 --yes # non-interactive
|
|
98
|
+
$ ish source delete ps-3a4 --json --yes`)
|
|
99
|
+
.action(async (id, opts, cmd) => {
|
|
97
100
|
await withClient(cmd, async (client, globals) => {
|
|
98
101
|
const rid = resolveId(id);
|
|
102
|
+
await confirmDestructive(`Delete source ${tagAlias(ALIAS_PREFIX.personSource, rid)}? This cannot be undone.`, { yes: opts.yes, json: globals.json });
|
|
99
103
|
await client.del(`/people/attachments/${rid}`);
|
|
100
104
|
output({
|
|
101
105
|
id: rid,
|
|
102
106
|
alias: tagAlias(ALIAS_PREFIX.personSource, rid),
|
|
103
|
-
message: "
|
|
107
|
+
message: "Source deleted",
|
|
104
108
|
}, globals.json, { writePath: true });
|
|
105
109
|
});
|
|
106
110
|
});
|
package/dist/commands/study.js
CHANGED
|
@@ -544,8 +544,18 @@ Next: configure a run with \`ish iteration create --study <id>\`,
|
|
|
544
544
|
const data = await client.post(`/products/${resolvedWs}/studies`, body);
|
|
545
545
|
if (data.id) {
|
|
546
546
|
const config = loadConfig();
|
|
547
|
+
const prevStudy = config.study;
|
|
547
548
|
config.study = data.id;
|
|
548
549
|
saveConfig(config);
|
|
550
|
+
// Auto-activating the new study is intentional ergonomics (the
|
|
551
|
+
// common next step is `ish iteration create --study <new>`), but
|
|
552
|
+
// it was previously silent — surprised users who had set a
|
|
553
|
+
// different active study just before (ISSUE-030). Always
|
|
554
|
+
// surface the side-effect on stderr.
|
|
555
|
+
if (!globals.json) {
|
|
556
|
+
const verb = prevStudy && prevStudy !== data.id ? "replaced" : "set";
|
|
557
|
+
console.error(`Active study ${verb} to "${data.name || data.id}".`);
|
|
558
|
+
}
|
|
549
559
|
}
|
|
550
560
|
const result = data;
|
|
551
561
|
if (result.id)
|
|
@@ -856,6 +866,15 @@ checklists ("steps") ride along when present in the JSON forms
|
|
|
856
866
|
json: globals.json,
|
|
857
867
|
});
|
|
858
868
|
await client.del(`/studies/${rid}`);
|
|
869
|
+
// If the deleted study was active, clear it (Pattern A — parallel to
|
|
870
|
+
// ask delete and chat endpoint delete which already do this).
|
|
871
|
+
const config = loadConfig();
|
|
872
|
+
if (config.study === rid) {
|
|
873
|
+
delete config.study;
|
|
874
|
+
saveConfig(config);
|
|
875
|
+
if (!globals.json)
|
|
876
|
+
console.error("(Cleared active study.)");
|
|
877
|
+
}
|
|
859
878
|
output({ id: rid, alias: tagAlias(ALIAS_PREFIX.study, rid), message: "Study deleted" }, globals.json, { writePath: true });
|
|
860
879
|
});
|
|
861
880
|
});
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ish workspace — Manage workspaces (API: /products).
|
|
3
3
|
*/
|
|
4
|
-
import { withClient, getWebUrl, terminalLink, resolveWorkspace } from "../lib/command-helpers.js";
|
|
4
|
+
import { withClient, getWebUrl, terminalLink, resolveWorkspace, confirmDestructive } from "../lib/command-helpers.js";
|
|
5
5
|
import { resolveId, tagAlias, ALIAS_PREFIX } from "../lib/alias-store.js";
|
|
6
6
|
import { loadConfig, saveConfig } from "../config.js";
|
|
7
7
|
import { formatWorkspaceList, formatWorkspaceDetail, formatSiteAccessStatus, output } from "../lib/output.js";
|
|
@@ -134,13 +134,35 @@ existing workspace was returned. On creation, \`reused: false\`.`)
|
|
|
134
134
|
});
|
|
135
135
|
workspace
|
|
136
136
|
.command("delete")
|
|
137
|
-
.description("Delete a workspace")
|
|
137
|
+
.description("Delete a workspace (and ALL nested studies, asks, people, secrets, configs, sources, chat endpoints)")
|
|
138
138
|
.argument("<id>", "Workspace ID")
|
|
139
|
-
.
|
|
140
|
-
.
|
|
139
|
+
.option("-y, --yes", "Skip confirmation prompt (required in --json or non-TTY contexts)")
|
|
140
|
+
.addHelpText("after", `
|
|
141
|
+
Deleting a workspace is the highest-blast-radius destructive op in the CLI:
|
|
142
|
+
it removes ALL nested studies, asks, people, secrets, configs, sources, and
|
|
143
|
+
chat endpoints. This cannot be undone.
|
|
144
|
+
|
|
145
|
+
Examples:
|
|
146
|
+
$ ish workspace delete <id> # interactive — prompts for confirmation
|
|
147
|
+
$ ish workspace delete <id> --yes # non-interactive
|
|
148
|
+
$ ish workspace delete <id> --json --yes`)
|
|
149
|
+
.action(async (id, opts, cmd) => {
|
|
141
150
|
await withClient(cmd, async (client, globals) => {
|
|
142
151
|
const rid = resolveId(id);
|
|
152
|
+
await confirmDestructive(`Delete workspace ${tagAlias(ALIAS_PREFIX.workspace, rid)}? This will delete ALL nested studies, asks, people, secrets, configs, sources, and chat endpoints. This cannot be undone.`, { yes: opts.yes, json: globals.json });
|
|
143
153
|
await client.del(`/products/${rid}`);
|
|
154
|
+
// If the deleted workspace was active, clear it + its scoped children
|
|
155
|
+
// so subsequent commands don't render orphan refs (Pattern A).
|
|
156
|
+
const config = loadConfig();
|
|
157
|
+
if (config.workspace === rid) {
|
|
158
|
+
delete config.workspace;
|
|
159
|
+
delete config.study;
|
|
160
|
+
delete config.ask;
|
|
161
|
+
delete config.chat_endpoint;
|
|
162
|
+
saveConfig(config);
|
|
163
|
+
if (!globals.json)
|
|
164
|
+
console.error("(Cleared active workspace + study / ask / chat endpoint.)");
|
|
165
|
+
}
|
|
144
166
|
output({ id: rid, alias: tagAlias(ALIAS_PREFIX.workspace, rid), message: "Workspace deleted" }, globals.json, { writePath: true });
|
|
145
167
|
});
|
|
146
168
|
});
|
|
@@ -187,9 +209,12 @@ Examples:
|
|
|
187
209
|
if (opts.clear) {
|
|
188
210
|
const config = loadConfig();
|
|
189
211
|
delete config.workspace;
|
|
212
|
+
// workspace-scoped children: clearing the workspace orphans them all.
|
|
190
213
|
delete config.study;
|
|
214
|
+
delete config.ask;
|
|
215
|
+
delete config.chat_endpoint;
|
|
191
216
|
saveConfig(config);
|
|
192
|
-
console.error("Cleared active workspace (and study).");
|
|
217
|
+
console.error("Cleared active workspace (and active study / ask / chat endpoint).");
|
|
193
218
|
return;
|
|
194
219
|
}
|
|
195
220
|
if (!id) {
|
|
@@ -199,10 +224,20 @@ Examples:
|
|
|
199
224
|
const rid = resolveId(id);
|
|
200
225
|
const data = await client.get(`/products/${rid}`);
|
|
201
226
|
const config = loadConfig();
|
|
227
|
+
const switched = config.workspace !== rid;
|
|
202
228
|
config.workspace = rid;
|
|
203
|
-
|
|
229
|
+
if (switched) {
|
|
230
|
+
// Switching workspaces orphans all workspace-scoped active refs;
|
|
231
|
+
// dropping them avoids silent cross-workspace footguns (ISSUE-004).
|
|
232
|
+
delete config.study;
|
|
233
|
+
delete config.ask;
|
|
234
|
+
delete config.chat_endpoint;
|
|
235
|
+
}
|
|
204
236
|
saveConfig(config);
|
|
205
237
|
console.error(`Active workspace set to "${data.name || rid}".`);
|
|
238
|
+
if (switched) {
|
|
239
|
+
console.error("(Cleared active study / ask / chat endpoint — they belonged to the previous workspace.)");
|
|
240
|
+
}
|
|
206
241
|
});
|
|
207
242
|
});
|
|
208
243
|
}
|