@spekn/cli 1.0.1 → 1.0.2

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 (79) hide show
  1. package/dist/main.js +34806 -29538
  2. package/dist/prompts/governance-analysis.prompt.md +109 -0
  3. package/dist/resources/prompts/repo-analysis.prompt.md +28 -136
  4. package/dist/resources/prompts/repo-sync-analysis.prompt.md +31 -68
  5. package/dist/tui/app.d.ts +7 -0
  6. package/dist/tui/app.js +122 -0
  7. package/dist/tui/args.d.ts +8 -0
  8. package/dist/tui/args.js +57 -0
  9. package/dist/tui/capabilities/policy.d.ts +7 -0
  10. package/dist/tui/capabilities/policy.js +64 -0
  11. package/dist/tui/chunk-4WEASLXY.mjs +3444 -0
  12. package/dist/tui/chunk-755CADEG.mjs +3401 -0
  13. package/dist/tui/chunk-BUJQVTY5.mjs +3409 -0
  14. package/dist/tui/chunk-BZKKMGFB.mjs +1959 -0
  15. package/dist/tui/chunk-DJYOBCNM.mjs +3159 -0
  16. package/dist/tui/chunk-GTFTFDY4.mjs +3417 -0
  17. package/dist/tui/chunk-IMEBD2KA.mjs +3444 -0
  18. package/dist/tui/chunk-IX6DR5SW.mjs +3433 -0
  19. package/dist/tui/chunk-JKFOY4IF.mjs +2003 -0
  20. package/dist/tui/chunk-OXXZ3O5L.mjs +3378 -0
  21. package/dist/tui/chunk-SHJNIAAJ.mjs +1697 -0
  22. package/dist/tui/chunk-V4SNDRUS.mjs +1666 -0
  23. package/dist/tui/chunk-VXVHNZST.mjs +1666 -0
  24. package/dist/tui/chunk-WCTSFKTA.mjs +3459 -0
  25. package/dist/tui/chunk-X2XP5ACW.mjs +3443 -0
  26. package/dist/tui/chunk-YUYJ7VBG.mjs +2029 -0
  27. package/dist/tui/chunk-ZM3EI5IA.mjs +3384 -0
  28. package/dist/tui/chunk-ZYOX64HP.mjs +1653 -0
  29. package/dist/tui/components/frame.d.ts +8 -0
  30. package/dist/tui/components/frame.js +8 -0
  31. package/dist/tui/components/status-bar.d.ts +8 -0
  32. package/dist/tui/components/status-bar.js +8 -0
  33. package/dist/tui/index.d.ts +2 -0
  34. package/dist/tui/index.js +23 -0
  35. package/dist/tui/index.mjs +6999 -6938
  36. package/dist/tui/keymap/use-global-keymap.d.ts +19 -0
  37. package/dist/tui/keymap/use-global-keymap.js +82 -0
  38. package/dist/tui/navigation/nav-items.d.ts +3 -0
  39. package/dist/tui/navigation/nav-items.js +18 -0
  40. package/dist/tui/prompts/spec-creation-system.prompt.md +47 -0
  41. package/dist/tui/prompts/spec-refinement-system.prompt.md +72 -0
  42. package/dist/tui/screens/bridge.d.ts +8 -0
  43. package/dist/tui/screens/bridge.js +19 -0
  44. package/dist/tui/screens/decisions.d.ts +5 -0
  45. package/dist/tui/screens/decisions.js +28 -0
  46. package/dist/tui/screens/export.d.ts +5 -0
  47. package/dist/tui/screens/export.js +16 -0
  48. package/dist/tui/screens/home.d.ts +5 -0
  49. package/dist/tui/screens/home.js +33 -0
  50. package/dist/tui/screens/locked.d.ts +5 -0
  51. package/dist/tui/screens/locked.js +9 -0
  52. package/dist/tui/screens/specs.d.ts +5 -0
  53. package/dist/tui/screens/specs.js +31 -0
  54. package/dist/tui/services/client.d.ts +1 -0
  55. package/dist/tui/services/client.js +18 -0
  56. package/dist/tui/services/context-service.d.ts +19 -0
  57. package/dist/tui/services/context-service.js +246 -0
  58. package/dist/tui/shared-enums.d.ts +16 -0
  59. package/dist/tui/shared-enums.js +19 -0
  60. package/dist/tui/state/use-app-state.d.ts +35 -0
  61. package/dist/tui/state/use-app-state.js +177 -0
  62. package/dist/tui/types.d.ts +77 -0
  63. package/dist/tui/types.js +2 -0
  64. package/dist/tui/use-session-store-63YUGUFA.mjs +8 -0
  65. package/dist/tui/use-session-store-ACO2SMJC.mjs +8 -0
  66. package/dist/tui/use-session-store-BVFDAWOB.mjs +8 -0
  67. package/dist/tui/use-session-store-DJIZ3FQZ.mjs +9 -0
  68. package/dist/tui/use-session-store-EAIQA4UG.mjs +9 -0
  69. package/dist/tui/use-session-store-EFBAXC3G.mjs +8 -0
  70. package/dist/tui/use-session-store-FJOR4KTG.mjs +8 -0
  71. package/dist/tui/use-session-store-IJE5KVOC.mjs +8 -0
  72. package/dist/tui/use-session-store-KGAFXCKI.mjs +8 -0
  73. package/dist/tui/use-session-store-KS4DPNDY.mjs +8 -0
  74. package/dist/tui/use-session-store-MMHJENNL.mjs +8 -0
  75. package/dist/tui/use-session-store-OZ6HC4I2.mjs +9 -0
  76. package/dist/tui/use-session-store-PTMWISNJ.mjs +8 -0
  77. package/dist/tui/use-session-store-VCDECQMW.mjs +8 -0
  78. package/dist/tui/use-session-store-VOK5ML5J.mjs +9 -0
  79. package/package.json +6 -3
