@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.
Files changed (154) hide show
  1. package/README.md +58 -0
  2. package/dist/main.js +3707 -611
  3. package/dist/tui/index.mjs +2 -2
  4. package/package.json +29 -12
  5. package/dist/__tests__/export-cli.test.d.ts +0 -1
  6. package/dist/__tests__/export-cli.test.js +0 -70
  7. package/dist/__tests__/tui-args-policy.test.d.ts +0 -1
  8. package/dist/__tests__/tui-args-policy.test.js +0 -50
  9. package/dist/acp-S2MHZOAD.mjs +0 -23
  10. package/dist/acp-UCCI44JY.mjs +0 -25
  11. package/dist/auth/credentials-store.d.ts +0 -2
  12. package/dist/auth/credentials-store.js +0 -5
  13. package/dist/auth/device-flow.d.ts +0 -36
  14. package/dist/auth/device-flow.js +0 -189
  15. package/dist/auth/jwt.d.ts +0 -1
  16. package/dist/auth/jwt.js +0 -6
  17. package/dist/auth/session.d.ts +0 -67
  18. package/dist/auth/session.js +0 -86
  19. package/dist/auth-login.d.ts +0 -34
  20. package/dist/auth-login.js +0 -202
  21. package/dist/auth-logout.d.ts +0 -25
  22. package/dist/auth-logout.js +0 -115
  23. package/dist/auth-status.d.ts +0 -24
  24. package/dist/auth-status.js +0 -109
  25. package/dist/backlog-generate.d.ts +0 -11
  26. package/dist/backlog-generate.js +0 -308
  27. package/dist/backlog-health.d.ts +0 -11
  28. package/dist/backlog-health.js +0 -287
  29. package/dist/bridge-login.d.ts +0 -40
  30. package/dist/bridge-login.js +0 -277
  31. package/dist/chunk-3PAYRI4G.mjs +0 -2428
  32. package/dist/chunk-M4CS3A25.mjs +0 -2426
  33. package/dist/commands/auth/login.d.ts +0 -30
  34. package/dist/commands/auth/login.js +0 -164
  35. package/dist/commands/auth/logout.d.ts +0 -25
  36. package/dist/commands/auth/logout.js +0 -115
  37. package/dist/commands/auth/status.d.ts +0 -24
  38. package/dist/commands/auth/status.js +0 -109
  39. package/dist/commands/backlog/generate.d.ts +0 -11
  40. package/dist/commands/backlog/generate.js +0 -308
  41. package/dist/commands/backlog/health.d.ts +0 -11
  42. package/dist/commands/backlog/health.js +0 -287
  43. package/dist/commands/bridge/login.d.ts +0 -36
  44. package/dist/commands/bridge/login.js +0 -258
  45. package/dist/commands/export.d.ts +0 -35
  46. package/dist/commands/export.js +0 -485
  47. package/dist/commands/marketplace-export.d.ts +0 -21
  48. package/dist/commands/marketplace-export.js +0 -214
  49. package/dist/commands/project-clean.d.ts +0 -1
  50. package/dist/commands/project-clean.js +0 -126
  51. package/dist/commands/repo/common.d.ts +0 -105
  52. package/dist/commands/repo/common.js +0 -775
  53. package/dist/commands/repo/detach.d.ts +0 -2
  54. package/dist/commands/repo/detach.js +0 -120
  55. package/dist/commands/repo/register.d.ts +0 -21
  56. package/dist/commands/repo/register.js +0 -175
  57. package/dist/commands/repo/sync.d.ts +0 -22
  58. package/dist/commands/repo/sync.js +0 -873
  59. package/dist/commands/skills-import-local.d.ts +0 -16
  60. package/dist/commands/skills-import-local.js +0 -352
  61. package/dist/commands/spec/drift-check.d.ts +0 -3
  62. package/dist/commands/spec/drift-check.js +0 -186
  63. package/dist/commands/spec/frontmatter.d.ts +0 -11
  64. package/dist/commands/spec/frontmatter.js +0 -219
  65. package/dist/commands/spec/lint.d.ts +0 -11
  66. package/dist/commands/spec/lint.js +0 -499
  67. package/dist/commands/spec/parse.d.ts +0 -11
  68. package/dist/commands/spec/parse.js +0 -162
  69. package/dist/export.d.ts +0 -35
  70. package/dist/export.js +0 -485
  71. package/dist/main.d.ts +0 -1
  72. package/dist/marketplace-export.d.ts +0 -21
  73. package/dist/marketplace-export.js +0 -214
  74. package/dist/project-clean.d.ts +0 -1
  75. package/dist/project-clean.js +0 -126
  76. package/dist/project-context.d.ts +0 -99
  77. package/dist/project-context.js +0 -376
  78. package/dist/repo-common.d.ts +0 -101
  79. package/dist/repo-common.js +0 -671
  80. package/dist/repo-detach.d.ts +0 -2
  81. package/dist/repo-detach.js +0 -102
  82. package/dist/repo-ingest.d.ts +0 -29
  83. package/dist/repo-ingest.js +0 -305
  84. package/dist/repo-register.d.ts +0 -21
  85. package/dist/repo-register.js +0 -175
  86. package/dist/repo-sync.d.ts +0 -16
  87. package/dist/repo-sync.js +0 -152
  88. package/dist/resources/prompt-loader.d.ts +0 -1
  89. package/dist/resources/prompt-loader.js +0 -62
  90. package/dist/skills-import-local.d.ts +0 -16
  91. package/dist/skills-import-local.js +0 -352
  92. package/dist/spec-drift-check.d.ts +0 -3
  93. package/dist/spec-drift-check.js +0 -186
  94. package/dist/spec-frontmatter.d.ts +0 -11
  95. package/dist/spec-frontmatter.js +0 -219
  96. package/dist/spec-lint.d.ts +0 -11
  97. package/dist/spec-lint.js +0 -499
  98. package/dist/spec-parse.d.ts +0 -11
  99. package/dist/spec-parse.js +0 -162
  100. package/dist/stubs/dotenv.d.ts +0 -5
  101. package/dist/stubs/dotenv.js +0 -6
  102. package/dist/stubs/typeorm.d.ts +0 -22
  103. package/dist/stubs/typeorm.js +0 -28
  104. package/dist/tui/app.d.ts +0 -7
  105. package/dist/tui/app.js +0 -122
  106. package/dist/tui/args.d.ts +0 -8
  107. package/dist/tui/args.js +0 -57
  108. package/dist/tui/capabilities/policy.d.ts +0 -7
  109. package/dist/tui/capabilities/policy.js +0 -64
  110. package/dist/tui/components/frame.d.ts +0 -8
  111. package/dist/tui/components/frame.js +0 -8
  112. package/dist/tui/components/status-bar.d.ts +0 -8
  113. package/dist/tui/components/status-bar.js +0 -8
  114. package/dist/tui/index.d.ts +0 -2
  115. package/dist/tui/index.js +0 -23
  116. package/dist/tui/keymap/use-global-keymap.d.ts +0 -19
  117. package/dist/tui/keymap/use-global-keymap.js +0 -82
  118. package/dist/tui/navigation/nav-items.d.ts +0 -3
  119. package/dist/tui/navigation/nav-items.js +0 -18
  120. package/dist/tui/screens/bridge.d.ts +0 -8
  121. package/dist/tui/screens/bridge.js +0 -19
  122. package/dist/tui/screens/decisions.d.ts +0 -5
  123. package/dist/tui/screens/decisions.js +0 -28
  124. package/dist/tui/screens/export.d.ts +0 -5
  125. package/dist/tui/screens/export.js +0 -16
  126. package/dist/tui/screens/home.d.ts +0 -5
  127. package/dist/tui/screens/home.js +0 -33
  128. package/dist/tui/screens/locked.d.ts +0 -5
  129. package/dist/tui/screens/locked.js +0 -9
  130. package/dist/tui/screens/specs.d.ts +0 -5
  131. package/dist/tui/screens/specs.js +0 -31
  132. package/dist/tui/services/client.d.ts +0 -1
  133. package/dist/tui/services/client.js +0 -18
  134. package/dist/tui/services/context-service.d.ts +0 -19
  135. package/dist/tui/services/context-service.js +0 -246
  136. package/dist/tui/shared-enums.d.ts +0 -16
  137. package/dist/tui/shared-enums.js +0 -19
  138. package/dist/tui/state/use-app-state.d.ts +0 -35
  139. package/dist/tui/state/use-app-state.js +0 -177
  140. package/dist/tui/types.d.ts +0 -77
  141. package/dist/tui/types.js +0 -2
  142. package/dist/tui-bundle.d.ts +0 -1
  143. package/dist/tui-bundle.js +0 -5
  144. package/dist/tui-entry.mjs +0 -1407
  145. package/dist/utils/cli-runtime.d.ts +0 -5
  146. package/dist/utils/cli-runtime.js +0 -22
  147. package/dist/utils/help-error.d.ts +0 -7
  148. package/dist/utils/help-error.js +0 -14
  149. package/dist/utils/interaction.d.ts +0 -19
  150. package/dist/utils/interaction.js +0 -93
  151. package/dist/utils/structured-log.d.ts +0 -7
  152. package/dist/utils/structured-log.js +0 -112
  153. package/dist/utils/trpc-url.d.ts +0 -4
  154. package/dist/utils/trpc-url.js +0 -15
@@ -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,5 +0,0 @@
1
- import React from 'react';
2
- import type { NavItemPolicy } from '../types';
3
- export declare function LockedScreen({ item }: {
4
- item?: NavItemPolicy;
5
- }): React.JSX.Element;
@@ -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,5 +0,0 @@
1
- import React from 'react';
2
- import type { TuiState } from '../state/use-app-state';
3
- export declare function SpecsScreen({ state }: {
4
- state: TuiState;
5
- }): React.JSX.Element;
@@ -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];
@@ -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
- }