@spekn/cli 1.0.0 → 1.0.1
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/README.md +58 -0
- package/dist/main.js +3707 -611
- package/dist/tui/index.mjs +2 -2
- package/package.json +29 -12
- package/dist/__tests__/export-cli.test.d.ts +0 -1
- package/dist/__tests__/export-cli.test.js +0 -70
- package/dist/__tests__/tui-args-policy.test.d.ts +0 -1
- package/dist/__tests__/tui-args-policy.test.js +0 -50
- package/dist/acp-S2MHZOAD.mjs +0 -23
- package/dist/acp-UCCI44JY.mjs +0 -25
- package/dist/auth/credentials-store.d.ts +0 -2
- package/dist/auth/credentials-store.js +0 -5
- package/dist/auth/device-flow.d.ts +0 -36
- package/dist/auth/device-flow.js +0 -189
- package/dist/auth/jwt.d.ts +0 -1
- package/dist/auth/jwt.js +0 -6
- package/dist/auth/session.d.ts +0 -67
- package/dist/auth/session.js +0 -86
- package/dist/auth-login.d.ts +0 -34
- package/dist/auth-login.js +0 -202
- package/dist/auth-logout.d.ts +0 -25
- package/dist/auth-logout.js +0 -115
- package/dist/auth-status.d.ts +0 -24
- package/dist/auth-status.js +0 -109
- package/dist/backlog-generate.d.ts +0 -11
- package/dist/backlog-generate.js +0 -308
- package/dist/backlog-health.d.ts +0 -11
- package/dist/backlog-health.js +0 -287
- package/dist/bridge-login.d.ts +0 -40
- package/dist/bridge-login.js +0 -277
- package/dist/chunk-3PAYRI4G.mjs +0 -2428
- package/dist/chunk-M4CS3A25.mjs +0 -2426
- package/dist/commands/auth/login.d.ts +0 -30
- package/dist/commands/auth/login.js +0 -164
- package/dist/commands/auth/logout.d.ts +0 -25
- package/dist/commands/auth/logout.js +0 -115
- package/dist/commands/auth/status.d.ts +0 -24
- package/dist/commands/auth/status.js +0 -109
- package/dist/commands/backlog/generate.d.ts +0 -11
- package/dist/commands/backlog/generate.js +0 -308
- package/dist/commands/backlog/health.d.ts +0 -11
- package/dist/commands/backlog/health.js +0 -287
- package/dist/commands/bridge/login.d.ts +0 -36
- package/dist/commands/bridge/login.js +0 -258
- package/dist/commands/export.d.ts +0 -35
- package/dist/commands/export.js +0 -485
- package/dist/commands/marketplace-export.d.ts +0 -21
- package/dist/commands/marketplace-export.js +0 -214
- package/dist/commands/project-clean.d.ts +0 -1
- package/dist/commands/project-clean.js +0 -126
- package/dist/commands/repo/common.d.ts +0 -105
- package/dist/commands/repo/common.js +0 -775
- package/dist/commands/repo/detach.d.ts +0 -2
- package/dist/commands/repo/detach.js +0 -120
- package/dist/commands/repo/register.d.ts +0 -21
- package/dist/commands/repo/register.js +0 -175
- package/dist/commands/repo/sync.d.ts +0 -22
- package/dist/commands/repo/sync.js +0 -873
- package/dist/commands/skills-import-local.d.ts +0 -16
- package/dist/commands/skills-import-local.js +0 -352
- package/dist/commands/spec/drift-check.d.ts +0 -3
- package/dist/commands/spec/drift-check.js +0 -186
- package/dist/commands/spec/frontmatter.d.ts +0 -11
- package/dist/commands/spec/frontmatter.js +0 -219
- package/dist/commands/spec/lint.d.ts +0 -11
- package/dist/commands/spec/lint.js +0 -499
- package/dist/commands/spec/parse.d.ts +0 -11
- package/dist/commands/spec/parse.js +0 -162
- package/dist/export.d.ts +0 -35
- package/dist/export.js +0 -485
- package/dist/main.d.ts +0 -1
- package/dist/marketplace-export.d.ts +0 -21
- package/dist/marketplace-export.js +0 -214
- package/dist/project-clean.d.ts +0 -1
- package/dist/project-clean.js +0 -126
- package/dist/project-context.d.ts +0 -99
- package/dist/project-context.js +0 -376
- package/dist/repo-common.d.ts +0 -101
- package/dist/repo-common.js +0 -671
- package/dist/repo-detach.d.ts +0 -2
- package/dist/repo-detach.js +0 -102
- package/dist/repo-ingest.d.ts +0 -29
- package/dist/repo-ingest.js +0 -305
- package/dist/repo-register.d.ts +0 -21
- package/dist/repo-register.js +0 -175
- package/dist/repo-sync.d.ts +0 -16
- package/dist/repo-sync.js +0 -152
- package/dist/resources/prompt-loader.d.ts +0 -1
- package/dist/resources/prompt-loader.js +0 -62
- package/dist/skills-import-local.d.ts +0 -16
- package/dist/skills-import-local.js +0 -352
- package/dist/spec-drift-check.d.ts +0 -3
- package/dist/spec-drift-check.js +0 -186
- package/dist/spec-frontmatter.d.ts +0 -11
- package/dist/spec-frontmatter.js +0 -219
- package/dist/spec-lint.d.ts +0 -11
- package/dist/spec-lint.js +0 -499
- package/dist/spec-parse.d.ts +0 -11
- package/dist/spec-parse.js +0 -162
- package/dist/stubs/dotenv.d.ts +0 -5
- package/dist/stubs/dotenv.js +0 -6
- package/dist/stubs/typeorm.d.ts +0 -22
- package/dist/stubs/typeorm.js +0 -28
- package/dist/tui/app.d.ts +0 -7
- package/dist/tui/app.js +0 -122
- package/dist/tui/args.d.ts +0 -8
- package/dist/tui/args.js +0 -57
- package/dist/tui/capabilities/policy.d.ts +0 -7
- package/dist/tui/capabilities/policy.js +0 -64
- package/dist/tui/components/frame.d.ts +0 -8
- package/dist/tui/components/frame.js +0 -8
- package/dist/tui/components/status-bar.d.ts +0 -8
- package/dist/tui/components/status-bar.js +0 -8
- package/dist/tui/index.d.ts +0 -2
- package/dist/tui/index.js +0 -23
- package/dist/tui/keymap/use-global-keymap.d.ts +0 -19
- package/dist/tui/keymap/use-global-keymap.js +0 -82
- package/dist/tui/navigation/nav-items.d.ts +0 -3
- package/dist/tui/navigation/nav-items.js +0 -18
- package/dist/tui/screens/bridge.d.ts +0 -8
- package/dist/tui/screens/bridge.js +0 -19
- package/dist/tui/screens/decisions.d.ts +0 -5
- package/dist/tui/screens/decisions.js +0 -28
- package/dist/tui/screens/export.d.ts +0 -5
- package/dist/tui/screens/export.js +0 -16
- package/dist/tui/screens/home.d.ts +0 -5
- package/dist/tui/screens/home.js +0 -33
- package/dist/tui/screens/locked.d.ts +0 -5
- package/dist/tui/screens/locked.js +0 -9
- package/dist/tui/screens/specs.d.ts +0 -5
- package/dist/tui/screens/specs.js +0 -31
- package/dist/tui/services/client.d.ts +0 -1
- package/dist/tui/services/client.js +0 -18
- package/dist/tui/services/context-service.d.ts +0 -19
- package/dist/tui/services/context-service.js +0 -246
- package/dist/tui/shared-enums.d.ts +0 -16
- package/dist/tui/shared-enums.js +0 -19
- package/dist/tui/state/use-app-state.d.ts +0 -35
- package/dist/tui/state/use-app-state.js +0 -177
- package/dist/tui/types.d.ts +0 -77
- package/dist/tui/types.js +0 -2
- package/dist/tui-bundle.d.ts +0 -1
- package/dist/tui-bundle.js +0 -5
- package/dist/tui-entry.mjs +0 -1407
- package/dist/utils/cli-runtime.d.ts +0 -5
- package/dist/utils/cli-runtime.js +0 -22
- package/dist/utils/help-error.d.ts +0 -7
- package/dist/utils/help-error.js +0 -14
- package/dist/utils/interaction.d.ts +0 -19
- package/dist/utils/interaction.js +0 -93
- package/dist/utils/structured-log.d.ts +0 -7
- package/dist/utils/structured-log.js +0 -112
- package/dist/utils/trpc-url.d.ts +0 -4
- package/dist/utils/trpc-url.js +0 -15
package/dist/tui/screens/home.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.HomeScreen = HomeScreen;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const ink_1 = require("ink");
|
|
6
|
-
const ui_1 = require("@inkjs/ui");
|
|
7
|
-
function nextBestAction(state) {
|
|
8
|
-
if (state.error)
|
|
9
|
-
return 'Fix authentication/context errors first';
|
|
10
|
-
if (!state.exportPreview)
|
|
11
|
-
return 'Generate context export (press 3, then g)';
|
|
12
|
-
if (state.workflow.blockedCount > 0)
|
|
13
|
-
return 'Review blocked workflow states';
|
|
14
|
-
if (!state.workflow.hasPlanningArtifacts)
|
|
15
|
-
return 'Create planning artifacts in PLAN phase';
|
|
16
|
-
if (!state.workflow.hasVerificationEvidence)
|
|
17
|
-
return 'Capture verification evidence';
|
|
18
|
-
return 'Review Decision Log and continue implementation';
|
|
19
|
-
}
|
|
20
|
-
function phaseVariant(state) {
|
|
21
|
-
if (state.workflow.blockedCount > 0)
|
|
22
|
-
return 'warning';
|
|
23
|
-
if (state.workflow.currentPhase)
|
|
24
|
-
return 'info';
|
|
25
|
-
return 'success';
|
|
26
|
-
}
|
|
27
|
-
function HomeScreen({ state }) {
|
|
28
|
-
if (state.loading) {
|
|
29
|
-
return (0, jsx_runtime_1.jsx)(ui_1.Spinner, { label: "Loading dashboard..." });
|
|
30
|
-
}
|
|
31
|
-
const action = nextBestAction(state);
|
|
32
|
-
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, color: "green", children: "Next Best Action" }), (0, jsx_runtime_1.jsx)(ink_1.Text, { children: action }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Workflow" }), (0, jsx_runtime_1.jsxs)(ui_1.StatusMessage, { variant: phaseVariant(state), children: ["Phase: ", state.workflow.currentPhase ?? 'n/a', " | Blocked: ", state.workflow.blockedCount] })] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Unfinished Work" }), (0, jsx_runtime_1.jsxs)(ui_1.UnorderedList, { children: [(0, jsx_runtime_1.jsx)(ui_1.UnorderedList.Item, { children: (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Specs: ", state.specs.length] }) }), (0, jsx_runtime_1.jsx)(ui_1.UnorderedList.Item, { children: (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Decisions: ", state.decisions.length] }) }), (0, jsx_runtime_1.jsx)(ui_1.UnorderedList.Item, { children: (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Export ready: ", state.exportPreview ? 'yes' : 'no'] }) })] })] }), (0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "Quick actions: [2] Specs [3] Export [4] Decisions [5] Bridge" }) })] }));
|
|
33
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.LockedScreen = LockedScreen;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const ink_1 = require("ink");
|
|
6
|
-
const ui_1 = require("@inkjs/ui");
|
|
7
|
-
function LockedScreen({ item }) {
|
|
8
|
-
return ((0, jsx_runtime_1.jsx)(ink_1.Box, { flexDirection: "column", children: (0, jsx_runtime_1.jsxs)(ui_1.Alert, { variant: "warning", children: [item?.label ?? 'Feature', " is locked \u2014 ", item?.reason ?? 'Upgrade required', ". ", item?.description ?? 'Higher-tier feature preview', ". This feature is visible for discoverability but unavailable on your current plan."] }) }));
|
|
9
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.SpecsScreen = SpecsScreen;
|
|
4
|
-
const jsx_runtime_1 = require("react/jsx-runtime");
|
|
5
|
-
const ink_1 = require("ink");
|
|
6
|
-
const ui_1 = require("@inkjs/ui");
|
|
7
|
-
function statusBadgeColor(status) {
|
|
8
|
-
if (status === 'locked')
|
|
9
|
-
return 'green';
|
|
10
|
-
if (status === 'draft')
|
|
11
|
-
return 'yellow';
|
|
12
|
-
if (status === 'review')
|
|
13
|
-
return 'blue';
|
|
14
|
-
if (status === 'archived')
|
|
15
|
-
return 'red';
|
|
16
|
-
return 'yellow';
|
|
17
|
-
}
|
|
18
|
-
function filterItems(items, query) {
|
|
19
|
-
if (!query.trim())
|
|
20
|
-
return items;
|
|
21
|
-
const normalized = query.trim().toLowerCase();
|
|
22
|
-
return items.filter((item) => item.title.toLowerCase().includes(normalized));
|
|
23
|
-
}
|
|
24
|
-
function SpecsScreen({ state }) {
|
|
25
|
-
if (state.loading) {
|
|
26
|
-
return (0, jsx_runtime_1.jsx)(ui_1.Spinner, { label: "Loading specifications..." });
|
|
27
|
-
}
|
|
28
|
-
const specs = filterItems(state.specs, state.searchQuery).slice(0, 7);
|
|
29
|
-
const selected = specs[0];
|
|
30
|
-
return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { bold: true, children: ["Specifications ", state.searchQuery ? `(filter: ${state.searchQuery})` : ''] }), specs.length === 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "No specs in current view." })) : ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsx)(ui_1.UnorderedList, { children: specs.map((spec, index) => ((0, jsx_runtime_1.jsx)(ui_1.UnorderedList.Item, { children: (0, jsx_runtime_1.jsxs)(ink_1.Text, { color: index === 0 ? 'green' : undefined, children: [spec.title, " ", (0, jsx_runtime_1.jsx)(ui_1.Badge, { color: statusBadgeColor(spec.status), children: spec.status }), " v", spec.version] }) }, spec.id))) }), state.specs.length > 7 ? (0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "...showing first 7 specs (Miller's law limit)" }) : null] })), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Anchor Detail" }), selected ? ((0, jsx_runtime_1.jsxs)(jsx_runtime_1.Fragment, { children: [(0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["ID: ", selected.id] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Status: ", selected.status] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Type: ", selected.type ?? 'n/a'] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Updated: ", selected.updatedAt ?? 'n/a'] })] })) : ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "Select a specification to view details." }))] })] }));
|
|
31
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare function createApiClient(apiUrl: string, token: string, organizationId: string): any;
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.createApiClient = createApiClient;
|
|
4
|
-
const client_1 = require("@trpc/client");
|
|
5
|
-
const trpc_url_1 = require("../../utils/trpc-url");
|
|
6
|
-
function createApiClient(apiUrl, token, organizationId) {
|
|
7
|
-
return (0, client_1.createTRPCProxyClient)({
|
|
8
|
-
links: [
|
|
9
|
-
(0, client_1.httpBatchLink)({
|
|
10
|
-
url: (0, trpc_url_1.normalizeTrpcUrl)(apiUrl),
|
|
11
|
-
headers: {
|
|
12
|
-
authorization: token ? `Bearer ${token}` : '',
|
|
13
|
-
'x-organization-id': organizationId,
|
|
14
|
-
},
|
|
15
|
-
}),
|
|
16
|
-
],
|
|
17
|
-
});
|
|
18
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { BridgeSummary, DecisionListItem, ExportFormat, ExportPreview, LocalBridgeSummary, SpecListItem, TuiBootContext, WorkflowSummary } from '../types';
|
|
2
|
-
export declare class TuiContextService {
|
|
3
|
-
private readonly apiUrl;
|
|
4
|
-
private readonly credentialsStore;
|
|
5
|
-
constructor(apiUrl: string);
|
|
6
|
-
bootstrap(projectIdArg?: string): Promise<{
|
|
7
|
-
boot: TuiBootContext;
|
|
8
|
-
client: any;
|
|
9
|
-
}>;
|
|
10
|
-
loadSpecs(client: any, projectId: string): Promise<SpecListItem[]>;
|
|
11
|
-
loadDecisions(client: any, projectId: string): Promise<DecisionListItem[]>;
|
|
12
|
-
loadWorkflowSummary(client: any, projectId: string): Promise<WorkflowSummary>;
|
|
13
|
-
previewExport(client: any, projectId: string, format: ExportFormat): Promise<ExportPreview>;
|
|
14
|
-
generateExport(client: any, projectId: string, format: ExportFormat): Promise<ExportPreview>;
|
|
15
|
-
loadBridgeSummary(client: any): Promise<BridgeSummary>;
|
|
16
|
-
loadLocalBridgeSummary(): Promise<LocalBridgeSummary>;
|
|
17
|
-
startLocalBridgeDetached(): void;
|
|
18
|
-
stopLocalBridge(configPort?: number): Promise<void>;
|
|
19
|
-
}
|
|
@@ -1,246 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.TuiContextService = void 0;
|
|
7
|
-
const node_child_process_1 = require("node:child_process");
|
|
8
|
-
const node_fs_1 = __importDefault(require("node:fs"));
|
|
9
|
-
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
-
const node_path_1 = __importDefault(require("node:path"));
|
|
11
|
-
const shared_enums_1 = require("../shared-enums");
|
|
12
|
-
const credentials_store_1 = require("../../auth/credentials-store");
|
|
13
|
-
const client_1 = require("./client");
|
|
14
|
-
const DEFAULT_BRIDGE_CONFIG = {
|
|
15
|
-
port: 19550,
|
|
16
|
-
pairing: null,
|
|
17
|
-
};
|
|
18
|
-
function loadLocalBridgeConfig() {
|
|
19
|
-
const configPath = node_path_1.default.join(node_os_1.default.homedir(), '.spekn', 'bridge', 'config.json');
|
|
20
|
-
try {
|
|
21
|
-
const raw = node_fs_1.default.readFileSync(configPath, 'utf-8');
|
|
22
|
-
const parsed = JSON.parse(raw);
|
|
23
|
-
return {
|
|
24
|
-
port: typeof parsed.port === 'number' ? parsed.port : DEFAULT_BRIDGE_CONFIG.port,
|
|
25
|
-
pairing: parsed.pairing ?? DEFAULT_BRIDGE_CONFIG.pairing,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
catch {
|
|
29
|
-
return DEFAULT_BRIDGE_CONFIG;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
function decodeJwtPayload(token) {
|
|
33
|
-
try {
|
|
34
|
-
const parts = token.split('.');
|
|
35
|
-
if (parts.length !== 3)
|
|
36
|
-
return null;
|
|
37
|
-
const base64 = parts[1].replace(/-/g, '+').replace(/_/g, '/');
|
|
38
|
-
const padded = base64.padEnd(base64.length + ((4 - (base64.length % 4)) % 4), '=');
|
|
39
|
-
const json = Buffer.from(padded, 'base64').toString('utf-8');
|
|
40
|
-
return JSON.parse(json);
|
|
41
|
-
}
|
|
42
|
-
catch {
|
|
43
|
-
return null;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
function normalizePlan(raw) {
|
|
47
|
-
if (raw === shared_enums_1.OrganizationPlan.PRO)
|
|
48
|
-
return shared_enums_1.OrganizationPlan.PRO;
|
|
49
|
-
if (raw === shared_enums_1.OrganizationPlan.TEAM)
|
|
50
|
-
return shared_enums_1.OrganizationPlan.TEAM;
|
|
51
|
-
if (raw === shared_enums_1.OrganizationPlan.ENTERPRISE)
|
|
52
|
-
return shared_enums_1.OrganizationPlan.ENTERPRISE;
|
|
53
|
-
return shared_enums_1.OrganizationPlan.FREE;
|
|
54
|
-
}
|
|
55
|
-
function normalizeRole(raw) {
|
|
56
|
-
if (raw === 'owner' || raw === 'admin' || raw === 'member' || raw === 'viewer') {
|
|
57
|
-
return raw;
|
|
58
|
-
}
|
|
59
|
-
return 'member';
|
|
60
|
-
}
|
|
61
|
-
class TuiContextService {
|
|
62
|
-
apiUrl;
|
|
63
|
-
credentialsStore = new credentials_store_1.CredentialsStore();
|
|
64
|
-
constructor(apiUrl) {
|
|
65
|
-
this.apiUrl = apiUrl;
|
|
66
|
-
}
|
|
67
|
-
async bootstrap(projectIdArg) {
|
|
68
|
-
const token = await this.credentialsStore.getValidToken();
|
|
69
|
-
if (!token) {
|
|
70
|
-
throw new Error('No valid credentials. Run `spekn auth login` first.');
|
|
71
|
-
}
|
|
72
|
-
const claims = decodeJwtPayload(token);
|
|
73
|
-
const permissions = Array.isArray(claims?.permissions)
|
|
74
|
-
? claims?.permissions.filter((item) => typeof item === 'string')
|
|
75
|
-
: [];
|
|
76
|
-
const stored = this.credentialsStore.load();
|
|
77
|
-
const fallbackOrg = stored?.organizationId ?? process.env.SPEKN_ORGANIZATION_ID ?? '';
|
|
78
|
-
const bootstrapClient = (0, client_1.createApiClient)(this.apiUrl, token, fallbackOrg);
|
|
79
|
-
const orgs = await bootstrapClient.organization.list.query();
|
|
80
|
-
if (orgs.length === 0) {
|
|
81
|
-
throw new Error('No organization membership found for this account.');
|
|
82
|
-
}
|
|
83
|
-
const org = orgs.find((candidate) => candidate.id === fallbackOrg) ?? orgs[0];
|
|
84
|
-
const organizationId = org.id;
|
|
85
|
-
const client = (0, client_1.createApiClient)(this.apiUrl, token, organizationId);
|
|
86
|
-
const projects = await client.project.list.query({
|
|
87
|
-
limit: 20,
|
|
88
|
-
offset: 0,
|
|
89
|
-
});
|
|
90
|
-
if (projects.length === 0) {
|
|
91
|
-
throw new Error('No projects found for this organization. Create one in Spekn first.');
|
|
92
|
-
}
|
|
93
|
-
const project = projects.find((candidate) => candidate.id === projectIdArg) ?? projects[0];
|
|
94
|
-
return {
|
|
95
|
-
boot: {
|
|
96
|
-
apiUrl: this.apiUrl,
|
|
97
|
-
organizationId,
|
|
98
|
-
organizationName: org.name,
|
|
99
|
-
role: normalizeRole(org.role),
|
|
100
|
-
plan: normalizePlan(org.plan),
|
|
101
|
-
projectId: project.id,
|
|
102
|
-
projectName: project.name,
|
|
103
|
-
permissions,
|
|
104
|
-
},
|
|
105
|
-
client,
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
async loadSpecs(client, projectId) {
|
|
109
|
-
const specs = await client.specification.list.query({
|
|
110
|
-
projectId,
|
|
111
|
-
limit: 50,
|
|
112
|
-
offset: 0,
|
|
113
|
-
});
|
|
114
|
-
return (Array.isArray(specs) ? specs : []).map((spec) => ({
|
|
115
|
-
id: spec.id,
|
|
116
|
-
title: spec.title,
|
|
117
|
-
status: spec.status,
|
|
118
|
-
version: spec.version,
|
|
119
|
-
updatedAt: spec.updatedAt,
|
|
120
|
-
type: spec.frontmatter?.type,
|
|
121
|
-
}));
|
|
122
|
-
}
|
|
123
|
-
async loadDecisions(client, projectId) {
|
|
124
|
-
const result = await client.decision.getAll.query({
|
|
125
|
-
projectId,
|
|
126
|
-
limit: 50,
|
|
127
|
-
offset: 0,
|
|
128
|
-
});
|
|
129
|
-
const decisions = Array.isArray(result?.decisions) ? result.decisions : [];
|
|
130
|
-
return decisions.map((decision) => ({
|
|
131
|
-
id: decision.id,
|
|
132
|
-
title: decision.title,
|
|
133
|
-
status: decision.status,
|
|
134
|
-
decisionType: decision.decisionType,
|
|
135
|
-
specAnchor: decision.specAnchor,
|
|
136
|
-
createdAt: decision.createdAt,
|
|
137
|
-
}));
|
|
138
|
-
}
|
|
139
|
-
async loadWorkflowSummary(client, projectId) {
|
|
140
|
-
const states = await client.workflowState.listByProject.query({ projectId });
|
|
141
|
-
const first = Array.isArray(states) && states.length > 0 ? states[0] : null;
|
|
142
|
-
const currentPhase = first?.currentPhase ?? null;
|
|
143
|
-
const blockedCount = Array.isArray(states)
|
|
144
|
-
? states.filter((state) => state.specificationLockStatus === 'locked').length
|
|
145
|
-
: 0;
|
|
146
|
-
return {
|
|
147
|
-
currentPhase,
|
|
148
|
-
blockedCount,
|
|
149
|
-
hasVerificationEvidence: Boolean(first?.hasVerificationEvidence),
|
|
150
|
-
hasPlanningArtifacts: Boolean(first?.hasPlanningArtifacts),
|
|
151
|
-
};
|
|
152
|
-
}
|
|
153
|
-
async previewExport(client, projectId, format) {
|
|
154
|
-
const result = await client.export.preview.query({
|
|
155
|
-
projectId,
|
|
156
|
-
formatId: format,
|
|
157
|
-
});
|
|
158
|
-
return {
|
|
159
|
-
content: String(result.content ?? ''),
|
|
160
|
-
anchorCount: Number(result.anchorCount ?? 0),
|
|
161
|
-
specVersion: typeof result.specVersion === 'string' ? result.specVersion : undefined,
|
|
162
|
-
warnings: Array.isArray(result.warnings)
|
|
163
|
-
? result.warnings.filter((warning) => typeof warning === 'string')
|
|
164
|
-
: undefined,
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
async generateExport(client, projectId, format) {
|
|
168
|
-
const result = await client.export.generate.mutate({
|
|
169
|
-
projectId,
|
|
170
|
-
formatId: format,
|
|
171
|
-
});
|
|
172
|
-
return {
|
|
173
|
-
content: String(result.content ?? ''),
|
|
174
|
-
anchorCount: Number(result.anchorCount ?? 0),
|
|
175
|
-
specVersion: typeof result.specVersion === 'string' ? result.specVersion : undefined,
|
|
176
|
-
warnings: Array.isArray(result.warnings)
|
|
177
|
-
? result.warnings.filter((warning) => typeof warning === 'string')
|
|
178
|
-
: undefined,
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
async loadBridgeSummary(client) {
|
|
182
|
-
const [flag, devices, metrics] = await Promise.all([
|
|
183
|
-
client.bridge.getFeatureFlag.query().catch(() => ({ enabled: false })),
|
|
184
|
-
client.bridge.listDevices.query().catch(() => []),
|
|
185
|
-
client.bridge.getMetrics.query().catch(() => ({ connectedDevices: 0, authFailures: 0 })),
|
|
186
|
-
]);
|
|
187
|
-
return {
|
|
188
|
-
featureEnabled: Boolean(flag.enabled),
|
|
189
|
-
devices: Array.isArray(devices)
|
|
190
|
-
? devices.map((device) => ({
|
|
191
|
-
id: device.id,
|
|
192
|
-
name: device.name,
|
|
193
|
-
status: device.status,
|
|
194
|
-
isDefault: Boolean(device.isDefault),
|
|
195
|
-
lastSeenAt: device.lastSeenAt,
|
|
196
|
-
}))
|
|
197
|
-
: [],
|
|
198
|
-
connectedDevices: Number(metrics.connectedDevices ?? 0),
|
|
199
|
-
authFailures: Number(metrics.authFailures ?? 0),
|
|
200
|
-
};
|
|
201
|
-
}
|
|
202
|
-
async loadLocalBridgeSummary() {
|
|
203
|
-
const config = loadLocalBridgeConfig();
|
|
204
|
-
let running = false;
|
|
205
|
-
let uptimeSec;
|
|
206
|
-
try {
|
|
207
|
-
const response = await fetch(`http://127.0.0.1:${config.port}/health`);
|
|
208
|
-
if (response.ok) {
|
|
209
|
-
const payload = (await response.json());
|
|
210
|
-
running = true;
|
|
211
|
-
uptimeSec = Number(payload.uptime ?? 0);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
catch {
|
|
215
|
-
running = false;
|
|
216
|
-
}
|
|
217
|
-
return {
|
|
218
|
-
paired: config.pairing !== null,
|
|
219
|
-
deviceId: config.pairing?.deviceId,
|
|
220
|
-
deviceName: config.pairing?.deviceName,
|
|
221
|
-
port: config.port,
|
|
222
|
-
running,
|
|
223
|
-
uptimeSec,
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
startLocalBridgeDetached() {
|
|
227
|
-
const args = [process.argv[1] ?? '', 'bridge', 'start'];
|
|
228
|
-
const child = (0, node_child_process_1.spawn)(process.execPath, args, {
|
|
229
|
-
detached: true,
|
|
230
|
-
stdio: 'ignore',
|
|
231
|
-
});
|
|
232
|
-
child.unref();
|
|
233
|
-
}
|
|
234
|
-
async stopLocalBridge(configPort) {
|
|
235
|
-
const port = configPort ?? this.bridgeConfigStore.get().port;
|
|
236
|
-
try {
|
|
237
|
-
await fetch(`http://127.0.0.1:${port}/shutdown`, {
|
|
238
|
-
method: 'POST',
|
|
239
|
-
});
|
|
240
|
-
}
|
|
241
|
-
catch {
|
|
242
|
-
// best effort; bridge may already be down
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
exports.TuiContextService = TuiContextService;
|
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export declare const OrganizationPlan: {
|
|
2
|
-
readonly FREE: "free";
|
|
3
|
-
readonly PRO: "pro";
|
|
4
|
-
readonly TEAM: "team";
|
|
5
|
-
readonly ENTERPRISE: "enterprise";
|
|
6
|
-
};
|
|
7
|
-
export type OrganizationPlan = (typeof OrganizationPlan)[keyof typeof OrganizationPlan];
|
|
8
|
-
export declare const WorkflowPhase: {
|
|
9
|
-
readonly SPECIFY: "specify";
|
|
10
|
-
readonly CLARIFY: "clarify";
|
|
11
|
-
readonly PLAN: "plan";
|
|
12
|
-
readonly IMPLEMENT: "implement";
|
|
13
|
-
readonly VERIFY: "verify";
|
|
14
|
-
readonly COMPLETE: "complete";
|
|
15
|
-
};
|
|
16
|
-
export type WorkflowPhase = (typeof WorkflowPhase)[keyof typeof WorkflowPhase];
|
package/dist/tui/shared-enums.js
DELETED
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.WorkflowPhase = exports.OrganizationPlan = void 0;
|
|
4
|
-
// Keep lightweight enum values local to the TUI bundle so we don't pull
|
|
5
|
-
// @spekn/shared's TypeORM-backed index into ESM runtime code.
|
|
6
|
-
exports.OrganizationPlan = {
|
|
7
|
-
FREE: 'free',
|
|
8
|
-
PRO: 'pro',
|
|
9
|
-
TEAM: 'team',
|
|
10
|
-
ENTERPRISE: 'enterprise',
|
|
11
|
-
};
|
|
12
|
-
exports.WorkflowPhase = {
|
|
13
|
-
SPECIFY: 'specify',
|
|
14
|
-
CLARIFY: 'clarify',
|
|
15
|
-
PLAN: 'plan',
|
|
16
|
-
IMPLEMENT: 'implement',
|
|
17
|
-
VERIFY: 'verify',
|
|
18
|
-
COMPLETE: 'complete',
|
|
19
|
-
};
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import type { BridgeSummary, DecisionListItem, ExportFormat, ExportPreview, LocalBridgeSummary, NavItemPolicy, SpecListItem, TuiBootContext, TuiScreenId, WorkflowSummary } from '../types';
|
|
2
|
-
export interface TuiState {
|
|
3
|
-
boot: TuiBootContext | null;
|
|
4
|
-
client: any | null;
|
|
5
|
-
loading: boolean;
|
|
6
|
-
error: string | null;
|
|
7
|
-
screen: TuiScreenId;
|
|
8
|
-
navPolicy: NavItemPolicy[];
|
|
9
|
-
specs: SpecListItem[];
|
|
10
|
-
decisions: DecisionListItem[];
|
|
11
|
-
workflow: WorkflowSummary;
|
|
12
|
-
bridge: BridgeSummary | null;
|
|
13
|
-
localBridge: LocalBridgeSummary | null;
|
|
14
|
-
exportFormat: ExportFormat;
|
|
15
|
-
exportPreview: ExportPreview | null;
|
|
16
|
-
statusLine: string;
|
|
17
|
-
logs: string[];
|
|
18
|
-
searchQuery: string;
|
|
19
|
-
showHelp: boolean;
|
|
20
|
-
commandMode: boolean;
|
|
21
|
-
}
|
|
22
|
-
export declare function useAppState(apiUrl: string, initialScreen: TuiScreenId, projectId?: string): {
|
|
23
|
-
state: TuiState;
|
|
24
|
-
refresh: () => Promise<void>;
|
|
25
|
-
setScreen: (screen: TuiScreenId) => void;
|
|
26
|
-
toggleHelp: () => void;
|
|
27
|
-
setSearchQuery: (searchQuery: string) => void;
|
|
28
|
-
setCommandMode: (commandMode: boolean) => void;
|
|
29
|
-
setExportFormat: (exportFormat: ExportFormat) => void;
|
|
30
|
-
previewExport: () => Promise<void>;
|
|
31
|
-
generateExport: () => Promise<void>;
|
|
32
|
-
bridgeStart: () => void;
|
|
33
|
-
bridgeStop: () => Promise<void>;
|
|
34
|
-
appendLog: (entry: string) => void;
|
|
35
|
-
};
|
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.useAppState = useAppState;
|
|
4
|
-
const react_1 = require("react");
|
|
5
|
-
const policy_1 = require("../capabilities/policy");
|
|
6
|
-
const context_service_1 = require("../services/context-service");
|
|
7
|
-
const EMPTY_WORKFLOW = {
|
|
8
|
-
currentPhase: null,
|
|
9
|
-
blockedCount: 0,
|
|
10
|
-
hasPlanningArtifacts: false,
|
|
11
|
-
hasVerificationEvidence: false,
|
|
12
|
-
};
|
|
13
|
-
function useAppState(apiUrl, initialScreen, projectId) {
|
|
14
|
-
const service = (0, react_1.useMemo)(() => new context_service_1.TuiContextService(apiUrl), [apiUrl]);
|
|
15
|
-
const [state, setState] = (0, react_1.useState)({
|
|
16
|
-
boot: null,
|
|
17
|
-
client: null,
|
|
18
|
-
loading: true,
|
|
19
|
-
error: null,
|
|
20
|
-
screen: initialScreen,
|
|
21
|
-
navPolicy: [],
|
|
22
|
-
specs: [],
|
|
23
|
-
decisions: [],
|
|
24
|
-
workflow: EMPTY_WORKFLOW,
|
|
25
|
-
bridge: null,
|
|
26
|
-
localBridge: null,
|
|
27
|
-
exportFormat: 'claude-md',
|
|
28
|
-
exportPreview: null,
|
|
29
|
-
statusLine: 'Bootstrapping...',
|
|
30
|
-
logs: [],
|
|
31
|
-
searchQuery: '',
|
|
32
|
-
showHelp: false,
|
|
33
|
-
commandMode: false,
|
|
34
|
-
});
|
|
35
|
-
const appendLog = (0, react_1.useCallback)((entry) => {
|
|
36
|
-
setState((prev) => ({ ...prev, logs: [entry, ...prev.logs].slice(0, 40) }));
|
|
37
|
-
}, []);
|
|
38
|
-
const refresh = (0, react_1.useCallback)(async () => {
|
|
39
|
-
setState((prev) => ({ ...prev, loading: true, statusLine: 'Loading context...' }));
|
|
40
|
-
try {
|
|
41
|
-
const { boot, client } = await service.bootstrap(projectId);
|
|
42
|
-
const [specs, decisions, workflow, bridge, localBridge] = await Promise.all([
|
|
43
|
-
service.loadSpecs(client, boot.projectId),
|
|
44
|
-
service.loadDecisions(client, boot.projectId),
|
|
45
|
-
service.loadWorkflowSummary(client, boot.projectId),
|
|
46
|
-
service.loadBridgeSummary(client),
|
|
47
|
-
service.loadLocalBridgeSummary(),
|
|
48
|
-
]);
|
|
49
|
-
const capabilityContext = {
|
|
50
|
-
plan: boot.plan,
|
|
51
|
-
role: boot.role,
|
|
52
|
-
workflowPhase: workflow.currentPhase,
|
|
53
|
-
permissions: boot.permissions,
|
|
54
|
-
};
|
|
55
|
-
const navPolicy = (0, policy_1.resolveNavPolicy)(capabilityContext);
|
|
56
|
-
setState((prev) => ({
|
|
57
|
-
...prev,
|
|
58
|
-
boot,
|
|
59
|
-
client,
|
|
60
|
-
specs,
|
|
61
|
-
decisions,
|
|
62
|
-
workflow,
|
|
63
|
-
bridge,
|
|
64
|
-
localBridge,
|
|
65
|
-
navPolicy,
|
|
66
|
-
loading: false,
|
|
67
|
-
error: null,
|
|
68
|
-
statusLine: 'Ready',
|
|
69
|
-
}));
|
|
70
|
-
}
|
|
71
|
-
catch (error) {
|
|
72
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
73
|
-
setState((prev) => ({ ...prev, loading: false, error: message, statusLine: 'Error' }));
|
|
74
|
-
appendLog(`[error] ${message}`);
|
|
75
|
-
}
|
|
76
|
-
}, [appendLog, projectId, service]);
|
|
77
|
-
(0, react_1.useEffect)(() => {
|
|
78
|
-
void refresh();
|
|
79
|
-
}, [refresh]);
|
|
80
|
-
(0, react_1.useEffect)(() => {
|
|
81
|
-
const timer = setInterval(() => {
|
|
82
|
-
if (!state.client || !state.boot)
|
|
83
|
-
return;
|
|
84
|
-
void Promise.all([
|
|
85
|
-
service.loadWorkflowSummary(state.client, state.boot.projectId),
|
|
86
|
-
service.loadLocalBridgeSummary(),
|
|
87
|
-
])
|
|
88
|
-
.then(([workflow, localBridge]) => {
|
|
89
|
-
setState((prev) => {
|
|
90
|
-
const ctx = {
|
|
91
|
-
plan: prev.boot?.plan ?? prev.navPolicy[0]?.requiredPlan ?? (prev.boot?.plan ?? 'free'),
|
|
92
|
-
role: prev.boot?.role ?? 'member',
|
|
93
|
-
workflowPhase: workflow.currentPhase,
|
|
94
|
-
permissions: prev.boot?.permissions ?? [],
|
|
95
|
-
};
|
|
96
|
-
return {
|
|
97
|
-
...prev,
|
|
98
|
-
workflow,
|
|
99
|
-
localBridge,
|
|
100
|
-
navPolicy: (0, policy_1.resolveNavPolicy)(ctx),
|
|
101
|
-
};
|
|
102
|
-
});
|
|
103
|
-
})
|
|
104
|
-
.catch(() => undefined);
|
|
105
|
-
}, 6000);
|
|
106
|
-
return () => clearInterval(timer);
|
|
107
|
-
}, [service, state.boot, state.client, state.navPolicy]);
|
|
108
|
-
const setScreen = (0, react_1.useCallback)((screen) => {
|
|
109
|
-
setState((prev) => ({ ...prev, screen }));
|
|
110
|
-
}, []);
|
|
111
|
-
const toggleHelp = (0, react_1.useCallback)(() => {
|
|
112
|
-
setState((prev) => ({ ...prev, showHelp: !prev.showHelp }));
|
|
113
|
-
}, []);
|
|
114
|
-
const setSearchQuery = (0, react_1.useCallback)((searchQuery) => {
|
|
115
|
-
setState((prev) => ({ ...prev, searchQuery }));
|
|
116
|
-
}, []);
|
|
117
|
-
const setCommandMode = (0, react_1.useCallback)((commandMode) => {
|
|
118
|
-
setState((prev) => ({ ...prev, commandMode }));
|
|
119
|
-
}, []);
|
|
120
|
-
const setExportFormat = (0, react_1.useCallback)((exportFormat) => {
|
|
121
|
-
setState((prev) => ({ ...prev, exportFormat }));
|
|
122
|
-
}, []);
|
|
123
|
-
const previewExport = (0, react_1.useCallback)(async () => {
|
|
124
|
-
if (!state.client || !state.boot)
|
|
125
|
-
return;
|
|
126
|
-
setState((prev) => ({ ...prev, statusLine: 'Previewing export...' }));
|
|
127
|
-
try {
|
|
128
|
-
const preview = await service.previewExport(state.client, state.boot.projectId, state.exportFormat);
|
|
129
|
-
setState((prev) => ({ ...prev, exportPreview: preview, statusLine: 'Export preview ready' }));
|
|
130
|
-
appendLog(`[export] Previewed ${state.exportFormat} (${preview.anchorCount} anchors)`);
|
|
131
|
-
}
|
|
132
|
-
catch (error) {
|
|
133
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
134
|
-
appendLog(`[error] Export preview failed: ${message}`);
|
|
135
|
-
setState((prev) => ({ ...prev, statusLine: 'Export preview failed' }));
|
|
136
|
-
}
|
|
137
|
-
}, [appendLog, service, state.boot, state.client, state.exportFormat]);
|
|
138
|
-
const generateExport = (0, react_1.useCallback)(async () => {
|
|
139
|
-
if (!state.client || !state.boot)
|
|
140
|
-
return;
|
|
141
|
-
setState((prev) => ({ ...prev, statusLine: 'Generating export...' }));
|
|
142
|
-
try {
|
|
143
|
-
const output = await service.generateExport(state.client, state.boot.projectId, state.exportFormat);
|
|
144
|
-
setState((prev) => ({ ...prev, exportPreview: output, statusLine: 'Export generated' }));
|
|
145
|
-
appendLog(`[export] Generated ${state.exportFormat} (${output.anchorCount} anchors)`);
|
|
146
|
-
}
|
|
147
|
-
catch (error) {
|
|
148
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
-
appendLog(`[error] Export generation failed: ${message}`);
|
|
150
|
-
setState((prev) => ({ ...prev, statusLine: 'Export generation failed' }));
|
|
151
|
-
}
|
|
152
|
-
}, [appendLog, service, state.boot, state.client, state.exportFormat]);
|
|
153
|
-
const bridgeStart = (0, react_1.useCallback)(() => {
|
|
154
|
-
service.startLocalBridgeDetached();
|
|
155
|
-
appendLog('[bridge] Started local bridge process (detached)');
|
|
156
|
-
setState((prev) => ({ ...prev, statusLine: 'Bridge start triggered (detached)' }));
|
|
157
|
-
}, [appendLog, service]);
|
|
158
|
-
const bridgeStop = (0, react_1.useCallback)(async () => {
|
|
159
|
-
await service.stopLocalBridge(state.localBridge?.port);
|
|
160
|
-
appendLog('[bridge] Stop signal sent');
|
|
161
|
-
setState((prev) => ({ ...prev, statusLine: 'Bridge stop signal sent' }));
|
|
162
|
-
}, [appendLog, service, state.localBridge?.port]);
|
|
163
|
-
return {
|
|
164
|
-
state,
|
|
165
|
-
refresh,
|
|
166
|
-
setScreen,
|
|
167
|
-
toggleHelp,
|
|
168
|
-
setSearchQuery,
|
|
169
|
-
setCommandMode,
|
|
170
|
-
setExportFormat,
|
|
171
|
-
previewExport,
|
|
172
|
-
generateExport,
|
|
173
|
-
bridgeStart,
|
|
174
|
-
bridgeStop,
|
|
175
|
-
appendLog,
|
|
176
|
-
};
|
|
177
|
-
}
|