@@ -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
+ };
@@ -0,0 +1,35 @@
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
+ };
@@ -0,0 +1,177 @@
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
+ }
@@ -0,0 +1,77 @@
1
+ import type { OrganizationPlan, WorkflowPhase } from './shared-enums';
2
+ export type TuiScreenId = 'home' | 'specs' | 'export' | 'decisions' | 'bridge' | 'active-runs' | 'phase-gates' | 'skills-marketplace' | 'org-governance';
3
+ export type TuiRole = 'owner' | 'admin' | 'member' | 'viewer';
4
+ export type ActionState = 'enabled' | 'disabled' | 'locked';
5
+ export interface CapabilityContext {
6
+ plan: OrganizationPlan;
7
+ role: TuiRole;
8
+ workflowPhase: WorkflowPhase | null;
9
+ permissions: string[];
10
+ }
11
+ export interface NavItemPolicy {
12
+ id: TuiScreenId;
13
+ label: string;
14
+ description: string;
15
+ requiredPlan: OrganizationPlan;
16
+ state: ActionState;
17
+ reason?: string;
18
+ }
19
+ export interface TuiBootContext {
20
+ apiUrl: string;
21
+ organizationId: string;
22
+ organizationName: string;
23
+ role: TuiRole;
24
+ plan: OrganizationPlan;
25
+ projectId: string;
26
+ projectName: string;
27
+ permissions: string[];
28
+ }
29
+ export interface SpecListItem {
30
+ id: string;
31
+ title: string;
32
+ status: string;
33
+ version: string;
34
+ updatedAt?: string;
35
+ type?: string;
36
+ }
37
+ export interface DecisionListItem {
38
+ id: string;
39
+ title: string;
40
+ status: string;
41
+ decisionType: string;
42
+ specAnchor?: string | null;
43
+ createdAt?: string;
44
+ }
45
+ export interface WorkflowSummary {
46
+ currentPhase: WorkflowPhase | null;
47
+ blockedCount: number;
48
+ hasVerificationEvidence: boolean;
49
+ hasPlanningArtifacts: boolean;
50
+ }
51
+ export interface BridgeSummary {
52
+ featureEnabled: boolean;
53
+ devices: Array<{
54
+ id: string;
55
+ name: string;
56
+ status: string;
57
+ isDefault: boolean;
58
+ lastSeenAt?: string | null;
59
+ }>;
60
+ connectedDevices: number;
61
+ authFailures: number;
62
+ }
63
+ export interface LocalBridgeSummary {
64
+ paired: boolean;
65
+ deviceId?: string;
66
+ deviceName?: string;
67
+ port: number;
68
+ running: boolean;
69
+ uptimeSec?: number;
70
+ }
71
+ export type ExportFormat = 'claude-md' | 'cursorrules';
72
+ export interface ExportPreview {
73
+ content: string;
74
+ anchorCount: number;
75
+ specVersion?: string;
76
+ warnings?: string[];
77
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-OXXZ3O5L.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-GTFTFDY4.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-X2XP5ACW.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,9 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-V4SNDRUS.mjs";
5
+ import "./chunk-JKFOY4IF.mjs";
6
+ export {
7
+ buildOnboardingState,
8
+ useSessionStore
9
+ };
@@ -0,0 +1,9 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-VXVHNZST.mjs";
5
+ import "./chunk-BZKKMGFB.mjs";
6
+ export {
7
+ buildOnboardingState,
8
+ useSessionStore
9
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-ZM3EI5IA.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-BUJQVTY5.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-DJYOBCNM.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-4WEASLXY.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-755CADEG.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };
@@ -0,0 +1,8 @@
1
+ import {
2
+ buildOnboardingState,
3
+ useSessionStore
4
+ } from "./chunk-IMEBD2KA.mjs";
5
+ export {
6
+ buildOnboardingState,
7
+ useSessionStore
8
+ };