@spekn/cli 1.0.0

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 (159) hide show
  1. package/dist/__tests__/export-cli.test.d.ts +1 -0
  2. package/dist/__tests__/export-cli.test.js +70 -0
  3. package/dist/__tests__/tui-args-policy.test.d.ts +1 -0
  4. package/dist/__tests__/tui-args-policy.test.js +50 -0
  5. package/dist/acp-S2MHZOAD.mjs +23 -0
  6. package/dist/acp-UCCI44JY.mjs +25 -0
  7. package/dist/auth/credentials-store.d.ts +2 -0
  8. package/dist/auth/credentials-store.js +5 -0
  9. package/dist/auth/device-flow.d.ts +36 -0
  10. package/dist/auth/device-flow.js +189 -0
  11. package/dist/auth/jwt.d.ts +1 -0
  12. package/dist/auth/jwt.js +6 -0
  13. package/dist/auth/session.d.ts +67 -0
  14. package/dist/auth/session.js +86 -0
  15. package/dist/auth-login.d.ts +34 -0
  16. package/dist/auth-login.js +202 -0
  17. package/dist/auth-logout.d.ts +25 -0
  18. package/dist/auth-logout.js +115 -0
  19. package/dist/auth-status.d.ts +24 -0
  20. package/dist/auth-status.js +109 -0
  21. package/dist/backlog-generate.d.ts +11 -0
  22. package/dist/backlog-generate.js +308 -0
  23. package/dist/backlog-health.d.ts +11 -0
  24. package/dist/backlog-health.js +287 -0
  25. package/dist/bridge-login.d.ts +40 -0
  26. package/dist/bridge-login.js +277 -0
  27. package/dist/chunk-3PAYRI4G.mjs +2428 -0
  28. package/dist/chunk-M4CS3A25.mjs +2426 -0
  29. package/dist/commands/auth/login.d.ts +30 -0
  30. package/dist/commands/auth/login.js +164 -0
  31. package/dist/commands/auth/logout.d.ts +25 -0
  32. package/dist/commands/auth/logout.js +115 -0
  33. package/dist/commands/auth/status.d.ts +24 -0
  34. package/dist/commands/auth/status.js +109 -0
  35. package/dist/commands/backlog/generate.d.ts +11 -0
  36. package/dist/commands/backlog/generate.js +308 -0
  37. package/dist/commands/backlog/health.d.ts +11 -0
  38. package/dist/commands/backlog/health.js +287 -0
  39. package/dist/commands/bridge/login.d.ts +36 -0
  40. package/dist/commands/bridge/login.js +258 -0
  41. package/dist/commands/export.d.ts +35 -0
  42. package/dist/commands/export.js +485 -0
  43. package/dist/commands/marketplace-export.d.ts +21 -0
  44. package/dist/commands/marketplace-export.js +214 -0
  45. package/dist/commands/project-clean.d.ts +1 -0
  46. package/dist/commands/project-clean.js +126 -0
  47. package/dist/commands/repo/common.d.ts +105 -0
  48. package/dist/commands/repo/common.js +775 -0
  49. package/dist/commands/repo/detach.d.ts +2 -0
  50. package/dist/commands/repo/detach.js +120 -0
  51. package/dist/commands/repo/register.d.ts +21 -0
  52. package/dist/commands/repo/register.js +175 -0
  53. package/dist/commands/repo/sync.d.ts +22 -0
  54. package/dist/commands/repo/sync.js +873 -0
  55. package/dist/commands/skills-import-local.d.ts +16 -0
  56. package/dist/commands/skills-import-local.js +352 -0
  57. package/dist/commands/spec/drift-check.d.ts +3 -0
  58. package/dist/commands/spec/drift-check.js +186 -0
  59. package/dist/commands/spec/frontmatter.d.ts +11 -0
  60. package/dist/commands/spec/frontmatter.js +219 -0
  61. package/dist/commands/spec/lint.d.ts +11 -0
  62. package/dist/commands/spec/lint.js +499 -0
  63. package/dist/commands/spec/parse.d.ts +11 -0
  64. package/dist/commands/spec/parse.js +162 -0
  65. package/dist/export.d.ts +35 -0
  66. package/dist/export.js +485 -0
  67. package/dist/index.d.ts +11 -0
  68. package/dist/index.js +21 -0
  69. package/dist/main.d.ts +1 -0
  70. package/dist/main.js +115280 -0
  71. package/dist/marketplace-export.d.ts +21 -0
  72. package/dist/marketplace-export.js +214 -0
  73. package/dist/project-clean.d.ts +1 -0
  74. package/dist/project-clean.js +126 -0
  75. package/dist/project-context.d.ts +99 -0
  76. package/dist/project-context.js +376 -0
  77. package/dist/repo-common.d.ts +101 -0
  78. package/dist/repo-common.js +671 -0
  79. package/dist/repo-detach.d.ts +2 -0
  80. package/dist/repo-detach.js +102 -0
  81. package/dist/repo-ingest.d.ts +29 -0
  82. package/dist/repo-ingest.js +305 -0
  83. package/dist/repo-register.d.ts +21 -0
  84. package/dist/repo-register.js +175 -0
  85. package/dist/repo-sync.d.ts +16 -0
  86. package/dist/repo-sync.js +152 -0
  87. package/dist/resources/prompt-loader.d.ts +1 -0
  88. package/dist/resources/prompt-loader.js +62 -0
  89. package/dist/resources/prompts/README.md +21 -0
  90. package/dist/resources/prompts/prompts/repo-analysis.prompt.md +126 -0
  91. package/dist/resources/prompts/repo-analysis.prompt.md +151 -0
  92. package/dist/resources/prompts/repo-sync-analysis.prompt.md +85 -0
  93. package/dist/skills-import-local.d.ts +16 -0
  94. package/dist/skills-import-local.js +352 -0
  95. package/dist/spec-drift-check.d.ts +3 -0
  96. package/dist/spec-drift-check.js +186 -0
  97. package/dist/spec-frontmatter.d.ts +11 -0
  98. package/dist/spec-frontmatter.js +219 -0
  99. package/dist/spec-lint.d.ts +11 -0
  100. package/dist/spec-lint.js +499 -0
  101. package/dist/spec-parse.d.ts +11 -0
  102. package/dist/spec-parse.js +162 -0
  103. package/dist/stubs/dotenv.d.ts +5 -0
  104. package/dist/stubs/dotenv.js +6 -0
  105. package/dist/stubs/typeorm.d.ts +22 -0
  106. package/dist/stubs/typeorm.js +28 -0
  107. package/dist/tui/app.d.ts +7 -0
  108. package/dist/tui/app.js +122 -0
  109. package/dist/tui/args.d.ts +8 -0
  110. package/dist/tui/args.js +57 -0
  111. package/dist/tui/capabilities/policy.d.ts +7 -0
  112. package/dist/tui/capabilities/policy.js +64 -0
  113. package/dist/tui/components/frame.d.ts +8 -0
  114. package/dist/tui/components/frame.js +8 -0
  115. package/dist/tui/components/status-bar.d.ts +8 -0
  116. package/dist/tui/components/status-bar.js +8 -0
  117. package/dist/tui/index.d.ts +2 -0
  118. package/dist/tui/index.js +23 -0
  119. package/dist/tui/index.mjs +7563 -0
  120. package/dist/tui/keymap/use-global-keymap.d.ts +19 -0
  121. package/dist/tui/keymap/use-global-keymap.js +82 -0
  122. package/dist/tui/navigation/nav-items.d.ts +3 -0
  123. package/dist/tui/navigation/nav-items.js +18 -0
  124. package/dist/tui/screens/bridge.d.ts +8 -0
  125. package/dist/tui/screens/bridge.js +19 -0
  126. package/dist/tui/screens/decisions.d.ts +5 -0
  127. package/dist/tui/screens/decisions.js +28 -0
  128. package/dist/tui/screens/export.d.ts +5 -0
  129. package/dist/tui/screens/export.js +16 -0
  130. package/dist/tui/screens/home.d.ts +5 -0
  131. package/dist/tui/screens/home.js +33 -0
  132. package/dist/tui/screens/locked.d.ts +5 -0
  133. package/dist/tui/screens/locked.js +9 -0
  134. package/dist/tui/screens/specs.d.ts +5 -0
  135. package/dist/tui/screens/specs.js +31 -0
  136. package/dist/tui/services/client.d.ts +1 -0
  137. package/dist/tui/services/client.js +18 -0
  138. package/dist/tui/services/context-service.d.ts +19 -0
  139. package/dist/tui/services/context-service.js +246 -0
  140. package/dist/tui/shared-enums.d.ts +16 -0
  141. package/dist/tui/shared-enums.js +19 -0
  142. package/dist/tui/state/use-app-state.d.ts +35 -0
  143. package/dist/tui/state/use-app-state.js +177 -0
  144. package/dist/tui/types.d.ts +77 -0
  145. package/dist/tui/types.js +2 -0
  146. package/dist/tui-bundle.d.ts +1 -0
  147. package/dist/tui-bundle.js +5 -0
  148. package/dist/tui-entry.mjs +1407 -0
  149. package/dist/utils/cli-runtime.d.ts +5 -0
  150. package/dist/utils/cli-runtime.js +22 -0
  151. package/dist/utils/help-error.d.ts +7 -0
  152. package/dist/utils/help-error.js +14 -0
  153. package/dist/utils/interaction.d.ts +19 -0
  154. package/dist/utils/interaction.js +93 -0
  155. package/dist/utils/structured-log.d.ts +7 -0
  156. package/dist/utils/structured-log.js +112 -0
  157. package/dist/utils/trpc-url.d.ts +4 -0
  158. package/dist/utils/trpc-url.js +15 -0
  159. package/package.json +59 -0
