@skrr-ai/cli 0.1.10 → 0.1.11
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 +1 -0
- package/dist/commands/daemon/install.js +2 -1
- 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 +30 -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/daemonBroker.d.ts +7 -1
- package/dist/lib/daemonBroker.js +71 -0
- package/dist/lib/daemonHandoff.d.ts +38 -0
- package/dist/lib/daemonHandoff.js +297 -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 +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +7 -1
- 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 +1 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +3 -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 +15771 -15538
- package/package.json +5 -3
|
@@ -7,6 +7,7 @@ const web_url_1 = require("../../lib/web-url");
|
|
|
7
7
|
const prompt_1 = require("../../lib/prompt");
|
|
8
8
|
const task_resolver_1 = require("../../lib/task-resolver");
|
|
9
9
|
const tasks_1 = require("../../lib/tasks");
|
|
10
|
+
const create_1 = require("./create");
|
|
10
11
|
class TasksUpdate extends base_command_1.BaseCommand {
|
|
11
12
|
static description = 'Update an existing task (partial patch). Status and PROJECT changes go through ' +
|
|
12
13
|
'`tasks move` — see `--goal` / `--space` below.';
|
|
@@ -51,7 +52,34 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
51
52
|
}),
|
|
52
53
|
'start-date': core_1.Flags.string({ description: 'New planned start date (ISO string)' }),
|
|
53
54
|
'due-date': core_1.Flags.string({ description: 'New due date (ISO string)' }),
|
|
54
|
-
estimate: core_1.Flags.integer({
|
|
55
|
+
estimate: core_1.Flags.integer({
|
|
56
|
+
description: 'New estimate value from the Space estimate scale. Requires a scale on the Space (or an ' +
|
|
57
|
+
'ancestor it inherits from); with none set, the server drops the value. Set the scale ' +
|
|
58
|
+
'in the web UI — no CLI command configures it.',
|
|
59
|
+
}),
|
|
60
|
+
/**
|
|
61
|
+
* Declared, hidden, and refused — on purpose. (S4 dogfood finding)
|
|
62
|
+
*
|
|
63
|
+
* `--status` is the single most likely flag a person types at `tasks
|
|
64
|
+
* update`, and undeclared it produced oclif's `Nonexistent flag: --status`
|
|
65
|
+
* followed by a ~160-line dump of this command's help. The answer is in
|
|
66
|
+
* that dump — this command's own description says status changes go through
|
|
67
|
+
* `tasks move` — buried under thirty other flags.
|
|
68
|
+
*
|
|
69
|
+
* Declaring it routes `--status` past the parser and into
|
|
70
|
+
* `taskUpdateMutationGuard`, which already refuses a `status` key arriving
|
|
71
|
+
* via `--from-json` with the exact sentence naming `tasks move`. So the two
|
|
72
|
+
* ways of asking for the same wrong thing now get the same right answer,
|
|
73
|
+
* the same `TASK_STATUS_REQUIRES_MOVE` code in `--json`, and no help dump —
|
|
74
|
+
* and the copy has ONE home rather than a second parallel string.
|
|
75
|
+
*
|
|
76
|
+
* `hidden` because it is not a capability: help must not advertise a flag
|
|
77
|
+
* whose only behaviour is to explain itself.
|
|
78
|
+
*/
|
|
79
|
+
status: core_1.Flags.string({
|
|
80
|
+
hidden: true,
|
|
81
|
+
description: 'Not supported here — status changes go through `tasks move`.',
|
|
82
|
+
}),
|
|
55
83
|
'duplicate-of': core_1.Flags.string({
|
|
56
84
|
description: 'Mark this task as a duplicate of the canonical task (merges collaboration)',
|
|
57
85
|
}),
|
|
@@ -178,6 +206,10 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
178
206
|
startDate: flags['start-date'],
|
|
179
207
|
dueDate: flags['due-date'],
|
|
180
208
|
estimate: flags.estimate,
|
|
209
|
+
// Never sent: `taskUpdateMutationGuard` refuses any payload carrying
|
|
210
|
+
// `status` before the request is built. It is merged in only so the guard
|
|
211
|
+
// sees it and answers with the sentence that names `tasks move`.
|
|
212
|
+
status: flags.status,
|
|
181
213
|
duplicateOfId: flags['duplicate-of'],
|
|
182
214
|
goalId: flags.goal,
|
|
183
215
|
cycleId: flags.cycle,
|
|
@@ -258,6 +290,11 @@ class TasksUpdate extends base_command_1.BaseCommand {
|
|
|
258
290
|
catch (err) {
|
|
259
291
|
this.handleApiError(err);
|
|
260
292
|
}
|
|
293
|
+
// Postcondition, not a prediction — see `droppedEstimateWarning`. On stderr
|
|
294
|
+
// so `--json` stdout stays a parseable task row.
|
|
295
|
+
const estimateWarning = (0, create_1.droppedEstimateWarning)(flags.estimate, updated, updated?.spaceId);
|
|
296
|
+
if (estimateWarning)
|
|
297
|
+
this.warn(estimateWarning);
|
|
261
298
|
if (flags.json) {
|
|
262
299
|
this.log(JSON.stringify((0, web_url_1.withTaskUrl)(this.cliConfig.baseURL, updated), null, 2));
|
|
263
300
|
return;
|
|
@@ -13,3 +13,43 @@ export default class Whoami extends BaseCommand {
|
|
|
13
13
|
};
|
|
14
14
|
run(): Promise<void>;
|
|
15
15
|
}
|
|
16
|
+
export type WhoamiWorkspace = {
|
|
17
|
+
id?: string;
|
|
18
|
+
name?: string;
|
|
19
|
+
source: 'api' | 'selection';
|
|
20
|
+
} | null;
|
|
21
|
+
/**
|
|
22
|
+
* What the `Workspace` row says — including when there isn't one. (OSK-322)
|
|
23
|
+
*
|
|
24
|
+
* `whoami` printed a bare `-` here, and a dash is read as "you have none".
|
|
25
|
+
* That is the wrong conclusion: an unset workspace does not mean requests go
|
|
26
|
+
* nowhere, it means they resolve to the account's default. `skrr context`
|
|
27
|
+
* already words the same field correctly, so the sentence below is deliberately
|
|
28
|
+
* ITS sentence, not a second one — the two commands answer the same question
|
|
29
|
+
* and must not answer it differently.
|
|
30
|
+
*
|
|
31
|
+
* (It is duplicated rather than shared because the wording lives inline in
|
|
32
|
+
* `commands/context.ts`; the right home for both is `lib/`, which is a wider
|
|
33
|
+
* change than this fix.)
|
|
34
|
+
*/
|
|
35
|
+
export declare function describeWhoamiWorkspace(workspace: WhoamiWorkspace): string;
|
|
36
|
+
/**
|
|
37
|
+
* What the `Daemon ID` row says — including when there isn't one. (OSK-322)
|
|
38
|
+
*
|
|
39
|
+
* The field was ambiguous between two different questions, and the dogfood
|
|
40
|
+
* report shows the damage: `whoami` printed `Daemon ID: -` on a machine whose
|
|
41
|
+
* daemon `skrr daemon status` reported as bound, live and cloud-connected, so
|
|
42
|
+
* the reader concluded they had no daemon.
|
|
43
|
+
*
|
|
44
|
+
* The decision, stated here once: **`daemonId` is the daemon THIS CREDENTIAL
|
|
45
|
+
* represents, never the daemon running on this machine.** `whoami` is a
|
|
46
|
+
* credential report — every other row (token source, kind, expiry, refresh,
|
|
47
|
+
* at-rest encryption) describes the credential — so this row has to as well.
|
|
48
|
+
* A CLI credential authenticates a person, so the answer is always "none", and
|
|
49
|
+
* that is a fact rather than a missing lookup.
|
|
50
|
+
*
|
|
51
|
+
* The machine's daemon is a genuinely different subject with its own command,
|
|
52
|
+
* so the row names it instead of leaving the reader to guess which reading
|
|
53
|
+
* applies.
|
|
54
|
+
*/
|
|
55
|
+
export declare function describeWhoamiDaemon(daemonId: string | null): string;
|
package/dist/commands/whoami.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.describeWhoamiWorkspace = describeWhoamiWorkspace;
|
|
4
|
+
exports.describeWhoamiDaemon = describeWhoamiDaemon;
|
|
3
5
|
const core_1 = require("@oclif/core");
|
|
4
6
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
7
|
const auth_core_1 = require("@skrr-ai/auth-core");
|
|
@@ -82,6 +84,9 @@ class Whoami extends base_command_1.BaseCommand {
|
|
|
82
84
|
: selectedId
|
|
83
85
|
? { id: selectedId, source: 'selection' }
|
|
84
86
|
: null;
|
|
87
|
+
// Always null here, and that is the ANSWER rather than a gap: see
|
|
88
|
+
// `describeWhoamiDaemon` for what this field means and why a CLI
|
|
89
|
+
// credential can never carry one.
|
|
85
90
|
const daemonId = null;
|
|
86
91
|
const payload = {
|
|
87
92
|
account: {
|
|
@@ -121,15 +126,10 @@ class Whoami extends base_command_1.BaseCommand {
|
|
|
121
126
|
this.log(`Role: ${user.role || '-'}`);
|
|
122
127
|
this.log(`Provider: ${user.provider || '-'}`);
|
|
123
128
|
this.log(`ID: ${user.id}`);
|
|
124
|
-
// ADD-CLI2 — Phase L.1 contract fields.
|
|
125
|
-
//
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
? `${workspace.id} (this machine's selection)`
|
|
129
|
-
: `${workspace.name ?? '(unnamed)'}${workspace.id ? ` (${workspace.id})` : ''}`
|
|
130
|
-
: '-';
|
|
131
|
-
this.log(`Workspace: ${workspaceLabel}`);
|
|
132
|
-
this.log(`Daemon ID: ${daemonId ?? '-'}`);
|
|
129
|
+
// ADD-CLI2 — Phase L.1 contract fields. The JSON payload carries `null`;
|
|
130
|
+
// the text says what `null` MEANS rather than printing a bare `-`.
|
|
131
|
+
this.log(`Workspace: ${describeWhoamiWorkspace(workspace)}`);
|
|
132
|
+
this.log(`Daemon ID: ${describeWhoamiDaemon(daemonId)}`);
|
|
133
133
|
// Named, not just spelled: `oversky.dev` and `skrr.ai` do not read as a
|
|
134
134
|
// dev/prod pair to anyone who has not seen the constants, and `whoami` is
|
|
135
135
|
// where the login message sends you to find out where you are (OSK-301).
|
|
@@ -175,6 +175,54 @@ class Whoami extends base_command_1.BaseCommand {
|
|
|
175
175
|
}
|
|
176
176
|
}
|
|
177
177
|
exports.default = Whoami;
|
|
178
|
+
/**
|
|
179
|
+
* What the `Workspace` row says — including when there isn't one. (OSK-322)
|
|
180
|
+
*
|
|
181
|
+
* `whoami` printed a bare `-` here, and a dash is read as "you have none".
|
|
182
|
+
* That is the wrong conclusion: an unset workspace does not mean requests go
|
|
183
|
+
* nowhere, it means they resolve to the account's default. `skrr context`
|
|
184
|
+
* already words the same field correctly, so the sentence below is deliberately
|
|
185
|
+
* ITS sentence, not a second one — the two commands answer the same question
|
|
186
|
+
* and must not answer it differently.
|
|
187
|
+
*
|
|
188
|
+
* (It is duplicated rather than shared because the wording lives inline in
|
|
189
|
+
* `commands/context.ts`; the right home for both is `lib/`, which is a wider
|
|
190
|
+
* change than this fix.)
|
|
191
|
+
*/
|
|
192
|
+
function describeWhoamiWorkspace(workspace) {
|
|
193
|
+
if (!workspace) {
|
|
194
|
+
return 'none — resolves to your default workspace (`skrr workspaces list` names it)';
|
|
195
|
+
}
|
|
196
|
+
if (workspace.source === 'selection') {
|
|
197
|
+
return `${workspace.id} (this machine's selection)`;
|
|
198
|
+
}
|
|
199
|
+
return `${workspace.name ?? '(unnamed)'}${workspace.id ? ` (${workspace.id})` : ''}`;
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* What the `Daemon ID` row says — including when there isn't one. (OSK-322)
|
|
203
|
+
*
|
|
204
|
+
* The field was ambiguous between two different questions, and the dogfood
|
|
205
|
+
* report shows the damage: `whoami` printed `Daemon ID: -` on a machine whose
|
|
206
|
+
* daemon `skrr daemon status` reported as bound, live and cloud-connected, so
|
|
207
|
+
* the reader concluded they had no daemon.
|
|
208
|
+
*
|
|
209
|
+
* The decision, stated here once: **`daemonId` is the daemon THIS CREDENTIAL
|
|
210
|
+
* represents, never the daemon running on this machine.** `whoami` is a
|
|
211
|
+
* credential report — every other row (token source, kind, expiry, refresh,
|
|
212
|
+
* at-rest encryption) describes the credential — so this row has to as well.
|
|
213
|
+
* A CLI credential authenticates a person, so the answer is always "none", and
|
|
214
|
+
* that is a fact rather than a missing lookup.
|
|
215
|
+
*
|
|
216
|
+
* The machine's daemon is a genuinely different subject with its own command,
|
|
217
|
+
* so the row names it instead of leaving the reader to guess which reading
|
|
218
|
+
* applies.
|
|
219
|
+
*/
|
|
220
|
+
function describeWhoamiDaemon(daemonId) {
|
|
221
|
+
if (daemonId)
|
|
222
|
+
return daemonId;
|
|
223
|
+
return ('none — this credential authenticates you, not a daemon ' +
|
|
224
|
+
"(for this machine's daemon run `skrr daemon status`)");
|
|
225
|
+
}
|
|
178
226
|
/**
|
|
179
227
|
* Compact human duration. Shows the largest sensible unit — "5 min", "6
|
|
180
228
|
* hours", "12 days". Negative input (already expired) returns "expired".
|
|
@@ -1,4 +1,27 @@
|
|
|
1
|
+
import { dataService } from '@skrr-ai/data-provider';
|
|
1
2
|
import { BaseCommand } from '../../base-command';
|
|
3
|
+
import { type TableColumn } from '../../lib/format';
|
|
4
|
+
export type WikiLsRow = {
|
|
5
|
+
kind: string;
|
|
6
|
+
name: string;
|
|
7
|
+
title: string;
|
|
8
|
+
updated: string;
|
|
9
|
+
id: string;
|
|
10
|
+
};
|
|
11
|
+
/**
|
|
12
|
+
* One row per entry, with the two fields `ls` used to throw away.
|
|
13
|
+
*
|
|
14
|
+
* Exported for `ls.spec.ts`.
|
|
15
|
+
*/
|
|
16
|
+
export declare function buildRows(listing: Awaited<ReturnType<typeof dataService.listSpaceWikiDirectory>>): WikiLsRow[];
|
|
17
|
+
/**
|
|
18
|
+
* Both added columns are ELIDED when nothing fills them, so a wiki whose slugs
|
|
19
|
+
* already are its titles prints exactly what it printed before rather than
|
|
20
|
+
* gaining a column of dashes.
|
|
21
|
+
*
|
|
22
|
+
* Exported for `ls.spec.ts`.
|
|
23
|
+
*/
|
|
24
|
+
export declare function columnsFor(rows: WikiLsRow[]): Array<TableColumn<WikiLsRow>>;
|
|
2
25
|
export default class WikiLs extends BaseCommand {
|
|
3
26
|
static description: string;
|
|
4
27
|
static examples: string[];
|
package/dist/commands/wiki/ls.js
CHANGED
|
@@ -1,9 +1,70 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.buildRows = buildRows;
|
|
4
|
+
exports.columnsFor = columnsFor;
|
|
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 format_1 = require("../../lib/format");
|
|
9
|
+
/**
|
|
10
|
+
* One row per entry, with the two fields `ls` used to throw away.
|
|
11
|
+
*
|
|
12
|
+
* Exported for `ls.spec.ts`.
|
|
13
|
+
*/
|
|
14
|
+
function buildRows(listing) {
|
|
15
|
+
return [
|
|
16
|
+
...listing.folders.map((f) => ({
|
|
17
|
+
kind: 'dir',
|
|
18
|
+
name: `${f.name}/`,
|
|
19
|
+
// A folder's name IS its human name — it keeps its spaces — so a separate
|
|
20
|
+
// title would just repeat it. The listing carries no folder timestamp, so
|
|
21
|
+
// it has no date to show either.
|
|
22
|
+
title: '',
|
|
23
|
+
updated: '',
|
|
24
|
+
id: f.folder_id,
|
|
25
|
+
})),
|
|
26
|
+
...listing.pages.map((p) => ({
|
|
27
|
+
kind: 'page',
|
|
28
|
+
name: p.slug,
|
|
29
|
+
// A page's path segment is SLUGIFIED, so on a wiki whose pages were
|
|
30
|
+
// titled in the web UI this column was a screen of nicknames —
|
|
31
|
+
// `2026-week-36` for "2026 week 36" — and nothing a human recognises.
|
|
32
|
+
// `tree` has always rendered `slug [title]`; `ls` simply had no column
|
|
33
|
+
// for it, though `--json` carried it all along.
|
|
34
|
+
title: p.title && p.title !== p.slug ? p.title : '',
|
|
35
|
+
updated: (0, format_1.localStamp)(p.lastEditedAt ?? p.updatedAt) || '',
|
|
36
|
+
id: p.pageId,
|
|
37
|
+
})),
|
|
38
|
+
...listing.files.map((f) => ({
|
|
39
|
+
kind: 'file',
|
|
40
|
+
name: f.filename,
|
|
41
|
+
title: '',
|
|
42
|
+
updated: '',
|
|
43
|
+
id: f.file_id,
|
|
44
|
+
})),
|
|
45
|
+
];
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Both added columns are ELIDED when nothing fills them, so a wiki whose slugs
|
|
49
|
+
* already are its titles prints exactly what it printed before rather than
|
|
50
|
+
* gaining a column of dashes.
|
|
51
|
+
*
|
|
52
|
+
* Exported for `ls.spec.ts`.
|
|
53
|
+
*/
|
|
54
|
+
function columnsFor(rows) {
|
|
55
|
+
const columns = [
|
|
56
|
+
{ key: 'kind', header: 'TYPE' },
|
|
57
|
+
{ key: 'name', header: 'NAME', maxWidth: 48 },
|
|
58
|
+
];
|
|
59
|
+
if (rows.some((r) => r.title))
|
|
60
|
+
columns.push({ key: 'title', header: 'TITLE', maxWidth: 48 });
|
|
61
|
+
// The thing most worth knowing from a listing is what moved recently, and
|
|
62
|
+
// `ls` gave a UUID nobody asked for and no date at all.
|
|
63
|
+
if (rows.some((r) => r.updated))
|
|
64
|
+
columns.push({ key: 'updated', header: 'UPDATED' });
|
|
65
|
+
columns.push({ key: 'id', header: 'ID' });
|
|
66
|
+
return columns;
|
|
67
|
+
}
|
|
7
68
|
class WikiLs extends base_command_1.BaseCommand {
|
|
8
69
|
static description = "List a wiki directory's contents";
|
|
9
70
|
static examples = [
|
|
@@ -42,20 +103,12 @@ class WikiLs extends base_command_1.BaseCommand {
|
|
|
42
103
|
this.log(JSON.stringify(listing, null, 2));
|
|
43
104
|
return;
|
|
44
105
|
}
|
|
45
|
-
const rows =
|
|
46
|
-
...listing.folders.map((f) => ({ kind: 'dir', name: `${f.name}/`, id: f.folder_id })),
|
|
47
|
-
...listing.pages.map((p) => ({ kind: 'page', name: p.slug, id: p.pageId })),
|
|
48
|
-
...listing.files.map((f) => ({ kind: 'file', name: f.filename, id: f.file_id })),
|
|
49
|
-
];
|
|
106
|
+
const rows = buildRows(listing);
|
|
50
107
|
if (rows.length === 0) {
|
|
51
108
|
this.log('(empty)');
|
|
52
109
|
return;
|
|
53
110
|
}
|
|
54
|
-
(0, format_1.renderTable)(rows,
|
|
55
|
-
{ key: 'kind', header: 'TYPE' },
|
|
56
|
-
{ key: 'name', header: 'NAME', maxWidth: 48 },
|
|
57
|
-
{ key: 'id', header: 'ID' },
|
|
58
|
-
], (line) => this.log(line));
|
|
111
|
+
(0, format_1.renderTable)(rows, columnsFor(rows), (line) => this.log(line));
|
|
59
112
|
}
|
|
60
113
|
}
|
|
61
114
|
exports.default = WikiLs;
|
|
@@ -1,4 +1,54 @@
|
|
|
1
1
|
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* What is sitting on the destination path already.
|
|
4
|
+
*
|
|
5
|
+
* `free` and `self` are both "go ahead" — moving a page onto its own path is a
|
|
6
|
+
* no-op rename, not a collision — but they are kept apart so the caller cannot
|
|
7
|
+
* accidentally treat "nothing there" as "the thing there is us".
|
|
8
|
+
*/
|
|
9
|
+
export type DestinationOccupant = {
|
|
10
|
+
kind: 'free';
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'self';
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'page';
|
|
15
|
+
pageId: string;
|
|
16
|
+
title: string;
|
|
17
|
+
} | {
|
|
18
|
+
kind: 'folder';
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* Why this move must not proceed — or `null` when it may.
|
|
22
|
+
*
|
|
23
|
+
* Pure, so the decision is testable without a server: everything it needs is
|
|
24
|
+
* the occupant, the kinds involved, and whether the caller authorised a
|
|
25
|
+
* replacement. The messages say the word the old output never did — "already
|
|
26
|
+
* exists" — because a human watching the terminal could see the `-2` in the
|
|
27
|
+
* success line and still not read it as a collision.
|
|
28
|
+
*/
|
|
29
|
+
export declare function destinationConflict(opts: {
|
|
30
|
+
occupant: DestinationOccupant;
|
|
31
|
+
sourceKind: 'page' | 'folder';
|
|
32
|
+
source: string;
|
|
33
|
+
destination: string;
|
|
34
|
+
force: boolean;
|
|
35
|
+
binName: string;
|
|
36
|
+
spaceId: string;
|
|
37
|
+
}): {
|
|
38
|
+
message: string;
|
|
39
|
+
code: string;
|
|
40
|
+
} | null;
|
|
41
|
+
/**
|
|
42
|
+
* Did the server disambiguate the slug anyway?
|
|
43
|
+
*
|
|
44
|
+
* The pre-flight check above closes the ordinary case, but it cannot close the
|
|
45
|
+
* race — a page can appear on the destination between the check and the move —
|
|
46
|
+
* and an older server has no notion of refusing. `allocateSlug` suffixes (`y` ->
|
|
47
|
+
* `y-2`), which is precisely the outcome that must never read as success, so the
|
|
48
|
+
* post-check reuses `write.ts`'s detector rather than a second copy of the
|
|
49
|
+
* server's slug rules.
|
|
50
|
+
*/
|
|
51
|
+
export declare function serverRenamedOnMove(requestedSlug: string, movedSlug?: string): boolean;
|
|
2
52
|
/**
|
|
3
53
|
* `skrr wiki mv <space> <source> <destination>` — move or rename a page OR a
|
|
4
54
|
* folder.
|
|
@@ -13,6 +63,16 @@ import { BaseCommand } from '../../base-command';
|
|
|
13
63
|
* through `PATCH /pages/:pageId/move` (which creates missing destination
|
|
14
64
|
* folders); folders go through `PATCH /folders/:folder_id`, whose server-side
|
|
15
65
|
* guard refuses to move a folder into its own descendant.
|
|
66
|
+
*
|
|
67
|
+
* OCCUPIED DESTINATIONS ARE REFUSED, not renamed around. The move endpoint runs
|
|
68
|
+
* the same `allocateSlug` de-duplication a CREATE does, so `mv X Y` onto an
|
|
69
|
+
* existing `Y` landed the source at `Y-2`, reported success, and left `Y`
|
|
70
|
+
* holding its OLD content — and `mv X Y; cat Y` then reads the stale page while
|
|
71
|
+
* believing it read the new one. Moving something onto a canonical path is the
|
|
72
|
+
* entire reason to run `mv`, so "I could not do that, so I did something
|
|
73
|
+
* adjacent" is the one outcome this command must not present as success.
|
|
74
|
+
* `--force` gives the filesystem's answer (replace the destination); the
|
|
75
|
+
* default gives the safe one (refuse and change nothing).
|
|
16
76
|
*/
|
|
17
77
|
export default class WikiMv extends BaseCommand {
|
|
18
78
|
static description: string;
|
|
@@ -24,8 +84,18 @@ export default class WikiMv extends BaseCommand {
|
|
|
24
84
|
};
|
|
25
85
|
static flags: {
|
|
26
86
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
87
|
+
force: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
27
88
|
};
|
|
28
89
|
run(): Promise<void>;
|
|
90
|
+
/**
|
|
91
|
+
* What already occupies `destination` — a page, a folder, us, or nothing.
|
|
92
|
+
*
|
|
93
|
+
* A page and a folder cannot share a path, so this asks in the same order the
|
|
94
|
+
* source resolution does. Both lookups treat a 404 as an answer rather than a
|
|
95
|
+
* failure; anything else still surfaces, so an unreadable destination never
|
|
96
|
+
* degrades into "looks free, go ahead and clobber".
|
|
97
|
+
*/
|
|
98
|
+
private resolveDestination;
|
|
29
99
|
/**
|
|
30
100
|
* Reparent and/or rename a folder.
|
|
31
101
|
*
|