@skrr-ai/cli 0.1.10 → 0.1.12
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/base-command.d.ts +1 -13
- package/dist/base-command.js +78 -1
- package/dist/commands/browser/skill/show.js +7 -1
- package/dist/commands/code/index.d.ts +1 -0
- package/dist/commands/code/index.js +9 -1
- package/dist/commands/commitments/analytics/index.js +2 -0
- package/dist/commands/daemon/byok.d.ts +1 -0
- package/dist/commands/daemon/byok.js +2 -1
- package/dist/commands/daemon/install.d.ts +15 -0
- package/dist/commands/daemon/install.js +39 -2
- package/dist/commands/daemon/login.d.ts +31 -0
- package/dist/commands/daemon/login.js +56 -0
- package/dist/commands/daemon/restart.d.ts +9 -0
- package/dist/commands/daemon/restart.js +36 -0
- package/dist/commands/daemon/start.d.ts +1 -0
- package/dist/commands/daemon/start.js +2 -1
- package/dist/commands/daemon/status.d.ts +1 -0
- package/dist/commands/daemon/status.js +2 -1
- package/dist/commands/daemon/stop.d.ts +1 -0
- package/dist/commands/daemon/stop.js +2 -1
- package/dist/commands/daemon/uninstall.d.ts +1 -0
- package/dist/commands/daemon/uninstall.js +2 -1
- package/dist/commands/daemon/unlock.d.ts +9 -0
- package/dist/commands/daemon/unlock.js +33 -0
- package/dist/commands/goals/key-results/create.js +32 -1
- package/dist/commands/goals/key-results/update.d.ts +11 -0
- package/dist/commands/goals/key-results/update.js +80 -2
- package/dist/commands/goals/plan-now.d.ts +54 -2
- package/dist/commands/goals/plan-now.js +175 -18
- package/dist/commands/goals/planner-config.d.ts +60 -9
- package/dist/commands/goals/planner-config.js +82 -34
- package/dist/commands/goals/revisions.js +17 -0
- package/dist/commands/goals/show.d.ts +17 -0
- package/dist/commands/goals/show.js +90 -3
- package/dist/commands/login.js +53 -0
- package/dist/commands/spaces/create.js +2 -1
- package/dist/commands/spaces/index.js +9 -1
- package/dist/commands/spaces/list.d.ts +18 -0
- package/dist/commands/spaces/list.js +57 -7
- package/dist/commands/spaces/show.js +4 -1
- package/dist/commands/spaces/summary.d.ts +4 -0
- package/dist/commands/spaces/summary.js +77 -1
- package/dist/commands/spaces/update.d.ts +4 -0
- package/dist/commands/spaces/update.js +38 -1
- package/dist/commands/tasks/actionability.js +40 -1
- package/dist/commands/tasks/activity.d.ts +29 -0
- package/dist/commands/tasks/activity.js +47 -0
- package/dist/commands/tasks/complete.d.ts +47 -0
- package/dist/commands/tasks/complete.js +159 -12
- package/dist/commands/tasks/create.d.ts +26 -0
- package/dist/commands/tasks/create.js +60 -1
- package/dist/commands/tasks/events/append.d.ts +2 -0
- package/dist/commands/tasks/events/append.js +41 -10
- package/dist/commands/tasks/events/list.js +60 -9
- package/dist/commands/tasks/output.js +22 -2
- package/dist/commands/tasks/ready.d.ts +38 -0
- package/dist/commands/tasks/ready.js +37 -0
- package/dist/commands/tasks/runs.d.ts +22 -0
- package/dist/commands/tasks/runs.js +110 -2
- package/dist/commands/tasks/show.d.ts +28 -0
- package/dist/commands/tasks/show.js +61 -0
- package/dist/commands/tasks/timeline.d.ts +7 -0
- package/dist/commands/tasks/timeline.js +34 -3
- package/dist/commands/tasks/update.d.ts +20 -0
- package/dist/commands/tasks/update.js +38 -1
- package/dist/commands/whoami.d.ts +40 -0
- package/dist/commands/whoami.js +57 -9
- package/dist/commands/wiki/ls.d.ts +23 -0
- package/dist/commands/wiki/ls.js +63 -10
- package/dist/commands/wiki/mv.d.ts +70 -0
- package/dist/commands/wiki/mv.js +198 -4
- package/dist/commands/wiki/rm.js +12 -1
- package/dist/commands/wiki/write.js +35 -3
- package/dist/help.d.ts +27 -0
- package/dist/help.js +48 -0
- package/dist/hooks/command-not-found.d.ts +31 -0
- package/dist/hooks/command-not-found.js +12 -80
- package/dist/lib/command-miss.d.ts +60 -0
- package/dist/lib/command-miss.js +128 -0
- package/dist/lib/commitment-analytics.d.ts +10 -0
- package/dist/lib/commitment-analytics.js +10 -0
- package/dist/lib/config.js +6 -2
- package/dist/lib/daemon-installer.d.ts +62 -0
- package/dist/lib/daemon-installer.js +247 -0
- package/dist/lib/daemon-setup.d.ts +49 -0
- package/dist/lib/daemon-setup.js +103 -0
- package/dist/lib/daemonBroker.d.ts +7 -1
- package/dist/lib/daemonBroker.js +71 -0
- package/dist/lib/daemonHandoff.d.ts +47 -0
- package/dist/lib/daemonHandoff.js +304 -0
- package/dist/lib/exec-oversky.d.ts +30 -0
- package/dist/lib/exec-oversky.js +41 -0
- package/dist/lib/format.d.ts +7 -0
- package/dist/lib/format.js +27 -5
- package/dist/lib/login.js +22 -2
- package/dist/lib/sky-code.js +1 -1
- package/dist/lib/task-transcript.d.ts +9 -0
- package/dist/lib/task-transcript.js +13 -2
- package/dist/lib/tasks.d.ts +34 -0
- package/dist/lib/tasks.js +70 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.d.ts +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/configRoot.js +67 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +21 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseKeys.d.ts +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseKeys.js +94 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.d.ts +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/releaseManifest.js +235 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.d.ts +50 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/configRoot.js +59 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +3 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +10 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseKeys.d.ts +87 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseKeys.js +91 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.d.ts +161 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/releaseManifest.js +227 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +15 -4
- package/oclif.manifest.json +1831 -1598
- package/package.json +5 -3
package/dist/commands/wiki/mv.js
CHANGED
|
@@ -1,10 +1,69 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.destinationConflict = destinationConflict;
|
|
4
|
+
exports.serverRenamedOnMove = serverRenamedOnMove;
|
|
3
5
|
const core_1 = require("@oclif/core");
|
|
4
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
7
|
const base_command_1 = require("../../base-command");
|
|
6
8
|
const http_error_1 = require("../../lib/http-error");
|
|
7
9
|
const wiki_1 = require("../../lib/wiki");
|
|
10
|
+
const write_1 = require("./write");
|
|
11
|
+
/**
|
|
12
|
+
* Why this move must not proceed — or `null` when it may.
|
|
13
|
+
*
|
|
14
|
+
* Pure, so the decision is testable without a server: everything it needs is
|
|
15
|
+
* the occupant, the kinds involved, and whether the caller authorised a
|
|
16
|
+
* replacement. The messages say the word the old output never did — "already
|
|
17
|
+
* exists" — because a human watching the terminal could see the `-2` in the
|
|
18
|
+
* success line and still not read it as a collision.
|
|
19
|
+
*/
|
|
20
|
+
function destinationConflict(opts) {
|
|
21
|
+
const { occupant, sourceKind, source, destination, force, binName, spaceId } = opts;
|
|
22
|
+
if (occupant.kind === 'free' || occupant.kind === 'self')
|
|
23
|
+
return null;
|
|
24
|
+
if (occupant.kind === 'folder') {
|
|
25
|
+
// No `--force` escape here even for a folder source: replacing a folder
|
|
26
|
+
// means deleting a subtree, and `rmdir` is the verb that asks about that.
|
|
27
|
+
return {
|
|
28
|
+
message: `'${destination}' already exists — it is a folder, and \`mv\` will not replace one.\n` +
|
|
29
|
+
`Move '${source}' somewhere else, or remove the folder first with ` +
|
|
30
|
+
`\`${binName} wiki rmdir ${spaceId} ${destination}\`.`,
|
|
31
|
+
code: 'WIKI_DESTINATION_EXISTS',
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (sourceKind === 'folder') {
|
|
35
|
+
return {
|
|
36
|
+
message: `'${destination}' already exists — it is a page, so a folder cannot take that path.\n` +
|
|
37
|
+
`Pick another destination, or remove the page first with ` +
|
|
38
|
+
`\`${binName} wiki rm ${spaceId} ${destination}\`.`,
|
|
39
|
+
code: 'WIKI_DESTINATION_EXISTS',
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
if (!force) {
|
|
43
|
+
return {
|
|
44
|
+
message: `'${destination}' already exists (page ${occupant.pageId}, "${occupant.title}"). ` +
|
|
45
|
+
`Nothing was moved.\n` +
|
|
46
|
+
`Pass --force to replace it with '${source}', or choose a different destination. ` +
|
|
47
|
+
`Without it \`mv\` would land '${source}' beside the page rather than on it, and ` +
|
|
48
|
+
`\`${binName} wiki cat ${spaceId} ${destination}\` would still read the OLD content.`,
|
|
49
|
+
code: 'WIKI_DESTINATION_EXISTS',
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Did the server disambiguate the slug anyway?
|
|
56
|
+
*
|
|
57
|
+
* The pre-flight check above closes the ordinary case, but it cannot close the
|
|
58
|
+
* race — a page can appear on the destination between the check and the move —
|
|
59
|
+
* and an older server has no notion of refusing. `allocateSlug` suffixes (`y` ->
|
|
60
|
+
* `y-2`), which is precisely the outcome that must never read as success, so the
|
|
61
|
+
* post-check reuses `write.ts`'s detector rather than a second copy of the
|
|
62
|
+
* server's slug rules.
|
|
63
|
+
*/
|
|
64
|
+
function serverRenamedOnMove(requestedSlug, movedSlug) {
|
|
65
|
+
return (0, write_1.createdSlugCollided)(requestedSlug, movedSlug);
|
|
66
|
+
}
|
|
8
67
|
/**
|
|
9
68
|
* `skrr wiki mv <space> <source> <destination>` — move or rename a page OR a
|
|
10
69
|
* folder.
|
|
@@ -19,6 +78,16 @@ const wiki_1 = require("../../lib/wiki");
|
|
|
19
78
|
* through `PATCH /pages/:pageId/move` (which creates missing destination
|
|
20
79
|
* folders); folders go through `PATCH /folders/:folder_id`, whose server-side
|
|
21
80
|
* guard refuses to move a folder into its own descendant.
|
|
81
|
+
*
|
|
82
|
+
* OCCUPIED DESTINATIONS ARE REFUSED, not renamed around. The move endpoint runs
|
|
83
|
+
* the same `allocateSlug` de-duplication a CREATE does, so `mv X Y` onto an
|
|
84
|
+
* existing `Y` landed the source at `Y-2`, reported success, and left `Y`
|
|
85
|
+
* holding its OLD content — and `mv X Y; cat Y` then reads the stale page while
|
|
86
|
+
* believing it read the new one. Moving something onto a canonical path is the
|
|
87
|
+
* entire reason to run `mv`, so "I could not do that, so I did something
|
|
88
|
+
* adjacent" is the one outcome this command must not present as success.
|
|
89
|
+
* `--force` gives the filesystem's answer (replace the destination); the
|
|
90
|
+
* default gives the safe one (refuse and change nothing).
|
|
22
91
|
*/
|
|
23
92
|
class WikiMv extends base_command_1.BaseCommand {
|
|
24
93
|
static description = 'Move or rename a wiki page or folder';
|
|
@@ -26,6 +95,7 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
26
95
|
'<%= config.bin %> wiki mv <space-id> draft Engineering/Runbooks/deploy',
|
|
27
96
|
'<%= config.bin %> wiki mv <space-id> Engineering/old-name Engineering/new-name',
|
|
28
97
|
'<%= config.bin %> wiki mv <space-id> Notes Engineering/Notes',
|
|
98
|
+
'<%= config.bin %> wiki mv <space-id> new-runbook Engineering/deploy --force',
|
|
29
99
|
'<%= config.bin %> wiki mv <space-id> Engineering/Runbooks Archive/Runbooks --json',
|
|
30
100
|
];
|
|
31
101
|
static args = {
|
|
@@ -36,13 +106,23 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
36
106
|
ignoreStdin: true,
|
|
37
107
|
}),
|
|
38
108
|
destination: core_1.Args.string({
|
|
39
|
-
description: 'Destination path (missing parent folders are created for pages)'
|
|
109
|
+
description: 'Destination path (missing parent folders are created for pages). ' +
|
|
110
|
+
'An occupied destination is refused unless --force.',
|
|
40
111
|
required: true,
|
|
41
112
|
ignoreStdin: true,
|
|
42
113
|
}),
|
|
43
114
|
};
|
|
44
115
|
static flags = {
|
|
45
116
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
117
|
+
force: core_1.Flags.boolean({
|
|
118
|
+
char: 'f',
|
|
119
|
+
// Named for what it destroys, not for what it skips: unlike `wiki rm`'s
|
|
120
|
+
// --force this suppresses no prompt, it authorises deleting whatever is
|
|
121
|
+
// already on the destination path.
|
|
122
|
+
description: 'Replace an existing page at the destination (deletes it first)',
|
|
123
|
+
aliases: ['yes'],
|
|
124
|
+
charAliases: ['y'],
|
|
125
|
+
}),
|
|
46
126
|
};
|
|
47
127
|
async run() {
|
|
48
128
|
this.requireAuth();
|
|
@@ -79,6 +159,45 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
79
159
|
this.handleApiError(err);
|
|
80
160
|
}
|
|
81
161
|
if (page) {
|
|
162
|
+
const occupant = await this.resolveDestination(args.space, args.destination, {
|
|
163
|
+
pageId: page.pageId,
|
|
164
|
+
});
|
|
165
|
+
const conflict = destinationConflict({
|
|
166
|
+
occupant,
|
|
167
|
+
sourceKind: 'page',
|
|
168
|
+
source,
|
|
169
|
+
destination: args.destination,
|
|
170
|
+
force: flags.force,
|
|
171
|
+
binName: this.config.bin,
|
|
172
|
+
spaceId: args.space,
|
|
173
|
+
});
|
|
174
|
+
if (conflict) {
|
|
175
|
+
this.failWithCliError({ ...conflict, exit: 1, retryable: false });
|
|
176
|
+
}
|
|
177
|
+
// `--force` on an occupied destination: clear the path before moving, so
|
|
178
|
+
// the move lands ON it rather than beside it. Archive, not purge —
|
|
179
|
+
// `wiki rm --purge` is where destroying edit history is asked for
|
|
180
|
+
// explicitly, and it should not be a side effect of a move.
|
|
181
|
+
//
|
|
182
|
+
// The order is forced (moving first would just hit the collision again),
|
|
183
|
+
// so a move that fails after the delete leaves the destination archived
|
|
184
|
+
// and the source where it was. That is recoverable — the row survives —
|
|
185
|
+
// and it is the lesser failure: the alternative is the behaviour this
|
|
186
|
+
// whole guard exists to remove, where both pages end up present and the
|
|
187
|
+
// canonical path quietly holds the wrong one. A server-side atomic
|
|
188
|
+
// replace (`conflict: 'replace'` on the move endpoint, mirroring the one
|
|
189
|
+
// create already takes) is the real fix and is not the CLI's to make.
|
|
190
|
+
if (occupant.kind === 'page') {
|
|
191
|
+
try {
|
|
192
|
+
await data_provider_1.dataService.deleteSpaceWikiPage({
|
|
193
|
+
spaceId: args.space,
|
|
194
|
+
pageId: occupant.pageId,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
catch (err) {
|
|
198
|
+
this.handleApiError(err);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
82
201
|
let moved;
|
|
83
202
|
try {
|
|
84
203
|
moved = await data_provider_1.dataService.moveSpaceWikiPage({
|
|
@@ -90,11 +209,35 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
90
209
|
catch (err) {
|
|
91
210
|
this.handleApiError(err);
|
|
92
211
|
}
|
|
212
|
+
// The check above cannot close the window between it and the move, and an
|
|
213
|
+
// older server refuses nothing. Landing at `y-2` is a FAILED move, so say
|
|
214
|
+
// so rather than printing a success line naming a path nobody asked for.
|
|
215
|
+
if (serverRenamedOnMove(name, moved.slug)) {
|
|
216
|
+
this.failWithCliError({
|
|
217
|
+
message: `'${args.destination}' was already taken, so the server moved '${source}' to ` +
|
|
218
|
+
`'${moved.path ?? moved.slug}' instead. '${args.destination}' still holds its ` +
|
|
219
|
+
`ORIGINAL content — the move you asked for did NOT happen.\n` +
|
|
220
|
+
`Re-run with --force to replace the destination, or move ` +
|
|
221
|
+
`'${moved.path ?? moved.slug}' where you want it.`,
|
|
222
|
+
code: 'WIKI_DESTINATION_EXISTS',
|
|
223
|
+
exit: 1,
|
|
224
|
+
retryable: true,
|
|
225
|
+
details: {
|
|
226
|
+
requestedPath: args.destination,
|
|
227
|
+
requestedSlug: name,
|
|
228
|
+
actualSlug: moved.slug,
|
|
229
|
+
movedPageId: moved.pageId,
|
|
230
|
+
},
|
|
231
|
+
});
|
|
232
|
+
}
|
|
93
233
|
if (flags.json) {
|
|
94
234
|
this.log(JSON.stringify({ kind: 'page', ...moved }, null, 2));
|
|
95
235
|
return;
|
|
96
236
|
}
|
|
97
237
|
this.log(`Moved page ${source} → ${moved.path ?? args.destination}.`);
|
|
238
|
+
if (occupant.kind === 'page') {
|
|
239
|
+
this.log(`Replaced the page that was there ("${occupant.title}", now deleted).`);
|
|
240
|
+
}
|
|
98
241
|
// A rename moved the path but not the title. When the title was never
|
|
99
242
|
// authored (it still equals the old slug), point the user at the one
|
|
100
243
|
// command that finishes the rename rather than leaving them to notice the
|
|
@@ -111,7 +254,43 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
111
254
|
this.log(hint);
|
|
112
255
|
return;
|
|
113
256
|
}
|
|
114
|
-
await this.moveFolder(args.space, source, dir, name, flags
|
|
257
|
+
await this.moveFolder(args.space, source, args.destination, dir, name, flags);
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* What already occupies `destination` — a page, a folder, us, or nothing.
|
|
261
|
+
*
|
|
262
|
+
* A page and a folder cannot share a path, so this asks in the same order the
|
|
263
|
+
* source resolution does. Both lookups treat a 404 as an answer rather than a
|
|
264
|
+
* failure; anything else still surfaces, so an unreadable destination never
|
|
265
|
+
* degrades into "looks free, go ahead and clobber".
|
|
266
|
+
*/
|
|
267
|
+
async resolveDestination(spaceId, destination,
|
|
268
|
+
// The thing being moved. `mv x x` and a rename that changes nothing both
|
|
269
|
+
// resolve the destination to the SOURCE, and refusing those would turn a
|
|
270
|
+
// harmless no-op into an error.
|
|
271
|
+
self = {}) {
|
|
272
|
+
try {
|
|
273
|
+
const existing = await data_provider_1.dataService.getSpaceWikiPageByPath(spaceId, destination);
|
|
274
|
+
if (existing) {
|
|
275
|
+
if (self.pageId && existing.pageId === self.pageId)
|
|
276
|
+
return { kind: 'self' };
|
|
277
|
+
return { kind: 'page', pageId: existing.pageId, title: existing.title };
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
catch (err) {
|
|
281
|
+
if (!(0, http_error_1.isHttpNotFound)(err))
|
|
282
|
+
this.handleApiError(err);
|
|
283
|
+
}
|
|
284
|
+
let folderId = null;
|
|
285
|
+
try {
|
|
286
|
+
folderId = await (0, wiki_1.resolveWikiFolderId)(spaceId, destination, data_provider_1.dataService.listSpaceWikiDirectory, http_error_1.isHttpNotFound);
|
|
287
|
+
}
|
|
288
|
+
catch (err) {
|
|
289
|
+
this.handleApiError(err);
|
|
290
|
+
}
|
|
291
|
+
if (!folderId)
|
|
292
|
+
return { kind: 'free' };
|
|
293
|
+
return folderId === self.folderId ? { kind: 'self' } : { kind: 'folder' };
|
|
115
294
|
}
|
|
116
295
|
/**
|
|
117
296
|
* Reparent and/or rename a folder.
|
|
@@ -121,7 +300,7 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
121
300
|
* would mean a typo in the destination silently builds a new tree instead of
|
|
122
301
|
* failing — `mkdir -p` is a separate decision the caller should make.
|
|
123
302
|
*/
|
|
124
|
-
async moveFolder(spaceId, source, destinationDir, name,
|
|
303
|
+
async moveFolder(spaceId, source, destination, destinationDir, name, flags) {
|
|
125
304
|
let folderId;
|
|
126
305
|
try {
|
|
127
306
|
folderId = await (0, wiki_1.resolveWikiFolderId)(spaceId, source, data_provider_1.dataService.listSpaceWikiDirectory, http_error_1.isHttpNotFound);
|
|
@@ -157,6 +336,21 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
157
336
|
});
|
|
158
337
|
}
|
|
159
338
|
}
|
|
339
|
+
// Same refusal the page branch makes, for the same reason: a folder that
|
|
340
|
+
// silently lands beside its destination instead of on it is a move that
|
|
341
|
+
// reports success without happening.
|
|
342
|
+
const conflict = destinationConflict({
|
|
343
|
+
occupant: await this.resolveDestination(spaceId, destination, { folderId }),
|
|
344
|
+
sourceKind: 'folder',
|
|
345
|
+
source,
|
|
346
|
+
destination,
|
|
347
|
+
force: flags.force,
|
|
348
|
+
binName: this.config.bin,
|
|
349
|
+
spaceId,
|
|
350
|
+
});
|
|
351
|
+
if (conflict) {
|
|
352
|
+
this.failWithCliError({ ...conflict, exit: 1, retryable: false });
|
|
353
|
+
}
|
|
160
354
|
let updated;
|
|
161
355
|
try {
|
|
162
356
|
updated = await data_provider_1.dataService.updateSpaceWikiFolder({
|
|
@@ -168,7 +362,7 @@ class WikiMv extends base_command_1.BaseCommand {
|
|
|
168
362
|
catch (err) {
|
|
169
363
|
this.handleApiError(err);
|
|
170
364
|
}
|
|
171
|
-
if (json) {
|
|
365
|
+
if (flags.json) {
|
|
172
366
|
this.log(JSON.stringify({ kind: 'folder', ...updated }, null, 2));
|
|
173
367
|
return;
|
|
174
368
|
}
|
package/dist/commands/wiki/rm.js
CHANGED
|
@@ -90,7 +90,18 @@ class WikiRm extends base_command_1.BaseCommand {
|
|
|
90
90
|
this.log(JSON.stringify(result, null, 2));
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
|
|
93
|
+
if (flags.purge) {
|
|
94
|
+
this.log(`Purged ${args.path}.`);
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
// The page id is the ONLY handle left that reaches this content, and it was
|
|
98
|
+
// the one thing not printed. `rm` archives rather than destroys — the help
|
|
99
|
+
// says so — and `wiki cat --page-id <id>` still returns the body. But `ls`
|
|
100
|
+
// drops the page and `rm` said only "Deleted <path>.", so the handle was
|
|
101
|
+
// discarded at exactly the moment it became the sole way back. An honest
|
|
102
|
+
// "archived, not destroyed" then behaves like "destroyed".
|
|
103
|
+
this.log(`Deleted ${args.path} (${page.pageId}).`);
|
|
104
|
+
this.log(` Archived, not destroyed — read it with \`${this.config.bin} wiki cat ${args.space} --page-id ${page.pageId}\`.`);
|
|
94
105
|
}
|
|
95
106
|
}
|
|
96
107
|
exports.default = WikiRm;
|
|
@@ -94,9 +94,25 @@ class WikiWrite extends base_command_1.BaseCommand {
|
|
|
94
94
|
// one place a CLI user (or an agent with no docs) looks before writing, and
|
|
95
95
|
// `__x__` silently staying literal is exactly the kind of surprise that has to
|
|
96
96
|
// reach them there. (OSK-4844)
|
|
97
|
+
//
|
|
98
|
+
// It has to be SELF-CONTAINED, which the first cut was not: it named the cost
|
|
99
|
+
// of the escape rule ("a backslash before punctuation escapes it") and then
|
|
100
|
+
// pointed at `docs/cli/SKRR_WIKI.md` for what that actually costs you — a path
|
|
101
|
+
// inside the git checkout, which someone who installed `skrr` from npm does
|
|
102
|
+
// not have, with no URL and no `skrr docs` to reach it by. So the CLI's own
|
|
103
|
+
// answer to "what does that mean for my regex?" was a file the reader cannot
|
|
104
|
+
// open. It is two sentences; they belong here.
|
|
105
|
+
//
|
|
106
|
+
// Stated as CONSUMED rather than as "escaped", because the wiki stores
|
|
107
|
+
// rendered HTML: those bytes are gone on the FIRST write, not merely hidden
|
|
108
|
+
// on read, and no `wiki cat --html` gets them back.
|
|
97
109
|
static description = 'Write a wiki page, creating it (and any missing folders) if needed. ' +
|
|
98
|
-
'Markdown by default
|
|
99
|
-
'backslash before punctuation
|
|
110
|
+
'Markdown by default: `__x__` stays literal (use `**x**` for bold), and a ' +
|
|
111
|
+
'backslash before ASCII punctuation is an escape, so the backslash itself ' +
|
|
112
|
+
'is CONSUMED and not stored — `\\d+\\.\\d+` becomes `\\d+.\\d+`. Double it ' +
|
|
113
|
+
'(`\\\\.`) to keep a literal backslash, or put the text in a code span or ' +
|
|
114
|
+
'fence, where nothing is interpreted. A backslash before anything else ' +
|
|
115
|
+
'(a letter, a space) is kept as written.';
|
|
100
116
|
static examples = [
|
|
101
117
|
'<%= config.bin %> wiki write <space-id> Engineering/deploy --file runbook.md',
|
|
102
118
|
'cat notes.md | <%= config.bin %> wiki write <space-id> notes --stdin',
|
|
@@ -113,7 +129,9 @@ class WikiWrite extends base_command_1.BaseCommand {
|
|
|
113
129
|
stdin: core_1.Flags.boolean({ description: 'Read content from stdin' }),
|
|
114
130
|
html: core_1.Flags.boolean({ description: 'Treat the input as HTML instead of Markdown' }),
|
|
115
131
|
append: core_1.Flags.boolean({ description: 'Append instead of replacing (>> rather than >)' }),
|
|
116
|
-
title: core_1.Flags.string({
|
|
132
|
+
title: core_1.Flags.string({
|
|
133
|
+
description: 'Title for a NEWLY-CREATED page (defaults to the slug). Retitle an existing page with `wiki update --title`',
|
|
134
|
+
}),
|
|
117
135
|
};
|
|
118
136
|
async run() {
|
|
119
137
|
this.requireAuth();
|
|
@@ -216,6 +234,20 @@ class WikiWrite extends base_command_1.BaseCommand {
|
|
|
216
234
|
},
|
|
217
235
|
});
|
|
218
236
|
}
|
|
237
|
+
// `--title` is create-only, which the help now says — but the command still
|
|
238
|
+
// ACCEPTED it, reported success, and changed nothing about the one thing
|
|
239
|
+
// the flag names. `wiki update`'s own help already argues this case: it
|
|
240
|
+
// refuses an all-empty patch precisely so "a mistyped flag name [does not]
|
|
241
|
+
// look like success". Same reasoning, and the write itself succeeded, so
|
|
242
|
+
// this is a warning naming the command that does the job rather than a
|
|
243
|
+
// failure. To stderr, so `--json` stays a clean contract — and BEFORE the
|
|
244
|
+
// `--json` early return, or the caller most likely to be scripting this is
|
|
245
|
+
// the one caller who never hears it.
|
|
246
|
+
if (wroteIntoExisting && flags.title !== undefined && flags.title !== page.title) {
|
|
247
|
+
this.warn(`--title was ignored: '${page.path ?? args.path}' already exists, and \`wiki write\` only titles pages ` +
|
|
248
|
+
`it creates. Retitle it with \`${this.config.bin} wiki update ${args.space} ${page.path ?? args.path} ` +
|
|
249
|
+
`--title ${JSON.stringify(flags.title)}\` (the slug is kept, so stored paths stay valid).`);
|
|
250
|
+
}
|
|
219
251
|
if (flags.json) {
|
|
220
252
|
this.log(JSON.stringify(page, null, 2));
|
|
221
253
|
return;
|
package/dist/help.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { Help } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* Make `--help` on a command that does not exist say the same thing running it
|
|
4
|
+
* says.
|
|
5
|
+
*
|
|
6
|
+
* oclif's help command resolves the id itself and never reaches the
|
|
7
|
+
* `command_not_found` hook, so the two paths answered differently:
|
|
8
|
+
*
|
|
9
|
+
* $ skrr daemon reload
|
|
10
|
+
* Error: `skrr daemon` exists, but does not take the positional
|
|
11
|
+
* argument `reload`.
|
|
12
|
+
* See `skrr daemon --help`.
|
|
13
|
+
*
|
|
14
|
+
* $ skrr daemon reload --help
|
|
15
|
+
* › Error: Command daemon:reload not found.
|
|
16
|
+
*
|
|
17
|
+
* The second names an internal colon-form id the user never typed and cannot
|
|
18
|
+
* type, and offers no next step — and it is the one a reader exploring the CLI
|
|
19
|
+
* hits FIRST, because asking for help on a guessed subcommand is what people do
|
|
20
|
+
* before running it. The audience most likely to need the good message was the
|
|
21
|
+
* audience reliably denied it.
|
|
22
|
+
*
|
|
23
|
+
* Only the not-found path is overridden. Real help output is untouched.
|
|
24
|
+
*/
|
|
25
|
+
export default class SkrrHelp extends Help {
|
|
26
|
+
showHelp(argv: string[]): Promise<void>;
|
|
27
|
+
}
|
package/dist/help.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const command_miss_1 = require("./lib/command-miss");
|
|
5
|
+
/**
|
|
6
|
+
* Make `--help` on a command that does not exist say the same thing running it
|
|
7
|
+
* says.
|
|
8
|
+
*
|
|
9
|
+
* oclif's help command resolves the id itself and never reaches the
|
|
10
|
+
* `command_not_found` hook, so the two paths answered differently:
|
|
11
|
+
*
|
|
12
|
+
* $ skrr daemon reload
|
|
13
|
+
* Error: `skrr daemon` exists, but does not take the positional
|
|
14
|
+
* argument `reload`.
|
|
15
|
+
* See `skrr daemon --help`.
|
|
16
|
+
*
|
|
17
|
+
* $ skrr daemon reload --help
|
|
18
|
+
* › Error: Command daemon:reload not found.
|
|
19
|
+
*
|
|
20
|
+
* The second names an internal colon-form id the user never typed and cannot
|
|
21
|
+
* type, and offers no next step — and it is the one a reader exploring the CLI
|
|
22
|
+
* hits FIRST, because asking for help on a guessed subcommand is what people do
|
|
23
|
+
* before running it. The audience most likely to need the good message was the
|
|
24
|
+
* audience reliably denied it.
|
|
25
|
+
*
|
|
26
|
+
* Only the not-found path is overridden. Real help output is untouched.
|
|
27
|
+
*/
|
|
28
|
+
class SkrrHelp extends core_1.Help {
|
|
29
|
+
async showHelp(argv) {
|
|
30
|
+
// Mirror oclif's own id resolution: the topic separator is a space here, so
|
|
31
|
+
// the id is the leading non-flag tokens joined with `:`.
|
|
32
|
+
const tokens = [];
|
|
33
|
+
for (const token of argv) {
|
|
34
|
+
if (token.startsWith('-'))
|
|
35
|
+
break;
|
|
36
|
+
tokens.push(token);
|
|
37
|
+
}
|
|
38
|
+
const id = tokens.join(':');
|
|
39
|
+
// A bare topic (`skrr daemon`) has no command but DOES have help to show,
|
|
40
|
+
// so it must fall through — only an id that is neither a command nor a
|
|
41
|
+
// topic is a miss.
|
|
42
|
+
if (id && !this.config.findCommand(id) && !this.config.findTopic(id)) {
|
|
43
|
+
throw new Error((0, command_miss_1.describeCommandMiss)((0, command_miss_1.lookupFromConfig)(this.config), id));
|
|
44
|
+
}
|
|
45
|
+
return super.showHelp(argv);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
exports.default = SkrrHelp;
|
|
@@ -1,3 +1,34 @@
|
|
|
1
1
|
import { Hook } from '@oclif/core';
|
|
2
|
+
/**
|
|
3
|
+
* Turn "command not found" back into the truth when the command DOES exist.
|
|
4
|
+
*
|
|
5
|
+
* ── The failure ───────────────────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* oclif resolves a command by joining argv with `:`, so an unexpected
|
|
8
|
+
* positional becomes part of the command id:
|
|
9
|
+
*
|
|
10
|
+
* $ skrr shortcuts list 4a167054-e70e-4b1c-ab43-88de52d3d4a2
|
|
11
|
+
* › Error: command shortcuts:list:4a167054-… not found
|
|
12
|
+
*
|
|
13
|
+
* `skrr shortcuts list` exists and is correct — only the argument form was
|
|
14
|
+
* wrong; it wants `--space`. But the message says the COMMAND is missing, which
|
|
15
|
+
* is the one conclusion that sends a reader looking in the wrong place. I hit
|
|
16
|
+
* the identical shape on `skrr commitments archive <id>` and briefly believed
|
|
17
|
+
* the subcommand had been removed.
|
|
18
|
+
*
|
|
19
|
+
* It is easy to hit because the CLI has no rule about how a space is passed:
|
|
20
|
+
* 65 commands take it as a positional and 61 as `--space`, and `projects
|
|
21
|
+
* create <SPACE>` sits beside `projects archive --space`. Picking one is a
|
|
22
|
+
* separate, larger change (OSK-4527). This makes guessing wrong cheap either
|
|
23
|
+
* way, which is worth having even after that lands.
|
|
24
|
+
*
|
|
25
|
+
* ── What it does ──────────────────────────────────────────────────────────
|
|
26
|
+
*
|
|
27
|
+
* Walks the id's prefixes longest-first. If one resolves to a real command, the
|
|
28
|
+
* remaining tokens are arguments that command did not want, and the message
|
|
29
|
+
* says so — naming the command, the leftover tokens, and the flag to try when
|
|
30
|
+
* the command has an obvious one. Otherwise it falls through to oclif's own
|
|
31
|
+
* not-found behaviour, because then the command really is missing.
|
|
32
|
+
*/
|
|
2
33
|
declare const hook: Hook<'command_not_found'>;
|
|
3
34
|
export default hook;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
const command_manifest_1 = require("../lib/command-manifest");
|
|
4
4
|
const usage_discovery_1 = require("../lib/usage-discovery");
|
|
5
|
+
const command_miss_1 = require("../lib/command-miss");
|
|
5
6
|
/**
|
|
6
7
|
* Turn "command not found" back into the truth when the command DOES exist.
|
|
7
8
|
*
|
|
@@ -33,8 +34,6 @@ const usage_discovery_1 = require("../lib/usage-discovery");
|
|
|
33
34
|
* the command has an obvious one. Otherwise it falls through to oclif's own
|
|
34
35
|
* not-found behaviour, because then the command really is missing.
|
|
35
36
|
*/
|
|
36
|
-
/** A uuid-ish token, which is what a stray space id looks like. */
|
|
37
|
-
const LOOKS_LIKE_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
38
37
|
const hook = async function (opts) {
|
|
39
38
|
const id = String(opts.id ?? '');
|
|
40
39
|
const parts = id.split(':').filter(Boolean);
|
|
@@ -66,86 +65,19 @@ const hook = async function (opts) {
|
|
|
66
65
|
throw err;
|
|
67
66
|
}
|
|
68
67
|
}
|
|
69
|
-
//
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
continue;
|
|
75
|
-
const extras = parts.slice(take);
|
|
76
|
-
const spoken = `${this.config.bin} ${parts.slice(0, take).join(' ')}`;
|
|
77
|
-
// Suggest the flag only when the command actually has it, so the hint can
|
|
78
|
-
// never point at something that does not exist.
|
|
79
|
-
const flags = Object.keys(found.flags ?? {});
|
|
80
|
-
const hint = extras.some((e) => LOOKS_LIKE_ID.test(e)) && flags.includes('space')
|
|
81
|
-
? ` Pass it as \`--space ${extras[0]}\`.`
|
|
82
|
-
: flags.includes('space')
|
|
83
|
-
? ' It takes `--space <id>`.'
|
|
84
|
-
: '';
|
|
85
|
-
const plural = extras.length === 1 ? 'argument' : 'arguments';
|
|
86
|
-
throw new Error(`\`${spoken}\` exists, but does not take the positional ${plural} ` +
|
|
87
|
-
`${extras.map((e) => `\`${e}\``).join(' ')}.${hint}\n` +
|
|
88
|
-
`See \`${spoken} --help\`.`);
|
|
89
|
-
}
|
|
90
|
-
// No prefix resolved — the command genuinely is not there.
|
|
68
|
+
// The message itself lives in `lib/command-miss`, shared with the help
|
|
69
|
+
// class — `skrr daemon reload` reaches this hook and `skrr daemon reload
|
|
70
|
+
// --help` does not, so the two surfaces have to read from one description or
|
|
71
|
+
// they drift, which is exactly how the `--help` path ended up with oclif's
|
|
72
|
+
// raw boilerplate while this path had a good message.
|
|
91
73
|
//
|
|
92
|
-
// This
|
|
93
|
-
//
|
|
94
|
-
//
|
|
95
|
-
//
|
|
96
|
-
//
|
|
97
|
-
|
|
98
|
-
// Topic names are candidates too, so a near-miss on a bare topic gets routed
|
|
99
|
-
// to the topic rather than nothing (OSK-4869): `skrr harness` (singular) is
|
|
100
|
-
// one edit from the `harnesses` topic, but that topic owns no command whose
|
|
101
|
-
// id is close to `harness`, so a command-id-only search suggests nothing.
|
|
102
|
-
const topics = this.config.topics;
|
|
103
|
-
const topicNames = Array.isArray(topics)
|
|
104
|
-
? topics.map((t) => String(t?.name ?? '')).filter(Boolean)
|
|
105
|
-
: [];
|
|
106
|
-
// Prefer siblings under the same topic. `commitments:archive:<id>` is a long
|
|
107
|
-
// way from every real command id, so a whole-string comparison finds nothing
|
|
108
|
-
// and the reader is told only that they are wrong — when the useful answer is
|
|
109
|
-
// "that topic exists and here is what it does have".
|
|
110
|
-
const topic = parts[0];
|
|
111
|
-
const siblings = allIds.filter((c) => c.startsWith(`${topic}:`));
|
|
112
|
-
const suggestions = siblings.length
|
|
113
|
-
? nearestCommands(parts.slice(0, 2).join(':'), siblings)
|
|
114
|
-
: nearestCommands(id, [...allIds, ...topicNames]);
|
|
115
|
-
throw new Error(`\`${this.config.bin} ${parts.join(' ')}\` is not a command.` +
|
|
116
|
-
(suggestions.length
|
|
117
|
-
? `\nDid you mean: ${suggestions.map((s) => `\`${this.config.bin} ${s.split(':').join(' ')}\``).join(', ')}?`
|
|
118
|
-
: `\nRun \`${this.config.bin} --help\` to see the topics.`));
|
|
74
|
+
// This MUST throw. A `command_not_found` hook that returns without throwing
|
|
75
|
+
// is treated as having handled the miss, so oclif prints nothing and exits 0
|
|
76
|
+
// — a typo would silently look like success, which is far worse than the
|
|
77
|
+
// message this hook was written to improve. (I shipped exactly that for one
|
|
78
|
+
// build; `skrr shortcuts lst` said nothing and exited 0.)
|
|
79
|
+
throw new Error((0, command_miss_1.describeCommandMiss)((0, command_miss_1.lookupFromConfig)(this.config), id));
|
|
119
80
|
};
|
|
120
|
-
/** Cheap edit distance, bounded — this runs only on a miss. */
|
|
121
|
-
function editDistance(a, b) {
|
|
122
|
-
const rows = a.length + 1;
|
|
123
|
-
const cols = b.length + 1;
|
|
124
|
-
let prev = Array.from({ length: cols }, (_, j) => j);
|
|
125
|
-
for (let i = 1; i < rows; i += 1) {
|
|
126
|
-
const cur = [i, ...Array(cols - 1).fill(0)];
|
|
127
|
-
for (let j = 1; j < cols; j += 1) {
|
|
128
|
-
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
129
|
-
}
|
|
130
|
-
prev = cur;
|
|
131
|
-
}
|
|
132
|
-
return prev[cols - 1];
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* The closest real command ids, so a typo gets a route forward rather than
|
|
136
|
-
* only a verdict.
|
|
137
|
-
*/
|
|
138
|
-
function nearestCommands(id, all, limit = 3) {
|
|
139
|
-
return (all
|
|
140
|
-
.map((candidate) => ({ candidate, d: editDistance(id, candidate) }))
|
|
141
|
-
// A third of the length keeps this to genuine near-misses; without a bound
|
|
142
|
-
// every miss suggests three unrelated commands, which is noise wearing the
|
|
143
|
-
// shape of help.
|
|
144
|
-
.filter(({ d }) => d <= Math.max(2, Math.floor(id.length / 3)))
|
|
145
|
-
.sort((x, y) => x.d - y.d)
|
|
146
|
-
.slice(0, limit)
|
|
147
|
-
.map(({ candidate }) => candidate));
|
|
148
|
-
}
|
|
149
81
|
exports.default = hook;
|
|
150
82
|
class VirtualUsageFlagError extends Error {
|
|
151
83
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What to say when a command id does not resolve — ONE description, shared by
|
|
3
|
+
* every surface that can produce a miss.
|
|
4
|
+
*
|
|
5
|
+
* There are two such surfaces, and they used to disagree. `skrr daemon reload`
|
|
6
|
+
* goes through the `command_not_found` hook and gets a message naming the real
|
|
7
|
+
* command, the mistake, and the next step. `skrr daemon reload --help` goes
|
|
8
|
+
* through oclif's HELP command, which does its own lookup and never reaches
|
|
9
|
+
* that hook — so it answered `Command daemon:reload not found.`: raw
|
|
10
|
+
* boilerplate naming an internal colon-form id the user never typed and cannot
|
|
11
|
+
* type, with no route forward.
|
|
12
|
+
*
|
|
13
|
+
* The audience is what makes that the wrong way round. Someone exploring a
|
|
14
|
+
* service-manager CLI types `--help` on a guessed subcommand FIRST. So the
|
|
15
|
+
* readers most likely to hit a miss were exactly the readers the good message
|
|
16
|
+
* was written for, and they systematically got the bad one.
|
|
17
|
+
*
|
|
18
|
+
* Keeping the text here rather than in either caller is the point: a fix to one
|
|
19
|
+
* phrasing reaches both, and neither surface can quietly drift from the other.
|
|
20
|
+
*/
|
|
21
|
+
/** Minimal view of the oclif config this needs — keeps the module testable. */
|
|
22
|
+
export type CommandLookup = {
|
|
23
|
+
bin: string;
|
|
24
|
+
findCommand: (id: string) => {
|
|
25
|
+
flags?: Record<string, unknown>;
|
|
26
|
+
} | undefined;
|
|
27
|
+
commandIDs: string[];
|
|
28
|
+
topicNames: string[];
|
|
29
|
+
};
|
|
30
|
+
/** Cheap edit distance, bounded — this runs only on a miss. */
|
|
31
|
+
export declare function editDistance(a: string, b: string): number;
|
|
32
|
+
/**
|
|
33
|
+
* The closest real command ids, so a typo gets a route forward rather than
|
|
34
|
+
* only a verdict.
|
|
35
|
+
*/
|
|
36
|
+
export declare function nearestCommands(id: string, all: string[], limit?: number): string[];
|
|
37
|
+
/**
|
|
38
|
+
* The message for a colon-joined id that did not resolve.
|
|
39
|
+
*
|
|
40
|
+
* Two shapes, in order:
|
|
41
|
+
*
|
|
42
|
+
* 1. A PREFIX of the id is a real command, so the extra tokens are arguments
|
|
43
|
+
* it did not want. Longest prefix first, because `a:b:c` should report
|
|
44
|
+
* `a b`, not `a`.
|
|
45
|
+
* 2. Nothing resolved, so the command genuinely is not there — say so, and
|
|
46
|
+
* suggest the nearest siblings under the same topic before falling back to
|
|
47
|
+
* the whole command list.
|
|
48
|
+
*/
|
|
49
|
+
export declare function describeCommandMiss(config: CommandLookup, id: string): string;
|
|
50
|
+
/** Adapt an oclif `Config` to the lookup shape above. */
|
|
51
|
+
export declare function lookupFromConfig(config: {
|
|
52
|
+
bin: string;
|
|
53
|
+
findCommand: (id: string) => unknown;
|
|
54
|
+
commandIDs: Array<string | {
|
|
55
|
+
toString(): string;
|
|
56
|
+
}>;
|
|
57
|
+
topics?: Array<{
|
|
58
|
+
name?: string;
|
|
59
|
+
}>;
|
|
60
|
+
}): CommandLookup;
|