@@ -0,0 +1,19 @@
1
+ import type { NavItemPolicy, TuiScreenId } from '../types';
2
+ interface KeymapOptions {
3
+ screen: TuiScreenId;
4
+ navPolicy: NavItemPolicy[];
5
+ commandMode: boolean;
6
+ showHelp: boolean;
7
+ onNavigate: (screen: TuiScreenId) => void;
8
+ onHelpToggle: () => void;
9
+ onSearchToggle: () => void;
10
+ onSearchClear: () => void;
11
+ onCommandModeToggle: (value: boolean) => void;
12
+ onRefresh: () => void;
13
+ onExportPreview: () => void;
14
+ onExportGenerate: () => void;
15
+ onBridgeStart: () => void;
16
+ onBridgeStop: () => void;
17
+ }
18
+ export declare function useGlobalKeymap(options: KeymapOptions): void;
19
+ export {};
@@ -0,0 +1,82 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.useGlobalKeymap = useGlobalKeymap;
4
+ const ink_1 = require("ink");
5
+ const nav_items_1 = require("../navigation/nav-items");
6
+ function useGlobalKeymap(options) {
7
+ (0, ink_1.useInput)((input, key) => {
8
+ // In command mode, TextInput handles all keystrokes — only handle escape
9
+ if (options.commandMode) {
10
+ if (key.escape) {
11
+ options.onCommandModeToggle(false);
12
+ }
13
+ return;
14
+ }
15
+ if (key.ctrl && input === 'c') {
16
+ process.exit(0);
17
+ return;
18
+ }
19
+ if (input === '?') {
20
+ options.onHelpToggle();
21
+ return;
22
+ }
23
+ if (input === ':') {
24
+ options.onCommandModeToggle(true);
25
+ return;
26
+ }
27
+ if (input === '/') {
28
+ options.onSearchToggle();
29
+ return;
30
+ }
31
+ if (key.escape) {
32
+ options.onSearchClear();
33
+ return;
34
+ }
35
+ if (input === 'j' || key.downArrow) {
36
+ options.onNavigate((0, nav_items_1.nextScreen)(options.screen, options.navPolicy));
37
+ return;
38
+ }
39
+ if (input === 'k' || key.upArrow) {
40
+ options.onNavigate((0, nav_items_1.previousScreen)(options.screen, options.navPolicy));
41
+ return;
42
+ }
43
+ if (input === 'h' || key.leftArrow) {
44
+ options.onNavigate((0, nav_items_1.previousScreen)(options.screen, options.navPolicy));
45
+ return;
46
+ }
47
+ if (input === 'l' || key.rightArrow) {
48
+ options.onNavigate((0, nav_items_1.nextScreen)(options.screen, options.navPolicy));
49
+ return;
50
+ }
51
+ if (input === 'r') {
52
+ options.onRefresh();
53
+ return;
54
+ }
55
+ if (options.screen === 'export' && input === 'p') {
56
+ options.onExportPreview();
57
+ return;
58
+ }
59
+ if (options.screen === 'export' && input === 'g') {
60
+ options.onExportGenerate();
61
+ return;
62
+ }
63
+ if (options.screen === 'bridge' && input === 's') {
64
+ options.onBridgeStart();
65
+ return;
66
+ }
67
+ if (options.screen === 'bridge' && input === 'x') {
68
+ options.onBridgeStop();
69
+ return;
70
+ }
71
+ if (input === '1')
72
+ options.onNavigate('home');
73
+ if (input === '2')
74
+ options.onNavigate('specs');
75
+ if (input === '3')
76
+ options.onNavigate('export');
77
+ if (input === '4')
78
+ options.onNavigate('decisions');
79
+ if (input === '5')
80
+ options.onNavigate('bridge');
81
+ });
82
+ }
@@ -0,0 +1,3 @@
1
+ import type { NavItemPolicy, TuiScreenId } from '../types';
2
+ export declare function nextScreen(current: TuiScreenId, items: NavItemPolicy[]): TuiScreenId;
3
+ export declare function previousScreen(current: TuiScreenId, items: NavItemPolicy[]): TuiScreenId;
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.nextScreen = nextScreen;
4
+ exports.previousScreen = previousScreen;
5
+ function nextScreen(current, items) {
6
+ const index = items.findIndex((item) => item.id === current);
7
+ if (index === -1 || items.length === 0)
8
+ return 'home';
9
+ const target = items[(index + 1) % items.length];
10
+ return target?.id ?? 'home';
11
+ }
12
+ function previousScreen(current, items) {
13
+ const index = items.findIndex((item) => item.id === current);
14
+ if (index === -1 || items.length === 0)
15
+ return 'home';
16
+ const target = items[(index - 1 + items.length) % items.length];
17
+ return target?.id ?? 'home';
18
+ }
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ import type { TuiState } from '../state/use-app-state';
3
+ interface BridgeScreenProps {
4
+ state: TuiState;
5
+ onBridgeStop?: () => void;
6
+ }
7
+ export declare function BridgeScreen({ state, onBridgeStop }: BridgeScreenProps): React.JSX.Element;
8
+ export {};
@@ -0,0 +1,19 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.BridgeScreen = BridgeScreen;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const react_1 = require("react");
6
+ const ink_1 = require("ink");
7
+ const ui_1 = require("@inkjs/ui");
8
+ function BridgeScreen({ state, onBridgeStop }) {
9
+ const [confirmingStop, setConfirmingStop] = (0, react_1.useState)(false);
10
+ if (state.loading) {
11
+ return (0, jsx_runtime_1.jsx)(ui_1.Spinner, { label: "Checking bridge health..." });
12
+ }
13
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Local Bridge" }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Feature flag: ", state.bridge?.featureEnabled ? 'enabled' : 'disabled'] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Cloud connected devices: ", state.bridge?.connectedDevices ?? 0] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Cloud auth failures: ", state.bridge?.authFailures ?? 0] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Local Runtime" }), state.localBridge?.running ? ((0, jsx_runtime_1.jsxs)(ui_1.StatusMessage, { variant: "success", children: ["Running on port ", state.localBridge.port] })) : ((0, jsx_runtime_1.jsx)(ui_1.StatusMessage, { variant: "error", children: "Stopped" })), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Paired: ", state.localBridge?.paired ? 'yes' : 'no'] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Uptime: ", state.localBridge?.uptimeSec ?? 0, "s"] })] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Actions" }), confirmingStop ? ((0, jsx_runtime_1.jsxs)(ink_1.Box, { gap: 1, children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { children: "Stop the bridge?" }), (0, jsx_runtime_1.jsx)(ui_1.ConfirmInput, { onConfirm: () => {
14
+ setConfirmingStop(false);
15
+ onBridgeStop?.();
16
+ }, onCancel: () => {
17
+ setConfirmingStop(false);
18
+ } })] })) : ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "s=start (detached), x=stop signal, r=refresh" }))] }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Registered Devices" }), (state.bridge?.devices ?? []).slice(0, 7).map((device) => ((0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["- ", device.name, " ", (0, jsx_runtime_1.jsx)(ui_1.Badge, { color: device.status === 'online' ? 'green' : device.status === 'error' ? 'red' : 'yellow', children: device.status }), " ", device.isDefault ? '(default)' : ''] }, device.id)))] })] }));
19
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { TuiState } from '../state/use-app-state';
3
+ export declare function DecisionsScreen({ state }: {
4
+ state: TuiState;
5
+ }): React.JSX.Element;
@@ -0,0 +1,28 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DecisionsScreen = DecisionsScreen;
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 === 'approved')
9
+ return 'green';
10
+ if (status === 'pending')
11
+ return 'yellow';
12
+ if (status === 'rejected')
13
+ return 'red';
14
+ return 'blue';
15
+ }
16
+ function filterItems(items, query) {
17
+ if (!query.trim())
18
+ return items;
19
+ const normalized = query.trim().toLowerCase();
20
+ return items.filter((item) => item.title.toLowerCase().includes(normalized));
21
+ }
22
+ function DecisionsScreen({ state }) {
23
+ if (state.loading) {
24
+ return (0, jsx_runtime_1.jsx)(ui_1.Spinner, { label: "Loading decisions..." });
25
+ }
26
+ const decisions = filterItems(state.decisions, state.searchQuery).slice(0, 7);
27
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Decision Log" }), decisions.length === 0 ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "No decisions in current view." })) : ((0, jsx_runtime_1.jsx)(ui_1.UnorderedList, { children: decisions.map((decision, 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: [decision.title, " ", (0, jsx_runtime_1.jsx)(ui_1.Badge, { color: statusBadgeColor(decision.status), children: decision.status }), " (", decision.decisionType, ") ", decision.specAnchor ? `@${decision.specAnchor}` : ''] }) }, decision.id))) })), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Role-aware Actions" }), state.boot?.role === 'viewer' ? ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "yellow", children: "Viewer role: read-only. Approvals disabled." })) : ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "Use command palette: :approve DECISION_ID or :reject DECISION_ID" }))] })] }));
28
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { TuiState } from '../state/use-app-state';
3
+ export declare function ExportScreen({ state }: {
4
+ state: TuiState;
5
+ }): React.JSX.Element;
@@ -0,0 +1,16 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ExportScreen = ExportScreen;
4
+ const jsx_runtime_1 = require("react/jsx-runtime");
5
+ const ink_1 = require("ink");
6
+ const ui_1 = require("@inkjs/ui");
7
+ function ExportScreen({ state }) {
8
+ if (state.loading) {
9
+ return (0, jsx_runtime_1.jsx)(ui_1.Spinner, { label: "Loading export..." });
10
+ }
11
+ const preview = state.exportPreview;
12
+ return ((0, jsx_runtime_1.jsxs)(ink_1.Box, { flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Context Export" }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Format: ", state.exportFormat] }), (0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "Commands: p=preview, g=generate, :=run command palette" }), (0, jsx_runtime_1.jsxs)(ink_1.Box, { marginTop: 1, flexDirection: "column", children: [(0, jsx_runtime_1.jsx)(ink_1.Text, { bold: true, children: "Summary" }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Anchors: ", preview?.anchorCount ?? 0] }), (0, jsx_runtime_1.jsxs)(ink_1.Text, { children: ["Spec version: ", preview?.specVersion ?? 'n/a'] }), preview?.warnings?.length ? ((0, jsx_runtime_1.jsxs)(ui_1.StatusMessage, { variant: "warning", children: ["Warnings: ", preview.warnings.join('; ')] })) : ((0, jsx_runtime_1.jsx)(ink_1.Text, { children: "Warnings: none" }))] }), state.statusLine.includes('generated') || state.statusLine.includes('Export generated') ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ui_1.StatusMessage, { variant: "success", children: "Export generated successfully" }) })) : state.statusLine.includes('failed') ? ((0, jsx_runtime_1.jsx)(ink_1.Box, { marginTop: 1, children: (0, jsx_runtime_1.jsx)(ui_1.StatusMessage, { variant: "error", children: "Export generation failed" }) })) : 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: "Preview (first 12 lines)" }), preview ? (preview.content
13
+ .split('\n')
14
+ .slice(0, 12)
15
+ .map((line, index) => ((0, jsx_runtime_1.jsx)(ink_1.Text, { children: line }, `preview-${index}`)))) : ((0, jsx_runtime_1.jsx)(ink_1.Text, { color: "gray", children: "No preview yet." }))] })] }));
16
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { TuiState } from '../state/use-app-state';
3
+ export declare function HomeScreen({ state }: {
4
+ state: TuiState;
5
+ }): React.JSX.Element;
@@ -0,0 +1,33 @@
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
+ }
@@ -0,0 +1,5 @@
1
+ import React from 'react';
2
+ import type { NavItemPolicy } from '../types';
3
+ export declare function LockedScreen({ item }: {
4
+ item?: NavItemPolicy;
5
+ }): React.JSX.Element;
@@ -0,0 +1,9 @@
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
+ }
@@ -0,0 +1,5 @@
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;
@@ -0,0 +1,31 @@
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
+ }
@@ -0,0 +1 @@
1
+ export declare function createApiClient(apiUrl: string, token: string, organizationId: string): any;
@@ -0,0 +1,18 @@
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
+ }
@@ -0,0 +1,19 @@
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
+ }
@@ -0,0 +1,246 @@
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;
@@ -0,0 +1,16 @@
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];
@@ -0,0 +1,19 @@
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
+ };