@wirecat/cli-core 0.369.0 → 0.370.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/CHANGELOG.md +15 -0
- package/dist/cli/guide.d.ts +15 -0
- package/dist/cli/guide.js +58 -9
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,21 @@ shape of this file.
|
|
|
7
7
|
|
|
8
8
|
## Unreleased
|
|
9
9
|
|
|
10
|
+
## 0.370.0 — 20.10.2026
|
|
11
|
+
|
|
12
|
+
### Changed — may break callers
|
|
13
|
+
|
|
14
|
+
- **`checkGuide` finds a scenario step a shell would change**, so a tool's guide test that passed may now fail. It
|
|
15
|
+
splits each step as a POSIX shell does and reports `>`, `<`, `|`, `&`, `;`, `` ` ``, `$(` and the globs `*`, `?`,
|
|
16
|
+
`[` left outside quotes (`` ` `` and `$(` also inside double quotes), and a quote never closed. `max review
|
|
17
|
+
date>1d` passed before, though a shell writes a file named `1d` and drops the filter. Quote the word:
|
|
18
|
+
`'date>1d'`. `shellProblems(run)` gives the same check for one step.
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
|
|
22
|
+
- **`MEMO_SCENARIOS` holds the titles of memo's cross-tool scenarios** (`personBrief`, `decisionTrail`), so every
|
|
23
|
+
tool's "Works with" list takes them from one place instead of typing its own copy.
|
|
24
|
+
|
|
10
25
|
## 0.369.0 — 20.10.2026
|
|
11
26
|
|
|
12
27
|
### Fixed
|
package/dist/cli/guide.d.ts
CHANGED
|
@@ -18,6 +18,19 @@ export interface GuideReference {
|
|
|
18
18
|
name: string;
|
|
19
19
|
title: string;
|
|
20
20
|
}
|
|
21
|
+
/** memo's cross-tool scenarios: the one source of the titles every tool lists, and memo's files must carry. */
|
|
22
|
+
export declare const MEMO_SCENARIOS: {
|
|
23
|
+
readonly personBrief: {
|
|
24
|
+
readonly tool: "memo";
|
|
25
|
+
readonly name: "person-brief";
|
|
26
|
+
readonly title: "One person across every tool";
|
|
27
|
+
};
|
|
28
|
+
readonly decisionTrail: {
|
|
29
|
+
readonly tool: "memo";
|
|
30
|
+
readonly name: "decision-trail";
|
|
31
|
+
readonly title: "What did we decide about X, across every source";
|
|
32
|
+
};
|
|
33
|
+
};
|
|
21
34
|
export interface GuideOptions {
|
|
22
35
|
/** Absolute path of the tool's scenario files: `fileURLToPath(new URL("../scenarios", import.meta.url))`. */
|
|
23
36
|
directory: string;
|
|
@@ -40,6 +53,8 @@ export declare const loadScenarios: (directory: string) => Promise<{
|
|
|
40
53
|
* program is built. `checkGuide` validates them in the tool's CI.
|
|
41
54
|
*/
|
|
42
55
|
export declare const guideCommand: (app: Pick<AppIdentity, "command">, options: GuideOptions) => Command;
|
|
56
|
+
/** Characters in a step that a shell would act on: the person who copies it would run something else. */
|
|
57
|
+
export declare const shellProblems: (run: string) => string[];
|
|
43
58
|
/** What is wrong with one step on this program: the path, each option and its value, the arguments. Never runs it. */
|
|
44
59
|
export declare const checkStep: (program: Command, run: string) => string[];
|
|
45
60
|
/**
|
package/dist/cli/guide.js
CHANGED
|
@@ -7,6 +7,11 @@ import { environmentOf, outputFor } from "./context.js";
|
|
|
7
7
|
import { NO_EFFECTS } from "./effects.js";
|
|
8
8
|
import { itemOf, listOf, renderAnswer } from "./envelope.js";
|
|
9
9
|
import { PROFILE_FREE } from "./profile.js";
|
|
10
|
+
/** memo's cross-tool scenarios: the one source of the titles every tool lists, and memo's files must carry. */
|
|
11
|
+
export const MEMO_SCENARIOS = {
|
|
12
|
+
personBrief: { tool: "memo", name: "person-brief", title: "One person across every tool" },
|
|
13
|
+
decisionTrail: { tool: "memo", name: "decision-trail", title: "What did we decide about X, across every source" },
|
|
14
|
+
};
|
|
10
15
|
const FRONT_MATTER = /^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/;
|
|
11
16
|
const FIELDS = new Set(["title", "goal", "steps", "look"]);
|
|
12
17
|
const NAME = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
@@ -157,17 +162,49 @@ export const guideCommand = (app, options) => {
|
|
|
157
162
|
guides.set(command, options);
|
|
158
163
|
return command;
|
|
159
164
|
};
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
const OPERATORS = {
|
|
166
|
+
">": "redirects output to a file",
|
|
167
|
+
"<": "reads input from a file",
|
|
168
|
+
"|": "pipes into another command",
|
|
169
|
+
"&": "runs the command in the background",
|
|
170
|
+
";": "ends the command there",
|
|
171
|
+
"`": "runs it as a command",
|
|
172
|
+
"$(": "runs it as a command",
|
|
173
|
+
"*": "expands it to file names",
|
|
174
|
+
"?": "expands it to file names",
|
|
175
|
+
"[": "expands it to file names",
|
|
176
|
+
};
|
|
177
|
+
/**
|
|
178
|
+
* A step split into words as a POSIX shell would — '…' literal, "…" with \ escapes, \ outside quotes — and the
|
|
179
|
+
* characters a shell would act on instead of passing them to the tool. `` ` `` and `$(` still act inside "…".
|
|
180
|
+
*/
|
|
181
|
+
const scan = (line) => {
|
|
162
182
|
const found = [];
|
|
183
|
+
const live = new Set();
|
|
163
184
|
let word;
|
|
164
185
|
let quote;
|
|
165
|
-
for (
|
|
166
|
-
|
|
167
|
-
|
|
186
|
+
for (let index = 0; index < line.length; index++) {
|
|
187
|
+
const char = line[index];
|
|
188
|
+
const next = line[index + 1];
|
|
189
|
+
if (quote === "'") {
|
|
190
|
+
if (char === "'")
|
|
168
191
|
quote = undefined;
|
|
169
192
|
else
|
|
170
|
-
word
|
|
193
|
+
word += char;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (char === "\\" && next !== undefined && (quote === undefined || '$`"\\'.includes(next))) {
|
|
197
|
+
word = (word ?? "") + next;
|
|
198
|
+
index++;
|
|
199
|
+
continue;
|
|
200
|
+
}
|
|
201
|
+
if (char === "`" || (char === "$" && next === "("))
|
|
202
|
+
live.add(char === "`" ? "`" : "$(");
|
|
203
|
+
if (quote === '"') {
|
|
204
|
+
if (char === '"')
|
|
205
|
+
quote = undefined;
|
|
206
|
+
else
|
|
207
|
+
word += char;
|
|
171
208
|
}
|
|
172
209
|
else if (char === '"' || char === "'") {
|
|
173
210
|
quote = char;
|
|
@@ -178,12 +215,24 @@ const words = (line) => {
|
|
|
178
215
|
found.push(word);
|
|
179
216
|
word = undefined;
|
|
180
217
|
}
|
|
181
|
-
else
|
|
218
|
+
else {
|
|
219
|
+
if (OPERATORS[char])
|
|
220
|
+
live.add(char);
|
|
182
221
|
word = (word ?? "") + char;
|
|
222
|
+
}
|
|
183
223
|
}
|
|
184
224
|
if (word !== undefined)
|
|
185
225
|
found.push(word);
|
|
186
|
-
return found;
|
|
226
|
+
return { words: found, live: [...live], open: quote !== undefined };
|
|
227
|
+
};
|
|
228
|
+
const words = (line) => scan(line).words;
|
|
229
|
+
/** Characters in a step that a shell would act on: the person who copies it would run something else. */
|
|
230
|
+
export const shellProblems = (run) => {
|
|
231
|
+
const { live, open } = scan(run);
|
|
232
|
+
return [
|
|
233
|
+
...live.map((char) => `a shell acts on ${char}: it ${OPERATORS[char]}; put the word in single quotes`),
|
|
234
|
+
...(open ? ["a quote is never closed"] : []),
|
|
235
|
+
];
|
|
187
236
|
};
|
|
188
237
|
const silent = (command) => command.exitOverride().configureOutput({ writeOut: () => { }, writeErr: () => { }, outputError: () => { } });
|
|
189
238
|
const ownerOf = (chain, flag) => {
|
|
@@ -295,7 +344,7 @@ export const checkGuide = async (build, { packageRoot } = {}) => {
|
|
|
295
344
|
const { scenarios, problems } = await loadScenarios(options.directory);
|
|
296
345
|
for (const scenario of scenarios)
|
|
297
346
|
scenario.steps.forEach((step, index) => {
|
|
298
|
-
for (const problem of checkStep(build(), step.run))
|
|
347
|
+
for (const problem of [...shellProblems(step.run), ...checkStep(build(), step.run)])
|
|
299
348
|
problems.push(`${scenario.name}.md step ${index + 1} (${step.run}): ${problem}`);
|
|
300
349
|
});
|
|
301
350
|
for (const reference of options.worksWith ?? [])
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -56,7 +56,7 @@ export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
|
56
56
|
export { changesAuthority, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
|
|
57
57
|
export { type AnswerOutput, answerSchema, type Body, type ItemBody, itemAnswer, itemOf, jsonlRecords, judgedAnswer, type ListBody, type ListInput, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
58
58
|
export { withAbort } from "./execution.js";
|
|
59
|
-
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, guideCommand, loadScenarios, type Scenario, } from "./guide.js";
|
|
59
|
+
export { checkGuide, checkStep, type GuideOptions, type GuideReference, type GuideStep, guideCommand, loadScenarios, MEMO_SCENARIOS, type Scenario, shellProblems, } from "./guide.js";
|
|
60
60
|
export { type CommandHelp, commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, type NextChoice, nextUsually, } from "./help/command-help.js";
|
|
61
61
|
export { commandsText } from "./help/commands-text.js";
|
|
62
62
|
export { type FirstScreen, type FirstScreenLine, firstScreen, installHelp, WIRECAT_TOOLS, } from "./help/first-screen.js";
|
package/dist/cli/index.js
CHANGED
|
@@ -51,7 +51,7 @@ export { DEFAULT_CONFIG, ensureDefaultConfig } from "./default-config.js";
|
|
|
51
51
|
export { changesAuthority, destroysNothing, FETCH, grantsAuthority, isLogin, isOtherServiceCall, isStoreChange, judgedEffects, judgedTargets, LOCAL_READ, LOCAL_WRITE, logsInOrOut, NO_EFFECTS, ownerOnlyOption, REMOTE_READ, REMOTE_WRITE, reachesOtherService, refuseUnderReadOnly, } from "./effects.js";
|
|
52
52
|
export { answerSchema, itemAnswer, itemOf, jsonlRecords, judgedAnswer, listOf, metaNow, printItem, renderAnswer, sealed, snakeKeys, snakeKeysKeeping, } from "./envelope.js";
|
|
53
53
|
export { withAbort } from "./execution.js";
|
|
54
|
-
export { checkGuide, checkStep, guideCommand, loadScenarios, } from "./guide.js";
|
|
54
|
+
export { checkGuide, checkStep, guideCommand, loadScenarios, MEMO_SCENARIOS, shellProblems, } from "./guide.js";
|
|
55
55
|
export { commandHelp, commandHelpMarkdown, commandHelpText, exampleReport, nextUsually, } from "./help/command-help.js";
|
|
56
56
|
export { commandsText } from "./help/commands-text.js";
|
|
57
57
|
export { firstScreen, installHelp, WIRECAT_TOOLS, } from "./help/first-screen.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wirecat/cli-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.370.0",
|
|
4
4
|
"description": "The messenger-neutral half of a messaging CLI: domain model, message locators, rendering, name resolution, the SQLite seam, the command skeleton and shared read commands, the send guard, run records and the message store",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Viacheslav Ptsarev",
|