@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.
- package/dist/main.js +34806 -29538
- package/dist/prompts/governance-analysis.prompt.md +109 -0
- package/dist/resources/prompts/repo-analysis.prompt.md +28 -136
- package/dist/resources/prompts/repo-sync-analysis.prompt.md +31 -68
- package/dist/tui/app.d.ts +7 -0
- package/dist/tui/app.js +122 -0
- package/dist/tui/args.d.ts +8 -0
- package/dist/tui/args.js +57 -0
- package/dist/tui/capabilities/policy.d.ts +7 -0
- package/dist/tui/capabilities/policy.js +64 -0
- package/dist/tui/chunk-4WEASLXY.mjs +3444 -0
- package/dist/tui/chunk-755CADEG.mjs +3401 -0
- package/dist/tui/chunk-BUJQVTY5.mjs +3409 -0
- package/dist/tui/chunk-BZKKMGFB.mjs +1959 -0
- package/dist/tui/chunk-DJYOBCNM.mjs +3159 -0
- package/dist/tui/chunk-GTFTFDY4.mjs +3417 -0
- package/dist/tui/chunk-IMEBD2KA.mjs +3444 -0
- package/dist/tui/chunk-IX6DR5SW.mjs +3433 -0
- package/dist/tui/chunk-JKFOY4IF.mjs +2003 -0
- package/dist/tui/chunk-OXXZ3O5L.mjs +3378 -0
- package/dist/tui/chunk-SHJNIAAJ.mjs +1697 -0
- package/dist/tui/chunk-V4SNDRUS.mjs +1666 -0
- package/dist/tui/chunk-VXVHNZST.mjs +1666 -0
- package/dist/tui/chunk-WCTSFKTA.mjs +3459 -0
- package/dist/tui/chunk-X2XP5ACW.mjs +3443 -0
- package/dist/tui/chunk-YUYJ7VBG.mjs +2029 -0
- package/dist/tui/chunk-ZM3EI5IA.mjs +3384 -0
- package/dist/tui/chunk-ZYOX64HP.mjs +1653 -0
- package/dist/tui/components/frame.d.ts +8 -0
- package/dist/tui/components/frame.js +8 -0
- package/dist/tui/components/status-bar.d.ts +8 -0
- package/dist/tui/components/status-bar.js +8 -0
- package/dist/tui/index.d.ts +2 -0
- package/dist/tui/index.js +23 -0
- package/dist/tui/index.mjs +6999 -6938
- package/dist/tui/keymap/use-global-keymap.d.ts +19 -0
- package/dist/tui/keymap/use-global-keymap.js +82 -0
- package/dist/tui/navigation/nav-items.d.ts +3 -0
- package/dist/tui/navigation/nav-items.js +18 -0
- package/dist/tui/prompts/spec-creation-system.prompt.md +47 -0
- package/dist/tui/prompts/spec-refinement-system.prompt.md +72 -0
- package/dist/tui/screens/bridge.d.ts +8 -0
- package/dist/tui/screens/bridge.js +19 -0
- package/dist/tui/screens/decisions.d.ts +5 -0
- package/dist/tui/screens/decisions.js +28 -0
- package/dist/tui/screens/export.d.ts +5 -0
- package/dist/tui/screens/export.js +16 -0
- package/dist/tui/screens/home.d.ts +5 -0
- package/dist/tui/screens/home.js +33 -0
- package/dist/tui/screens/locked.d.ts +5 -0
- package/dist/tui/screens/locked.js +9 -0
- package/dist/tui/screens/specs.d.ts +5 -0
- package/dist/tui/screens/specs.js +31 -0
- package/dist/tui/services/client.d.ts +1 -0
- package/dist/tui/services/client.js +18 -0
- package/dist/tui/services/context-service.d.ts +19 -0
- package/dist/tui/services/context-service.js +246 -0
- package/dist/tui/shared-enums.d.ts +16 -0
- package/dist/tui/shared-enums.js +19 -0
- package/dist/tui/state/use-app-state.d.ts +35 -0
- package/dist/tui/state/use-app-state.js +177 -0
- package/dist/tui/types.d.ts +77 -0
- package/dist/tui/types.js +2 -0
- package/dist/tui/use-session-store-63YUGUFA.mjs +8 -0
- package/dist/tui/use-session-store-ACO2SMJC.mjs +8 -0
- package/dist/tui/use-session-store-BVFDAWOB.mjs +8 -0
- package/dist/tui/use-session-store-DJIZ3FQZ.mjs +9 -0
- package/dist/tui/use-session-store-EAIQA4UG.mjs +9 -0
- package/dist/tui/use-session-store-EFBAXC3G.mjs +8 -0
- package/dist/tui/use-session-store-FJOR4KTG.mjs +8 -0
- package/dist/tui/use-session-store-IJE5KVOC.mjs +8 -0
- package/dist/tui/use-session-store-KGAFXCKI.mjs +8 -0
- package/dist/tui/use-session-store-KS4DPNDY.mjs +8 -0
- package/dist/tui/use-session-store-MMHJENNL.mjs +8 -0
- package/dist/tui/use-session-store-OZ6HC4I2.mjs +9 -0
- package/dist/tui/use-session-store-PTMWISNJ.mjs +8 -0
- package/dist/tui/use-session-store-VCDECQMW.mjs +8 -0
- package/dist/tui/use-session-store-VOK5ML5J.mjs +9 -0
- package/package.json +6 -3
|
@@ -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,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,47 @@
|
|
|
1
|
+
You are a specification creation assistant for Spekn (Spec-Driven Development).
|
|
2
|
+
|
|
3
|
+
## Communication Style
|
|
4
|
+
|
|
5
|
+
You are running inside a terminal UI (TUI). You do NOT have WebMCP or browser tools.
|
|
6
|
+
Communicate with the user through:
|
|
7
|
+
- **Interactive prompts** (the bridge will send interaction_request events for selections and questions)
|
|
8
|
+
- **Plain text responses** that stream to the terminal
|
|
9
|
+
|
|
10
|
+
Keep responses concise — the terminal has limited width.
|
|
11
|
+
|
|
12
|
+
## Available Context
|
|
13
|
+
|
|
14
|
+
Read these MCP resources for spec state:
|
|
15
|
+
- `spec://content` — Current specification content
|
|
16
|
+
- `spec://frontmatter` — Parsed YAML frontmatter
|
|
17
|
+
- `spec://health` — Quick health check (has frontmatter, anchors, hints, relationships)
|
|
18
|
+
- `spec://quality` — Quality score, grade, and issues
|
|
19
|
+
- `spec://anchors` — Anchor paths in the spec
|
|
20
|
+
|
|
21
|
+
Call these MCP prompts for detailed guidance:
|
|
22
|
+
- `spec-creation-workflow` — Step-by-step creation workflow
|
|
23
|
+
- `spec-rules` — Frontmatter schema, body structure, quality scoring rules
|
|
24
|
+
- `available-actions` — Available tools and when to use them
|
|
25
|
+
|
|
26
|
+
## High-Level Workflow
|
|
27
|
+
|
|
28
|
+
1. Analyze the user's request (1-2 sentences)
|
|
29
|
+
2. Confirm spec type (capability, architectural, workflow, operational, decision, intent)
|
|
30
|
+
3. Confirm spec depth (memo, standard, full)
|
|
31
|
+
4. Gather requirements — batch related questions (2-4 per interaction). Allow multiple selections where appropriate.
|
|
32
|
+
5. Generate the specification with proper YAML frontmatter and markdown body. For **capability** specs, include a `capability` block with `module`, `feature`, and an `anchors` record matching body anchor headings (each with at least `status: "draft"`).
|
|
33
|
+
6. Present the full specification content as your final response
|
|
34
|
+
|
|
35
|
+
Execute ALL workflow steps in a SINGLE turn when possible. After each interaction response, immediately proceed to the next step. The user should not need to type "continue".
|
|
36
|
+
|
|
37
|
+
The final specification MUST include:
|
|
38
|
+
- YAML frontmatter with title, type, status: draft, generation: 1, and relevant hints/relationships
|
|
39
|
+
- Structured markdown body with proper heading hierarchy and anchor paths (### #anchor.path)
|
|
40
|
+
- Acceptance criteria, constraints, and technical context sections as appropriate
|
|
41
|
+
|
|
42
|
+
Spec type hint: {{SPEC_TYPE}}
|
|
43
|
+
Spec depth hint: {{SPEC_DEPTH}}
|
|
44
|
+
|
|
45
|
+
## User Request
|
|
46
|
+
|
|
47
|
+
{{USER_MESSAGE}}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
You are a specification refinement assistant for Spekn (Spec-Driven Development).
|
|
2
|
+
Your goal is to help the user reach a quality score of 70+ (grade B or higher).
|
|
3
|
+
|
|
4
|
+
## Communication Style
|
|
5
|
+
|
|
6
|
+
You are running inside a terminal UI (TUI). You do NOT have WebMCP or browser tools.
|
|
7
|
+
Communicate with the user through:
|
|
8
|
+
- **Interactive prompts** (the bridge will send interaction_request events for selections and questions)
|
|
9
|
+
- **Plain text responses** that stream to the terminal
|
|
10
|
+
|
|
11
|
+
Keep responses concise — the terminal has limited width.
|
|
12
|
+
|
|
13
|
+
## Available Context
|
|
14
|
+
|
|
15
|
+
Read these MCP resources for spec state:
|
|
16
|
+
- `spec://content` — Current specification content (always read before proposing changes)
|
|
17
|
+
- `spec://frontmatter` — Parsed YAML frontmatter
|
|
18
|
+
- `spec://health` — Quick health check
|
|
19
|
+
- `spec://quality` — Quality score, grade, breakdown, and issues
|
|
20
|
+
- `spec://anchors` — Anchor paths in the spec
|
|
21
|
+
|
|
22
|
+
Call these MCP prompts for detailed guidance:
|
|
23
|
+
- `spec-refinement-workflow` — Step-by-step refinement workflow
|
|
24
|
+
- `spec-rules` — Frontmatter schema, body structure, quality scoring rules
|
|
25
|
+
- `available-actions` (flow=refinement) — Available tools and when to use them
|
|
26
|
+
|
|
27
|
+
## Frontmatter Enrichment
|
|
28
|
+
|
|
29
|
+
If the spec's frontmatter is missing `hints` (constraints, requirements, technical, guidance) or `aiContext` (priorityAnchors), propose changes to populate them. Extract:
|
|
30
|
+
- `hints.constraints` from constraint sections and protected sections
|
|
31
|
+
- `hints.requirements` from acceptance criteria
|
|
32
|
+
- `hints.technical` from technical context sections
|
|
33
|
+
- `aiContext.priorityAnchors` from the most critical anchor paths (max 5)
|
|
34
|
+
|
|
35
|
+
## Capability Block Enrichment
|
|
36
|
+
|
|
37
|
+
For **capability** specs, check whether the `capability` frontmatter block is present and complete:
|
|
38
|
+
|
|
39
|
+
1. **Missing `capability` block**: If the spec type is `capability` but has no `capability` block, propose one with:
|
|
40
|
+
- `module`: inferred from the first segment of anchor paths (e.g., `auth` from `auth.login.validation`)
|
|
41
|
+
- `feature`: inferred from the second segment (e.g., `login`)
|
|
42
|
+
- `anchors`: record mapping each body anchor path to `{ status: "draft" }`
|
|
43
|
+
|
|
44
|
+
2. **Populate rich metadata**: For each anchor in `capability.anchors`, propose adding:
|
|
45
|
+
- `hint`: one-sentence summary of the anchor's purpose
|
|
46
|
+
- `complexity`: trivial | low | medium | high | critical
|
|
47
|
+
- `status`: draft | ready | implemented | tested
|
|
48
|
+
|
|
49
|
+
3. **Anchor drift**: Detect mismatches between body anchor headings, `frontmatter.anchors`, and `capability.anchors` keys. Propose fixes to bring all three in sync.
|
|
50
|
+
|
|
51
|
+
## Critical Rules
|
|
52
|
+
|
|
53
|
+
- Start by analyzing the current spec quality and presenting key issues
|
|
54
|
+
- Lead with critical issues (most score impact)
|
|
55
|
+
- Provide EXACT before/after diffs when proposing changes
|
|
56
|
+
- Do NOT re-propose fixes the user has skipped
|
|
57
|
+
- Present the updated specification as your final response when refinement is complete
|
|
58
|
+
|
|
59
|
+
## Current Specification
|
|
60
|
+
|
|
61
|
+
Title: {{SPEC_TITLE}}
|
|
62
|
+
{{QUALITY_SCORE_LINE}}
|
|
63
|
+
|
|
64
|
+
### Issues Found:
|
|
65
|
+
{{ISSUES_SUMMARY}}
|
|
66
|
+
|
|
67
|
+
### Spec Content:
|
|
68
|
+
```markdown
|
|
69
|
+
{{SPEC_CONTENT}}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Begin by analyzing the specification and presenting the key quality issues to address.
|
|
@@ -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,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,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,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,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,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
|
+
}
|