@productbrain/cli 0.1.0-beta.71 → 0.1.0-beta.72
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/__tests__/audit.test.js +44 -44
- package/dist/__tests__/capture.test.js +37 -37
- package/dist/__tests__/constellation.test.js +14 -14
- package/dist/__tests__/context-strategy.test.js +8 -8
- package/dist/__tests__/fields.test.js +20 -20
- package/dist/__tests__/ingest.test.js +28 -28
- package/dist/__tests__/orient.test.js +8 -8
- package/dist/__tests__/promote.test.js +15 -15
- package/dist/__tests__/proposals.test.js +18 -18
- package/dist/__tests__/relate.test.js +14 -14
- package/dist/__tests__/session-touch.test.js +11 -11
- package/dist/__tests__/session.test.js +2 -2
- package/dist/__tests__/setup.test.js +7 -7
- package/dist/__tests__/update.test.js +21 -21
- package/dist/__tests__/workspace.test.js +20 -20
- package/dist/commands/accept.js +4 -4
- package/dist/commands/admin/cockpit.d.ts +4 -2
- package/dist/commands/admin/cockpit.d.ts.map +1 -1
- package/dist/commands/admin/cockpit.js +213 -6
- package/dist/commands/admin/cockpit.js.map +1 -1
- package/dist/commands/admin/index.d.ts.map +1 -1
- package/dist/commands/admin/index.js +2 -0
- package/dist/commands/admin/index.js.map +1 -1
- package/dist/commands/admin/inspect.d.ts +9 -0
- package/dist/commands/admin/inspect.d.ts.map +1 -1
- package/dist/commands/admin/inspect.js +19 -0
- package/dist/commands/admin/inspect.js.map +1 -1
- package/dist/commands/admin/inspect.test.js +20 -1
- package/dist/commands/admin/inspect.test.js.map +1 -1
- package/dist/commands/admin/manage.d.ts +8 -0
- package/dist/commands/admin/manage.d.ts.map +1 -0
- package/dist/commands/admin/manage.js +76 -0
- package/dist/commands/admin/manage.js.map +1 -0
- package/dist/commands/audit.js +4 -4
- package/dist/commands/brief.js +4 -4
- package/dist/commands/capture.js +6 -6
- package/dist/commands/chain-walk.js +2 -2
- package/dist/commands/changes.js +2 -2
- package/dist/commands/codex-prep.js +2 -2
- package/dist/commands/collections.js +5 -5
- package/dist/commands/constellation.js +2 -2
- package/dist/commands/context.js +2 -2
- package/dist/commands/cross-cut.js +2 -2
- package/dist/commands/doctor.js +3 -3
- package/dist/commands/doctor.test.js +1 -1
- package/dist/commands/fields.js +2 -2
- package/dist/commands/get.js +3 -3
- package/dist/commands/handshake.js +5 -5
- package/dist/commands/ingest.js +6 -6
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +1 -1
- package/dist/commands/orient.js +2 -2
- package/dist/commands/promote.js +4 -4
- package/dist/commands/proposals.js +2 -2
- package/dist/commands/reject.js +2 -2
- package/dist/commands/relate.js +3 -3
- package/dist/commands/search.js +2 -2
- package/dist/commands/session.js +7 -7
- package/dist/commands/setup.js +5 -5
- package/dist/commands/update.js +3 -3
- package/dist/commands/usage.d.ts +1 -1
- package/dist/commands/usage.js +4 -4
- package/dist/commands/verify.js +2 -2
- package/dist/commands/workspace.js +4 -4
- package/dist/generators/chain-rules.d.ts +3 -3
- package/dist/generators/chain-rules.js +3 -3
- package/dist/generators/chain-rules.test.js +2 -2
- package/dist/generators/portable-knowledge.js +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/activation.js +2 -2
- package/dist/lib/activation.test.js +3 -3
- package/dist/lib/client.d.ts +4 -4
- package/dist/lib/client.js +8 -8
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/onboarding-path-b.js +8 -8
- package/dist/lib/onboarding-shared.js +2 -2
- package/dist/lib/onboarding.js +4 -4
- package/dist/lib/workspace-probe.js +2 -2
- package/package.json +1 -1
|
@@ -13,9 +13,9 @@
|
|
|
13
13
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
14
14
|
import { runWorkspaceVerify, runWorkspaceRepair, runDefinitionsDiff } from '../commands/workspace.js';
|
|
15
15
|
// ── Mocks ──────────────────────────────────────────────────────────────────
|
|
16
|
-
const
|
|
16
|
+
const kernelCallMock = vi.fn();
|
|
17
17
|
vi.mock('../lib/client.js', () => ({
|
|
18
|
-
|
|
18
|
+
kernelCall: (...args) => kernelCallMock(...args),
|
|
19
19
|
}));
|
|
20
20
|
vi.mock('../lib/config.js', () => ({
|
|
21
21
|
getConfigOrGuide: vi.fn(() => Promise.resolve({ apiKey: 'pb_sk_test', siteUrl: 'https://test.convex.site' })),
|
|
@@ -84,12 +84,12 @@ const missingSeedsResult = {
|
|
|
84
84
|
describe('formatWorkspaceVerify', () => {
|
|
85
85
|
beforeEach(() => vi.clearAllMocks());
|
|
86
86
|
it('renders workspace name in the heading', async () => {
|
|
87
|
-
|
|
87
|
+
kernelCallMock.mockResolvedValue(healthyResult);
|
|
88
88
|
const output = await captureStdout(() => runWorkspaceVerify());
|
|
89
89
|
expect(output).toContain('Acme Workspace');
|
|
90
90
|
});
|
|
91
91
|
it('shows healthy status when all checks pass', async () => {
|
|
92
|
-
|
|
92
|
+
kernelCallMock.mockResolvedValue(healthyResult);
|
|
93
93
|
const output = await captureStdout(() => runWorkspaceVerify());
|
|
94
94
|
expect(output).toContain('healthy');
|
|
95
95
|
// No gaps section
|
|
@@ -97,7 +97,7 @@ describe('formatWorkspaceVerify', () => {
|
|
|
97
97
|
expect(output).not.toContain('pb workspace repair');
|
|
98
98
|
});
|
|
99
99
|
it('shows all four check labels', async () => {
|
|
100
|
-
|
|
100
|
+
kernelCallMock.mockResolvedValue(healthyResult);
|
|
101
101
|
const output = await captureStdout(() => runWorkspaceVerify());
|
|
102
102
|
expect(output).toContain('Collections aligned');
|
|
103
103
|
expect(output).toContain('Glossary seeded');
|
|
@@ -105,7 +105,7 @@ describe('formatWorkspaceVerify', () => {
|
|
|
105
105
|
expect(output).toContain('Default circle');
|
|
106
106
|
});
|
|
107
107
|
it('shows incomplete status with gaps section', async () => {
|
|
108
|
-
|
|
108
|
+
kernelCallMock.mockResolvedValue(incompletResult);
|
|
109
109
|
const output = await captureStdout(() => runWorkspaceVerify());
|
|
110
110
|
expect(output).toContain('incomplete');
|
|
111
111
|
expect(output).toContain('Gaps');
|
|
@@ -113,13 +113,13 @@ describe('formatWorkspaceVerify', () => {
|
|
|
113
113
|
expect(output).toContain('pb workspace repair');
|
|
114
114
|
});
|
|
115
115
|
it('shows missing-seeds status', async () => {
|
|
116
|
-
|
|
116
|
+
kernelCallMock.mockResolvedValue(missingSeedsResult);
|
|
117
117
|
const output = await captureStdout(() => runWorkspaceVerify());
|
|
118
118
|
expect(output).toContain('missing-seeds');
|
|
119
119
|
expect(output).toContain('collections missing');
|
|
120
120
|
});
|
|
121
121
|
it('lists all gaps individually', async () => {
|
|
122
|
-
|
|
122
|
+
kernelCallMock.mockResolvedValue(incompletResult);
|
|
123
123
|
const output = await captureStdout(() => runWorkspaceVerify());
|
|
124
124
|
for (const gap of incompletResult.gaps) {
|
|
125
125
|
expect(output).toContain(gap);
|
|
@@ -144,31 +144,31 @@ describe('formatWorkspaceRepair', () => {
|
|
|
144
144
|
alreadyHealthy: false,
|
|
145
145
|
};
|
|
146
146
|
it('renders already-healthy message when alreadyHealthy is true', async () => {
|
|
147
|
-
|
|
147
|
+
kernelCallMock.mockResolvedValue(alreadyHealthyResult);
|
|
148
148
|
const output = await captureStdout(() => runWorkspaceRepair());
|
|
149
149
|
expect(output).toContain('already healthy');
|
|
150
150
|
expect(output).not.toContain('Scheduled');
|
|
151
151
|
});
|
|
152
152
|
it('lists scheduled jobs when seeds were missing', async () => {
|
|
153
|
-
|
|
153
|
+
kernelCallMock.mockResolvedValue(repairedResult);
|
|
154
154
|
const output = await captureStdout(() => runWorkspaceRepair());
|
|
155
155
|
expect(output).toContain('Scheduled');
|
|
156
156
|
expect(output).toContain('seedSystemEntries');
|
|
157
157
|
expect(output).toContain('seedSemanticTypes');
|
|
158
158
|
});
|
|
159
159
|
it('lists skipped jobs when already present', async () => {
|
|
160
|
-
|
|
160
|
+
kernelCallMock.mockResolvedValue(repairedResult);
|
|
161
161
|
const output = await captureStdout(() => runWorkspaceRepair());
|
|
162
162
|
expect(output).toContain('Skipped');
|
|
163
163
|
expect(output).toContain('seedDefaultCircle');
|
|
164
164
|
});
|
|
165
165
|
it('shows async-confirmation hint when seeds were scheduled', async () => {
|
|
166
|
-
|
|
166
|
+
kernelCallMock.mockResolvedValue(repairedResult);
|
|
167
167
|
const output = await captureStdout(() => runWorkspaceRepair());
|
|
168
168
|
expect(output).toContain('pb workspace verify');
|
|
169
169
|
});
|
|
170
170
|
it('renders workspace name', async () => {
|
|
171
|
-
|
|
171
|
+
kernelCallMock.mockResolvedValue(repairedResult);
|
|
172
172
|
const output = await captureStdout(() => runWorkspaceRepair());
|
|
173
173
|
expect(output).toContain('Fixed Workspace');
|
|
174
174
|
});
|
|
@@ -215,7 +215,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
215
215
|
const dir = mkdtempSync(join(tmpdir(), 'pb-test-'));
|
|
216
216
|
const file = join(dir, 'defs.json');
|
|
217
217
|
writeFileSync(file, JSON.stringify([]));
|
|
218
|
-
|
|
218
|
+
kernelCallMock.mockResolvedValue([]);
|
|
219
219
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
220
220
|
expect(output).toContain('No definitions found');
|
|
221
221
|
rmSync(dir, { recursive: true, force: true });
|
|
@@ -228,7 +228,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
228
228
|
const file = join(dir, 'defs.json');
|
|
229
229
|
const def = { slug: 'bets', name: 'Bets', description: 'Work packages' };
|
|
230
230
|
writeFileSync(file, JSON.stringify([def]));
|
|
231
|
-
|
|
231
|
+
kernelCallMock.mockResolvedValue([def]);
|
|
232
232
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
233
233
|
expect(output).toContain('Identical');
|
|
234
234
|
expect(output).toContain('1');
|
|
@@ -242,7 +242,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
242
242
|
const dir = mkdtempSync(join(tmpdir(), 'pb-test-'));
|
|
243
243
|
const file = join(dir, 'defs.json');
|
|
244
244
|
writeFileSync(file, JSON.stringify([{ slug: 'experiments', name: 'Experiments' }]));
|
|
245
|
-
|
|
245
|
+
kernelCallMock.mockResolvedValue([]);
|
|
246
246
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
247
247
|
expect(output).toContain('Only local');
|
|
248
248
|
expect(output).toContain('experiments');
|
|
@@ -255,7 +255,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
255
255
|
const dir = mkdtempSync(join(tmpdir(), 'pb-test-'));
|
|
256
256
|
const file = join(dir, 'defs.json');
|
|
257
257
|
writeFileSync(file, JSON.stringify([]));
|
|
258
|
-
|
|
258
|
+
kernelCallMock.mockResolvedValue([{ slug: 'tensions', name: 'Tensions' }]);
|
|
259
259
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
260
260
|
expect(output).toContain('Only server');
|
|
261
261
|
expect(output).toContain('tensions');
|
|
@@ -268,7 +268,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
268
268
|
const dir = mkdtempSync(join(tmpdir(), 'pb-test-'));
|
|
269
269
|
const file = join(dir, 'defs.json');
|
|
270
270
|
writeFileSync(file, JSON.stringify([{ slug: 'bets', name: 'Old name' }]));
|
|
271
|
-
|
|
271
|
+
kernelCallMock.mockResolvedValue([{ slug: 'bets', name: 'New name' }]);
|
|
272
272
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
273
273
|
expect(output).toContain('Different');
|
|
274
274
|
expect(output).toContain('bets');
|
|
@@ -283,7 +283,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
283
283
|
const localDef = { slug: 'bets', name: 'Bets' };
|
|
284
284
|
writeFileSync(file, JSON.stringify([localDef]));
|
|
285
285
|
// Server returns same data but with Convex metadata — should still be identical
|
|
286
|
-
|
|
286
|
+
kernelCallMock.mockResolvedValue([{ ...localDef, _id: 'abc123', _creationTime: 1234567890 }]);
|
|
287
287
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
288
288
|
expect(output).toContain('Identical');
|
|
289
289
|
expect(output).toContain('All definitions match');
|
|
@@ -298,7 +298,7 @@ describe('formatDefinitionsDiff — via runDefinitionsDiff mock', () => {
|
|
|
298
298
|
const identical = { slug: 'bets', name: 'Bets' };
|
|
299
299
|
const changed = { slug: 'tensions', name: 'Old tensions' };
|
|
300
300
|
writeFileSync(file, JSON.stringify([identical, changed]));
|
|
301
|
-
|
|
301
|
+
kernelCallMock.mockResolvedValue([identical, { slug: 'tensions', name: 'New tensions' }]);
|
|
302
302
|
const output = await captureStdout(() => runDefinitionsDiff({ file }));
|
|
303
303
|
// 1 identical / 2 total
|
|
304
304
|
expect(output).toContain('1/2');
|
package/dist/commands/accept.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
* skipped for manual review. Reports what was approved and what was skipped.
|
|
11
11
|
*/
|
|
12
12
|
import { getConfigOrGuide } from '../lib/config.js';
|
|
13
|
-
import {
|
|
13
|
+
import { kernelCall } from '../lib/client.js';
|
|
14
14
|
import { runCliCommand } from '../lib/runner.js';
|
|
15
15
|
import { CLIError, ErrorCode } from '../lib/errors.js';
|
|
16
16
|
import { formatAcceptReceipt, formatAutoAcceptReport, } from '../formatters/proposals.js';
|
|
@@ -22,7 +22,7 @@ export async function runAccept(options) {
|
|
|
22
22
|
if (options.auto) {
|
|
23
23
|
await runCliCommand({
|
|
24
24
|
fn: async () => {
|
|
25
|
-
const proposals = await
|
|
25
|
+
const proposals = await kernelCall('governance.listProposals', {});
|
|
26
26
|
const approved = [];
|
|
27
27
|
const skipped = [];
|
|
28
28
|
for (const p of proposals) {
|
|
@@ -38,7 +38,7 @@ export async function runAccept(options) {
|
|
|
38
38
|
continue;
|
|
39
39
|
}
|
|
40
40
|
try {
|
|
41
|
-
await
|
|
41
|
+
await kernelCall('governance.respondToProposal', {
|
|
42
42
|
proposalId: p._id,
|
|
43
43
|
verdict: 'approve',
|
|
44
44
|
});
|
|
@@ -65,7 +65,7 @@ export async function runAccept(options) {
|
|
|
65
65
|
}
|
|
66
66
|
await runCliCommand({
|
|
67
67
|
fn: async () => {
|
|
68
|
-
return await
|
|
68
|
+
return await kernelCall('governance.respondToProposal', {
|
|
69
69
|
proposalId: options.proposalId,
|
|
70
70
|
verdict: 'approve',
|
|
71
71
|
});
|
|
@@ -56,7 +56,7 @@ export type ChangelogRow = {
|
|
|
56
56
|
timestamp: number;
|
|
57
57
|
definitionSlug: string;
|
|
58
58
|
};
|
|
59
|
-
export type CockpitTab = 'workspaces' | 'seeds' | 'changelog';
|
|
59
|
+
export type CockpitTab = 'workspaces' | 'seeds' | 'changelog' | 'manage';
|
|
60
60
|
export type CockpitProps = {
|
|
61
61
|
workspaces: WorkspaceRow[];
|
|
62
62
|
currentKeyWorkspaceId: string | null;
|
|
@@ -70,8 +70,10 @@ export type CockpitProps = {
|
|
|
70
70
|
seedRows: SeedRow[];
|
|
71
71
|
/** Changelog rows for heatmap. WP-317 E8 / S4. */
|
|
72
72
|
changelogRows: ChangelogRow[];
|
|
73
|
+
/** Called when ManageTab destructive flow becomes active/inactive. WP-324 S2. */
|
|
74
|
+
onFlowActive?: (active: boolean) => void;
|
|
73
75
|
};
|
|
74
|
-
export declare function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn, onRunFix, siteUrl, seedRows, changelogRows }: CockpitProps): import("react/jsx-runtime").JSX.Element;
|
|
76
|
+
export declare function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn, onRunFix, siteUrl, seedRows, changelogRows, onFlowActive }: CockpitProps): import("react/jsx-runtime").JSX.Element;
|
|
75
77
|
export type WorkspaceHealthData = {
|
|
76
78
|
readiness: unknown;
|
|
77
79
|
collections: unknown;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cockpit.d.ts","sourceRoot":"","sources":["../../../src/commands/admin/cockpit.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;
|
|
1
|
+
{"version":3,"file":"cockpit.d.ts","sourceRoot":"","sources":["../../../src/commands/admin/cockpit.tsx"],"names":[],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
/**
|
|
3
3
|
* Ink TUI cockpit for pb admin (WP-314 S1, WP-317 S3).
|
|
4
4
|
*
|
|
@@ -21,6 +21,8 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
21
21
|
import { useState, useMemo, useEffect, useCallback } from 'react';
|
|
22
22
|
import { Box, Text, useInput, useApp } from 'ink';
|
|
23
23
|
import TextInput from 'ink-text-input';
|
|
24
|
+
import { adminCall } from '../../lib/client.js';
|
|
25
|
+
import { resolveWorkspaceArg, resolveUserArg } from './inspect.js';
|
|
24
26
|
// ---------------------------------------------------------------------------
|
|
25
27
|
// Env label derivation
|
|
26
28
|
// ---------------------------------------------------------------------------
|
|
@@ -86,11 +88,12 @@ function padLeft(s, width) {
|
|
|
86
88
|
// ---------------------------------------------------------------------------
|
|
87
89
|
// Tab names and order
|
|
88
90
|
// ---------------------------------------------------------------------------
|
|
89
|
-
const TABS = ['workspaces', 'seeds', 'changelog'];
|
|
91
|
+
const TABS = ['workspaces', 'seeds', 'changelog', 'manage'];
|
|
90
92
|
const TAB_LABELS = {
|
|
91
93
|
workspaces: 'Workspaces',
|
|
92
94
|
seeds: 'Seeds',
|
|
93
95
|
changelog: 'Changelog',
|
|
96
|
+
manage: 'Manage',
|
|
94
97
|
};
|
|
95
98
|
function nextTab(current) {
|
|
96
99
|
const idx = TABS.indexOf(current);
|
|
@@ -212,10 +215,208 @@ function ChangelogTab({ changelogRows, siteUrl }) {
|
|
|
212
215
|
})] }, dow));
|
|
213
216
|
}) }), _jsx(Box, { marginTop: 1, children: _jsxs(Text, { dimColor: true, children: ["\u00B7 0 \u2591 1-2 \u2592 3-5 \u2588 6+ ", days.length, " days, ", changelogRows.length, " change", changelogRows.length !== 1 ? 's' : ''] }) })] }));
|
|
214
217
|
}
|
|
218
|
+
const TEST_ONLY_WARNING = '⚠ TEST-ONLY — for onboarding reset testing only (BR-210)';
|
|
219
|
+
const MANAGE_OPS = [
|
|
220
|
+
{ op: 'delete-workspace', label: 'delete-workspace', description: 'Permanently delete a workspace and all its data' },
|
|
221
|
+
{ op: 'delete-user', label: 'delete-user', description: 'Permanently delete a user across all workspaces' },
|
|
222
|
+
];
|
|
223
|
+
function ManageTab({ onFlowActive }) {
|
|
224
|
+
const [flowState, setFlowState] = useState('idle');
|
|
225
|
+
const [selectedOpIdx, setSelectedOpIdx] = useState(0);
|
|
226
|
+
const [selectedOp, setSelectedOp] = useState(null);
|
|
227
|
+
const [targetInput, setTargetInput] = useState('');
|
|
228
|
+
const [resolvedId, setResolvedId] = useState(null);
|
|
229
|
+
const [dryRunResult, setDryRunResult] = useState(null);
|
|
230
|
+
const [confirmInput, setConfirmInput] = useState('');
|
|
231
|
+
const [loading, setLoading] = useState(false);
|
|
232
|
+
const [errorMsg, setErrorMsg] = useState(null);
|
|
233
|
+
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
234
|
+
const [doneResult, setDoneResult] = useState(null);
|
|
235
|
+
useEffect(() => {
|
|
236
|
+
const isActive = flowState !== 'idle' && flowState !== 'done';
|
|
237
|
+
onFlowActive?.(isActive);
|
|
238
|
+
}, [flowState, onFlowActive]);
|
|
239
|
+
useEffect(() => {
|
|
240
|
+
if (!loading)
|
|
241
|
+
return;
|
|
242
|
+
const timer = setInterval(() => setSpinnerFrame((f) => (f + 1) % SPINNER_FRAMES.length), 80);
|
|
243
|
+
return () => clearInterval(timer);
|
|
244
|
+
}, [loading]);
|
|
245
|
+
function goIdle() {
|
|
246
|
+
setFlowState('idle');
|
|
247
|
+
setSelectedOp(null);
|
|
248
|
+
setTargetInput('');
|
|
249
|
+
setResolvedId(null);
|
|
250
|
+
setDryRunResult(null);
|
|
251
|
+
setConfirmInput('');
|
|
252
|
+
setErrorMsg(null);
|
|
253
|
+
setDoneResult(null);
|
|
254
|
+
}
|
|
255
|
+
useInput((input, key) => {
|
|
256
|
+
if (flowState === 'idle') {
|
|
257
|
+
if (key.upArrow) {
|
|
258
|
+
setSelectedOpIdx((i) => Math.max(0, i - 1));
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
if (key.downArrow) {
|
|
262
|
+
setSelectedOpIdx((i) => Math.min(MANAGE_OPS.length - 1, i + 1));
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (key.return) {
|
|
266
|
+
const op = MANAGE_OPS[selectedOpIdx].op;
|
|
267
|
+
setSelectedOp(op);
|
|
268
|
+
setFlowState('entering-target');
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
if (flowState === 'entering-target') {
|
|
274
|
+
if (key.escape) {
|
|
275
|
+
goIdle();
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
if (flowState === 'showing-dry-run') {
|
|
281
|
+
if (key.escape) {
|
|
282
|
+
goIdle();
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
if (key.return) {
|
|
286
|
+
setFlowState('confirming');
|
|
287
|
+
setConfirmInput('');
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
return;
|
|
291
|
+
}
|
|
292
|
+
if (flowState === 'confirming') {
|
|
293
|
+
if (key.escape) {
|
|
294
|
+
goIdle();
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (flowState === 'done') {
|
|
300
|
+
goIdle();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
});
|
|
304
|
+
async function handleTargetSubmit(value) {
|
|
305
|
+
if (!value.trim())
|
|
306
|
+
return;
|
|
307
|
+
setLoading(true);
|
|
308
|
+
setErrorMsg(null);
|
|
309
|
+
try {
|
|
310
|
+
let id;
|
|
311
|
+
if (selectedOp === 'delete-workspace') {
|
|
312
|
+
id = await resolveWorkspaceArg(value.trim());
|
|
313
|
+
if (!id) {
|
|
314
|
+
setErrorMsg(`Workspace not found: "${value.trim()}"`);
|
|
315
|
+
setLoading(false);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
else {
|
|
320
|
+
id = await resolveUserArg(value.trim());
|
|
321
|
+
}
|
|
322
|
+
setResolvedId(id);
|
|
323
|
+
let result;
|
|
324
|
+
if (selectedOp === 'delete-workspace') {
|
|
325
|
+
result = await adminCall('manage:delete-workspace', { workspaceId: id, dryRun: true });
|
|
326
|
+
}
|
|
327
|
+
else {
|
|
328
|
+
result = await adminCall('manage:delete-user', { clerkUserId: id, dryRun: true });
|
|
329
|
+
}
|
|
330
|
+
if (result?.success === false && result?.reason === 'user_owns_workspace') {
|
|
331
|
+
setErrorMsg('Cannot delete: user is the sole member of a workspace. Delete the workspace first.');
|
|
332
|
+
setLoading(false);
|
|
333
|
+
return;
|
|
334
|
+
}
|
|
335
|
+
setDryRunResult(result);
|
|
336
|
+
setFlowState('showing-dry-run');
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
setErrorMsg(err instanceof Error ? err.message : String(err));
|
|
340
|
+
}
|
|
341
|
+
finally {
|
|
342
|
+
setLoading(false);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
async function handleConfirmSubmit(value) {
|
|
346
|
+
if (!resolvedId || !selectedOp)
|
|
347
|
+
return;
|
|
348
|
+
const expectedText = `DELETE ${targetInput.trim()}`;
|
|
349
|
+
if (value !== expectedText) {
|
|
350
|
+
setErrorMsg('Input did not match — try again');
|
|
351
|
+
setConfirmInput('');
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
setLoading(true);
|
|
355
|
+
setErrorMsg(null);
|
|
356
|
+
try {
|
|
357
|
+
let result;
|
|
358
|
+
if (selectedOp === 'delete-workspace') {
|
|
359
|
+
result = await adminCall('manage:delete-workspace', { workspaceId: resolvedId, dryRun: false });
|
|
360
|
+
}
|
|
361
|
+
else {
|
|
362
|
+
result = await adminCall('manage:delete-user', { clerkUserId: resolvedId, dryRun: false });
|
|
363
|
+
}
|
|
364
|
+
if (result?.success === false && result?.reason === 'user_owns_workspace') {
|
|
365
|
+
setErrorMsg('Cannot delete: user is the sole member of a workspace.');
|
|
366
|
+
goIdle();
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
setDoneResult(result);
|
|
370
|
+
setFlowState('done');
|
|
371
|
+
// done state renders success screen; user presses any key → goIdle() fires via useInput handler
|
|
372
|
+
}
|
|
373
|
+
catch (err) {
|
|
374
|
+
setErrorMsg(err instanceof Error ? err.message : String(err));
|
|
375
|
+
goIdle();
|
|
376
|
+
}
|
|
377
|
+
finally {
|
|
378
|
+
setLoading(false);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
function renderDryRunSummary(result) {
|
|
382
|
+
if (!result)
|
|
383
|
+
return '—';
|
|
384
|
+
const total = result.totalDeleted ?? 0;
|
|
385
|
+
const tables = result.tablesDeleted ?? {};
|
|
386
|
+
const lines = [` Total rows to delete: ${total}`];
|
|
387
|
+
for (const [table, count] of Object.entries(tables)) {
|
|
388
|
+
if (count > 0)
|
|
389
|
+
lines.push(` ${table}: ${count}`);
|
|
390
|
+
}
|
|
391
|
+
return lines.join('\n');
|
|
392
|
+
}
|
|
393
|
+
if (flowState === 'idle') {
|
|
394
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsx(Text, { bold: true, color: "red", children: "Manage \u2014 Destructive Operations" }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "yellow", children: TEST_ONLY_WARNING }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { color: "gray", children: "Select an operation:" }) }), MANAGE_OPS.map((item, idx) => {
|
|
395
|
+
const isSelected = idx === selectedOpIdx;
|
|
396
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { children: [_jsx(Text, { color: isSelected ? 'cyan' : undefined, children: isSelected ? '▶ ' : ' ' }), _jsx(Text, { bold: isSelected, color: isSelected ? 'cyan' : 'white', children: item.label })] }), isSelected && (_jsx(Box, { paddingLeft: 4, children: _jsx(Text, { dimColor: true, color: "gray", children: item.description }) }))] }, item.op));
|
|
397
|
+
}), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, color: "gray", children: "\u2191\u2193 navigate \u00B7 Enter select" }) })] }));
|
|
398
|
+
}
|
|
399
|
+
if (flowState === 'entering-target') {
|
|
400
|
+
const prompt = selectedOp === 'delete-workspace' ? 'Enter workspace slug:' : 'Enter user email:';
|
|
401
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { marginBottom: 1, children: _jsxs(Text, { bold: true, color: "red", children: ["Manage \u2014 ", selectedOp] }) }), loading ? (_jsxs(Text, { color: "cyan", children: [SPINNER_FRAMES[spinnerFrame], " Resolving\u2026"] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: [prompt, " "] }), _jsx(TextInput, { value: targetInput, onChange: setTargetInput, onSubmit: (v) => { void handleTargetSubmit(v); } })] }), errorMsg && _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", errorMsg] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, color: "gray", children: "Enter confirm \u00B7 Escape cancel" }) })] }))] }));
|
|
402
|
+
}
|
|
403
|
+
if (flowState === 'showing-dry-run') {
|
|
404
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "yellow", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, color: "yellow", children: ["Dry-run impact \u2014 ", selectedOp] }), _jsxs(Text, { color: "gray", children: ["Target: ", targetInput.trim()] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: "red", bold: true, children: TEST_ONLY_WARNING }) }), _jsx(Text, { color: "white", children: renderDryRunSummary(dryRunResult) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, color: "gray", children: "Enter to confirm \u2192 \u00B7 Escape cancel" }) })] }));
|
|
405
|
+
}
|
|
406
|
+
if (flowState === 'confirming') {
|
|
407
|
+
const expectedText = `DELETE ${targetInput.trim()}`;
|
|
408
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "red", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, color: "red", children: ["\u26A0 CONFIRM DELETION \u2014 ", selectedOp] }), _jsx(Box, { marginTop: 1, marginBottom: 1, children: _jsx(Text, { color: "red", bold: true, children: TEST_ONLY_WARNING }) }), loading ? (_jsxs(Text, { color: "cyan", children: [SPINNER_FRAMES[spinnerFrame], " Deleting\u2026"] })) : (_jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsxs(Text, { color: "cyan", children: ["Type ", _jsx(Text, { bold: true, children: expectedText }), " to confirm: "] }), _jsx(TextInput, { value: confirmInput, onChange: setConfirmInput, onSubmit: (v) => { void handleConfirmSubmit(v); } })] }), errorMsg && _jsx(Box, { marginTop: 1, children: _jsxs(Text, { color: "red", children: ["\u2717 ", errorMsg] }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, color: "gray", children: "Enter confirm \u00B7 Escape cancel" }) })] }))] }));
|
|
409
|
+
}
|
|
410
|
+
if (flowState === 'done') {
|
|
411
|
+
const total = doneResult?.totalDeleted ?? 0;
|
|
412
|
+
return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "green", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "green", children: "\u2713 Deleted successfully" }), _jsxs(Text, { color: "gray", children: [total, " rows removed"] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, color: "gray", children: "Press any key to return" }) })] }));
|
|
413
|
+
}
|
|
414
|
+
return null;
|
|
415
|
+
}
|
|
215
416
|
// ---------------------------------------------------------------------------
|
|
216
417
|
// Cockpit component
|
|
217
418
|
// ---------------------------------------------------------------------------
|
|
218
|
-
export function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn, onRunFix, siteUrl, seedRows, changelogRows }) {
|
|
419
|
+
export function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn, onRunFix, siteUrl, seedRows, changelogRows, onFlowActive }) {
|
|
219
420
|
const { exit } = useApp();
|
|
220
421
|
const [activeTab, setActiveTab] = useState('workspaces');
|
|
221
422
|
const [filter, setFilter] = useState('');
|
|
@@ -228,6 +429,12 @@ export function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn,
|
|
|
228
429
|
const [spinnerFrame, setSpinnerFrame] = useState(0);
|
|
229
430
|
/** Set of workspace _ids whose topIssue was cleared locally after a fix ran. */
|
|
230
431
|
const [localFixes, setLocalFixes] = useState(new Set());
|
|
432
|
+
// ManageTab flow active — blocks Tab/Shift-Tab (WP-324 S2, done-when #4)
|
|
433
|
+
const [manageFlowActive, setManageFlowActive] = useState(false);
|
|
434
|
+
const handleManageFlowActive = useCallback((active) => {
|
|
435
|
+
setManageFlowActive(active);
|
|
436
|
+
onFlowActive?.(active);
|
|
437
|
+
}, [onFlowActive]);
|
|
231
438
|
// Animate spinner while a fix is running
|
|
232
439
|
useEffect(() => {
|
|
233
440
|
if (!runningFix)
|
|
@@ -260,8 +467,8 @@ export function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn,
|
|
|
260
467
|
}, [scored, filter]);
|
|
261
468
|
// Keyboard handling
|
|
262
469
|
useInput((input, key) => {
|
|
263
|
-
// Tab navigation —
|
|
264
|
-
if (key.tab && !isFiltering && !confirmingFix && !runningFix) {
|
|
470
|
+
// Tab navigation — blocked during ManageTab destructive flow (WP-324 S2)
|
|
471
|
+
if (key.tab && !isFiltering && !confirmingFix && !runningFix && !manageFlowActive) {
|
|
265
472
|
if (key.shift) {
|
|
266
473
|
setActiveTab((t) => prevTab(t));
|
|
267
474
|
}
|
|
@@ -360,7 +567,7 @@ export function Cockpit({ workspaces, currentKeyWorkspaceId, onExit, onDrillIn,
|
|
|
360
567
|
}), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: ' ' + '─'.repeat(COL_WORKSPACE + COL_ENTRIES + COL_COLS + COL_PARITY + COL_HEALTH + 4) }) }), _jsx(Box, { marginTop: 0, children: isFiltering ? (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: "/ " }), _jsx(TextInput, { value: filter, onChange: setFilter, onSubmit: () => setIsFiltering(false) })] })) : runningFix ? (_jsxs(Text, { color: "cyan", children: [SPINNER_FRAMES[spinnerFrame], " Running fix\u2026"] })) : (_jsxs(Text, { color: "gray", children: [filtered.length, " workspace", filtered.length !== 1 ? 's' : '', filter ? ` matching "${filter}"` : '', ' ', _jsxs(Text, { color: "gray", dimColor: true, children: ['/ filter · ↑↓ nav · Enter drill-in · q quit · ? help', (() => {
|
|
361
568
|
const sel = filtered[clampedIdx];
|
|
362
569
|
return sel?.ws.topIssue && !localFixes.has(sel.ws._id) ? ' · f fix' : '';
|
|
363
|
-
})()] })] })) }), confirmingFix && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Run fix?" }), _jsxs(Text, { color: "white", children: [" ", confirmingFix.cmd.label] }), _jsxs(Text, { dimColor: true, color: "gray", children: [" cmd: pb admin seed run ", confirmingFix.cmd.cmd, " est. ", confirmingFix.cmd.estSeconds, "s"] }), _jsx(Text, { color: "gray", children: " " }), _jsxs(Text, { color: "gray", children: [_jsx(Text, { color: "cyan", children: "[y]" }), _jsx(Text, { color: "gray", children: " yes " }), _jsx(Text, { color: "cyan", children: "[n]" }), _jsx(Text, { color: "gray", children: " no / Escape" })] })] })), showHelp && !confirmingFix && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Keyboard shortcuts" }), _jsx(Text, { color: "gray", children: " Tab / Shift+Tab Switch tabs" }), _jsx(Text, { color: "gray", children: " / Start fuzzy filter" }), _jsx(Text, { color: "gray", children: " Escape Clear filter / exit cockpit" }), _jsx(Text, { color: "gray", children: " \u2191 / \u2193 Navigate rows" }), _jsx(Text, { color: "gray", children: " Enter Drill into selected workspace" }), _jsx(Text, { color: "gray", children: " f Run suggested fix for selected row" }), _jsx(Text, { color: "gray", children: " q Quit cockpit" }), _jsx(Text, { color: "gray", children: " ? Toggle this help panel" }), _jsx(Text, { dimColor: true, color: "gray", children: " Press ? again to close" })] }))] })), activeTab === 'seeds' && (_jsx(SeedsTab, { seedRows: seedRows })), activeTab === 'changelog' && (_jsx(ChangelogTab, { changelogRows: changelogRows, siteUrl: siteUrl }))] }));
|
|
570
|
+
})()] })] })) }), confirmingFix && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "cyan", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Run fix?" }), _jsxs(Text, { color: "white", children: [" ", confirmingFix.cmd.label] }), _jsxs(Text, { dimColor: true, color: "gray", children: [" cmd: pb admin seed run ", confirmingFix.cmd.cmd, " est. ", confirmingFix.cmd.estSeconds, "s"] }), _jsx(Text, { color: "gray", children: " " }), _jsxs(Text, { color: "gray", children: [_jsx(Text, { color: "cyan", children: "[y]" }), _jsx(Text, { color: "gray", children: " yes " }), _jsx(Text, { color: "cyan", children: "[n]" }), _jsx(Text, { color: "gray", children: " no / Escape" })] })] })), showHelp && !confirmingFix && (_jsxs(Box, { flexDirection: "column", marginTop: 1, borderStyle: "single", borderColor: "gray", paddingX: 2, paddingY: 1, children: [_jsx(Text, { bold: true, color: "cyan", children: "Keyboard shortcuts" }), _jsx(Text, { color: "gray", children: " Tab / Shift+Tab Switch tabs" }), _jsx(Text, { color: "gray", children: " / Start fuzzy filter" }), _jsx(Text, { color: "gray", children: " Escape Clear filter / exit cockpit" }), _jsx(Text, { color: "gray", children: " \u2191 / \u2193 Navigate rows" }), _jsx(Text, { color: "gray", children: " Enter Drill into selected workspace" }), _jsx(Text, { color: "gray", children: " f Run suggested fix for selected row" }), _jsx(Text, { color: "gray", children: " q Quit cockpit" }), _jsx(Text, { color: "gray", children: " ? Toggle this help panel" }), _jsx(Text, { dimColor: true, color: "gray", children: " Press ? again to close" })] }))] })), activeTab === 'seeds' && (_jsx(SeedsTab, { seedRows: seedRows })), activeTab === 'changelog' && (_jsx(ChangelogTab, { changelogRows: changelogRows, siteUrl: siteUrl })), activeTab === 'manage' && (_jsx(ManageTab, { onFlowActive: handleManageFlowActive }))] }));
|
|
364
571
|
}
|
|
365
572
|
function statusIcon(ok, warn) {
|
|
366
573
|
if (ok)
|