nfo-cli 0.0.4-improve-prompting → 0.0.5

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/claude-command.js +6 -1
  2. package/dist/claude-command.js.map +1 -1
  3. package/dist/claude-trust.js +46 -0
  4. package/dist/claude-trust.js.map +1 -0
  5. package/dist/cli.js +0 -0
  6. package/dist/mcp/handlers.js +5 -0
  7. package/dist/mcp/handlers.js.map +1 -1
  8. package/dist/mcp/tool-defs.js +10 -0
  9. package/dist/mcp/tool-defs.js.map +1 -1
  10. package/dist/musicians/dismiss.js +1 -1
  11. package/dist/musicians/dismiss.js.map +1 -1
  12. package/dist/musicians/roles.js +15 -0
  13. package/dist/musicians/roles.js.map +1 -0
  14. package/dist/musicians/spawn.js +53 -18
  15. package/dist/musicians/spawn.js.map +1 -1
  16. package/dist/permission.js +6 -0
  17. package/dist/permission.js.map +1 -1
  18. package/dist/prompts/musician-role.js +2 -1
  19. package/dist/prompts/musician-role.js.map +1 -1
  20. package/dist/prompts/orchestrator-role.js +18 -6
  21. package/dist/prompts/orchestrator-role.js.map +1 -1
  22. package/dist/prompts/tool-discipline.js +7 -3
  23. package/dist/prompts/tool-discipline.js.map +1 -1
  24. package/package.json +8 -1
  25. package/assets/agent-screen.png +0 -0
  26. package/assets/main-screen.png +0 -0
  27. package/assets/orche-clawd.png +0 -0
  28. package/dist/tui/App.js +0 -428
  29. package/dist/tui/App.js.map +0 -1
  30. package/dist/tui/AppView.js +0 -13
  31. package/dist/tui/AppView.js.map +0 -1
  32. package/dist/tui/Auditorium.js +0 -17
  33. package/dist/tui/Auditorium.js.map +0 -1
  34. package/dist/tui/ConcertHall.js +0 -11
  35. package/dist/tui/ConcertHall.js.map +0 -1
  36. package/dist/tui/Help.js +0 -49
  37. package/dist/tui/Help.js.map +0 -1
  38. package/dist/tui/OrchestratorPane.js +0 -34
  39. package/dist/tui/OrchestratorPane.js.map +0 -1
  40. package/dist/tui/SidebarHeader.js +0 -6
  41. package/dist/tui/SidebarHeader.js.map +0 -1
  42. package/dist/tui/StatusBar.js +0 -6
  43. package/dist/tui/StatusBar.js.map +0 -1
  44. package/docs/plans/2026-05-29-nfo-phase-1-bootstrap.md +0 -2152
  45. package/docs/plans/2026-05-29-nfo-phase-2-mcp-musicians.md +0 -2467
  46. package/docs/plans/2026-05-29-nfo-phase-3-ink-tui.md +0 -1611
  47. package/docs/plans/2026-05-29-nfo-phase-4-permission-prompts.md +0 -460
  48. package/docs/plans/2026-05-29-nfo-phase-5-help-and-notify.md +0 -933
  49. package/docs/specs/2026-05-29-nfo-design.md +0 -468
  50. package/plan-explorer-musician-hardening.md +0 -56
  51. package/src/claude-command.ts +0 -35
  52. package/src/claude-detect.ts +0 -42
  53. package/src/cli.ts +0 -197
  54. package/src/commands/attach.ts +0 -24
  55. package/src/commands/dashboard-window.ts +0 -33
  56. package/src/commands/kill.ts +0 -50
  57. package/src/commands/launch.ts +0 -134
  58. package/src/commands/list.ts +0 -43
  59. package/src/commands/mcp-server.ts +0 -18
  60. package/src/commands/notes.ts +0 -18
  61. package/src/commands/restore.ts +0 -153
  62. package/src/commands/tui.tsx +0 -22
  63. package/src/config.ts +0 -44
  64. package/src/dashboard.ts +0 -1
  65. package/src/mcp/config.ts +0 -39
  66. package/src/mcp/handlers.ts +0 -141
  67. package/src/mcp/server.ts +0 -50
  68. package/src/mcp/tool-defs.ts +0 -151
  69. package/src/musicians/dismiss.ts +0 -60
  70. package/src/musicians/ids.ts +0 -21
  71. package/src/musicians/lookup.ts +0 -13
  72. package/src/musicians/message-log.ts +0 -152
  73. package/src/musicians/message.ts +0 -99
  74. package/src/musicians/query.ts +0 -19
  75. package/src/musicians/spawn.ts +0 -139
  76. package/src/notes.ts +0 -39
  77. package/src/notify.ts +0 -62
  78. package/src/orchestrator/report-back.ts +0 -33
  79. package/src/permission.ts +0 -30
  80. package/src/project-key.ts +0 -12
  81. package/src/prompts/musician-role.ts +0 -22
  82. package/src/prompts/orchestrator-role.ts +0 -84
  83. package/src/prompts/tool-discipline.ts +0 -41
  84. package/src/repo.ts +0 -14
  85. package/src/shell-quote.ts +0 -7
  86. package/src/state-updaters.ts +0 -132
  87. package/src/state.ts +0 -49
  88. package/src/state.types.ts +0 -67
  89. package/src/tmux.ts +0 -226
  90. package/src/tui/activity-line.ts +0 -16
  91. package/src/tui/components/App.tsx +0 -534
  92. package/src/tui/components/AppView.tsx +0 -98
  93. package/src/tui/components/Auditorium.tsx +0 -56
  94. package/src/tui/components/ConcertHall.tsx +0 -31
  95. package/src/tui/components/Help.tsx +0 -63
  96. package/src/tui/components/OrchestratorPane.tsx +0 -98
  97. package/src/tui/components/SidebarHeader.tsx +0 -34
  98. package/src/tui/components/StatusBar.tsx +0 -42
  99. package/src/tui/detect-permission.ts +0 -93
  100. package/src/tui/embedded-session-lifecycle.ts +0 -44
  101. package/src/tui/embedded-terminal.ts +0 -325
  102. package/src/tui/format-time.ts +0 -25
  103. package/src/tui/keymap.ts +0 -104
  104. package/src/tui/poll-activity.ts +0 -25
  105. package/src/tui/poll-idle.ts +0 -149
  106. package/src/tui/poll-permission.ts +0 -50
  107. package/src/tui/status-icon.ts +0 -35
  108. package/src/tui/terminal-input.ts +0 -136
  109. package/src/tui/watch-state.ts +0 -43
  110. package/src/worktree.ts +0 -41
  111. package/tests/claude-command.test.ts +0 -30
  112. package/tests/claude-detect.test.ts +0 -14
  113. package/tests/commands/attach.test.ts +0 -60
  114. package/tests/commands/kill.test.ts +0 -66
  115. package/tests/commands/launch.test.ts +0 -75
  116. package/tests/commands/list.test.ts +0 -47
  117. package/tests/commands/notes.test.ts +0 -53
  118. package/tests/commands/restore.test.ts +0 -126
  119. package/tests/helpers/tmp-config.ts +0 -16
  120. package/tests/helpers/tmp-repo.ts +0 -29
  121. package/tests/integration/orchestrator-spawn.test.ts +0 -108
  122. package/tests/mcp/handlers.test.ts +0 -163
  123. package/tests/mcp/tool-defs.test.ts +0 -35
  124. package/tests/musicians/dismiss.test.ts +0 -102
  125. package/tests/musicians/message.test.ts +0 -159
  126. package/tests/musicians/query.test.ts +0 -65
  127. package/tests/musicians/spawn.test.ts +0 -125
  128. package/tests/notes.test.ts +0 -56
  129. package/tests/notify.test.ts +0 -80
  130. package/tests/orchestrator/report-back.test.ts +0 -18
  131. package/tests/permission.test.ts +0 -39
  132. package/tests/project-key.test.ts +0 -33
  133. package/tests/prompts/tool-discipline.test.ts +0 -25
  134. package/tests/repo.test.ts +0 -38
  135. package/tests/state-updaters.test.ts +0 -126
  136. package/tests/state.test.ts +0 -85
  137. package/tests/tmux.test.ts +0 -126
  138. package/tests/tui/AppView.test.tsx +0 -92
  139. package/tests/tui/Auditorium.test.tsx +0 -67
  140. package/tests/tui/ConcertHall.test.tsx +0 -22
  141. package/tests/tui/Help.test.tsx +0 -38
  142. package/tests/tui/OrchestratorPane.test.ts +0 -30
  143. package/tests/tui/SidebarHeader.test.tsx +0 -20
  144. package/tests/tui/StatusBar.test.tsx +0 -51
  145. package/tests/tui/activity-line.test.ts +0 -21
  146. package/tests/tui/detect-permission.test.ts +0 -92
  147. package/tests/tui/embedded-session-lifecycle.test.ts +0 -55
  148. package/tests/tui/embedded-terminal.test.ts +0 -80
  149. package/tests/tui/format-time.test.ts +0 -25
  150. package/tests/tui/keymap.test.ts +0 -93
  151. package/tests/tui/poll-activity.test.ts +0 -81
  152. package/tests/tui/poll-idle.test.ts +0 -159
  153. package/tests/tui/poll-permission.test.ts +0 -222
  154. package/tests/tui/status-icon.test.ts +0 -27
  155. package/tests/tui/terminal-input.test.ts +0 -113
  156. package/tests/tui/watch-state.test.ts +0 -54
  157. package/tests/worktree.test.ts +0 -73
  158. package/tsconfig.json +0 -19
  159. package/vitest.config.ts +0 -12
@@ -1,31 +0,0 @@
1
- import type { ReactElement } from "react";
2
- import { Box, Text } from "ink";
3
- import type { OrchestraSummary } from "../../commands/list.js";
4
-
5
- export interface ConcertHallProps {
6
- orchestras: OrchestraSummary[];
7
- currentId: string;
8
- }
9
-
10
- export function ConcertHall(props: ConcertHallProps): ReactElement {
11
- return (
12
- <Box
13
- flexDirection="column"
14
- borderStyle="single"
15
- borderBottom={true}
16
- paddingX={1}
17
- >
18
- <Text bold={true}>Concert Hall</Text>
19
- {props.orchestras.map((o) => {
20
- const current = o.id === props.currentId;
21
- const marker = current ? "▸" : " ";
22
- const dot = o.running ? "●" : "○";
23
- return (
24
- <Text key={o.id} bold={current}>
25
- {marker} {dot} {o.id} ({o.musician_count})
26
- </Text>
27
- );
28
- })}
29
- </Box>
30
- );
31
- }
@@ -1,63 +0,0 @@
1
- import type { ReactElement } from "react";
2
- import { Box, Text } from "ink";
3
-
4
- interface Row {
5
- key: string;
6
- label: string;
7
- }
8
-
9
- const ROWS: Row[] = [
10
- { key: "↑ / k", label: "move selection up" },
11
- { key: "↓ / j", label: "move selection down" },
12
- { key: "Enter", label: "open the selected target in the left pane" },
13
- { key: "n", label: "open notes for this orchestra" },
14
- {
15
- key: "d",
16
- label:
17
- "arm dismiss for selected Musician (press d again / y / Enter to confirm)",
18
- },
19
- { key: "n / Esc", label: "cancel pending dismiss confirmation" },
20
- { key: "p", label: "jump to next Musician awaiting permission" },
21
- {
22
- key: "q",
23
- label:
24
- "detach this tmux client from NFO without killing the orchestra session",
25
- },
26
- {
27
- key: "Ctrl+g",
28
- label: "switch focus between the sidebar and the embedded Claude terminal",
29
- },
30
- {
31
- key: "typed keys",
32
- label:
33
- "go directly to the currently open tmux terminal while the left pane is focused",
34
- },
35
- {
36
- key: "Alt+Enter / Shift+Enter / Ctrl+J",
37
- label:
38
- "insert a newline in the focused terminal without treating it like Enter",
39
- },
40
- {
41
- key: "Mouse wheel",
42
- label:
43
- "scroll the left terminal through local scrollback when the pointer is over that pane",
44
- },
45
- { key: "?", label: "toggle this help / close" },
46
- ];
47
-
48
- export function Help(): ReactElement {
49
- return (
50
- <Box flexDirection="column" paddingX={1}>
51
- <Text bold={true}>Keybindings</Text>
52
- {ROWS.map((row) => {
53
- return (
54
- <Text key={row.key}>
55
- <Text color="cyan">{row.key.padEnd(8)}</Text>
56
- <Text> {row.label}</Text>
57
- </Text>
58
- );
59
- })}
60
- <Text dimColor={true}>Press ? to close.</Text>
61
- </Box>
62
- );
63
- }
@@ -1,98 +0,0 @@
1
- import type { ReactElement } from "react";
2
- import { Box, Text } from "ink";
3
- import type {
4
- EmbeddedTerminalLine,
5
- EmbeddedTerminalSpan,
6
- } from "../embedded-terminal.js";
7
-
8
- export interface OrchestratorPaneProps {
9
- title: string;
10
- lines: EmbeddedTerminalLine[];
11
- focused: boolean;
12
- connected: boolean;
13
- }
14
-
15
- export function resolveSpanStyle(
16
- span: EmbeddedTerminalSpan,
17
- focused: boolean,
18
- ): Omit<EmbeddedTerminalSpan, "text" | "cursor"> {
19
- const style: Omit<EmbeddedTerminalSpan, "text" | "cursor"> = {
20
- color: span.color,
21
- backgroundColor: span.backgroundColor,
22
- dimColor: span.dimColor,
23
- bold: span.bold,
24
- italic: span.italic,
25
- underline: span.underline,
26
- strikethrough: span.strikethrough,
27
- inverse: span.inverse,
28
- };
29
- if (span.cursor === true && focused) {
30
- style.color = "black";
31
- style.backgroundColor = "white";
32
- style.inverse = false;
33
- }
34
- return style;
35
- }
36
-
37
- function renderSpan(
38
- span: EmbeddedTerminalSpan,
39
- index: number,
40
- focused: boolean,
41
- ): ReactElement {
42
- const style = resolveSpanStyle(span, focused);
43
- return (
44
- <Text
45
- key={`${index}:${span.text}`}
46
- color={style.color}
47
- backgroundColor={style.backgroundColor}
48
- dimColor={style.dimColor}
49
- bold={style.bold}
50
- italic={style.italic}
51
- underline={style.underline}
52
- strikethrough={style.strikethrough}
53
- inverse={style.inverse}
54
- >
55
- {span.text}
56
- </Text>
57
- );
58
- }
59
-
60
- export function OrchestratorPane(props: OrchestratorPaneProps): ReactElement {
61
- return (
62
- <Box
63
- flexGrow={1}
64
- flexDirection="column"
65
- borderStyle="single"
66
- marginRight={1}
67
- minHeight={16}
68
- paddingX={1}
69
- >
70
- <Text bold={true}>{props.title}</Text>
71
- <Box flexDirection="column" flexGrow={1}>
72
- {props.lines.length > 0 ? (
73
- props.lines.map((line, index) => {
74
- return (
75
- <Text key={String(index)} wrap="truncate-end">
76
- {line.spans.length > 0
77
- ? line.spans.map((span, spanIndex) =>
78
- renderSpan(span, spanIndex, props.focused),
79
- )
80
- : " "}
81
- </Text>
82
- );
83
- })
84
- ) : (
85
- <Text dimColor={true}>Waiting for Claude terminal…</Text>
86
- )}
87
- </Box>
88
- <Text color={props.focused ? "cyan" : "gray"} wrap="truncate-end">
89
- {props.focused
90
- ? "[Ctrl+g] sidebar · [Mouse wheel] scroll"
91
- : "[Ctrl+g] focus left terminal"}
92
- </Text>
93
- {!props.connected ? (
94
- <Text color="yellow">Embedded tmux client disconnected.</Text>
95
- ) : null}
96
- </Box>
97
- );
98
- }
@@ -1,34 +0,0 @@
1
- import type { ReactElement } from "react";
2
- import { Box, Text } from "ink";
3
-
4
- export interface SidebarHeaderProps {
5
- orchestraId: string;
6
- musicianCount: number;
7
- pendingCount: number;
8
- version: string;
9
- }
10
-
11
- export function SidebarHeader(props: SidebarHeaderProps): ReactElement {
12
- return (
13
- <Box
14
- flexDirection="column"
15
- borderStyle="round"
16
- borderBottom={true}
17
- paddingX={1}
18
- >
19
- <Text bold={true}>No Fluff Orchestra · {props.orchestraId}</Text>
20
- <Text bold={true}>v.{props.version}</Text>
21
- {props.pendingCount > 0 ? (
22
- <Text color="yellow">
23
- {props.musicianCount} musicians · {props.pendingCount} awaiting
24
- permission
25
- </Text>
26
- ) : (
27
- <Text dimColor={true}>
28
- {props.musicianCount} musicians · {props.pendingCount} awaiting
29
- permission
30
- </Text>
31
- )}
32
- </Box>
33
- );
34
- }
@@ -1,42 +0,0 @@
1
- import type { ReactElement } from "react";
2
- import { Box, Text } from "ink";
3
-
4
- export interface StatusBarProps {
5
- permissionLevel: string;
6
- tokenHint: string;
7
- pendingCount: number;
8
- dismissConfirmation?: string | null;
9
- orchestratorFocused: boolean;
10
- }
11
-
12
- export function StatusBar(props: StatusBarProps): ReactElement {
13
- return (
14
- <Box
15
- flexDirection="column"
16
- borderStyle="single"
17
- borderTop={true}
18
- paddingX={1}
19
- >
20
- {props.pendingCount > 0 ? (
21
- <Text color="yellow">
22
- ⚠ {props.pendingCount} awaiting permission · [p] jump to next
23
- </Text>
24
- ) : null}
25
- {props.dismissConfirmation ? (
26
- <Text color="red">{props.dismissConfirmation}</Text>
27
- ) : null}
28
- <Text>
29
- {props.permissionLevel} · {props.tokenHint}
30
- </Text>
31
- {props.orchestratorFocused ? (
32
- <Text dimColor={true}>[type] active terminal [Ctrl+g] sidebar</Text>
33
- ) : (
34
- <Text dimColor={true}>
35
- [↑↓] nav [⏎] open left pane [d] dismiss [p] pending [n] notes [Ctrl+g]
36
- terminal
37
- </Text>
38
- )}
39
- <Text dimColor={true}>[q] detach [?] help</Text>
40
- </Box>
41
- );
42
- }
@@ -1,93 +0,0 @@
1
- const LAST_N_LINES = 20;
2
- const MAX_TOOL_LEN = 60;
3
-
4
- const INTRO_PATTERNS: RegExp[] = [
5
- /allow\s+\S+/i,
6
- /do you want to/i,
7
- /permission required/i,
8
- /use this tool/i,
9
- ];
10
-
11
- export interface PermissionDetection {
12
- pending: boolean;
13
- tool: string | null;
14
- }
15
-
16
- function hasYesLine(lines: string[]): boolean {
17
- for (const line of lines) {
18
- const trimmed = line.trimStart();
19
- if (trimmed.startsWith('1.') || trimmed.startsWith('1)')) {
20
- return true;
21
- }
22
- }
23
- return false;
24
- }
25
-
26
- function hasNoLine(lines: string[]): boolean {
27
- for (const line of lines) {
28
- const trimmed = line.trimStart();
29
- const startsWithSmallDigit =
30
- trimmed.startsWith('2.') ||
31
- trimmed.startsWith('2)') ||
32
- trimmed.startsWith('3.') ||
33
- trimmed.startsWith('3)');
34
- if (startsWithSmallDigit && trimmed.includes('No')) {
35
- return true;
36
- }
37
- }
38
- return false;
39
- }
40
-
41
- function hasIntroPattern(lines: string[]): boolean {
42
- const block = lines.join('\n');
43
- for (const pattern of INTRO_PATTERNS) {
44
- if (pattern.test(block)) {
45
- return true;
46
- }
47
- }
48
- return false;
49
- }
50
-
51
- function extractTool(lines: string[]): string | null {
52
- try {
53
- const block = lines.join('\n');
54
- const nameMatch = /^Allow ([A-Z][A-Za-z]+)/m.exec(block);
55
- if (!nameMatch) {
56
- return null;
57
- }
58
- const toolName = nameMatch[1];
59
- // Find the full line that contains the Allow … match, to search for backticks.
60
- const matchIndex = nameMatch.index;
61
- const lineStart = matchIndex;
62
- const lineEnd = block.indexOf('\n', matchIndex);
63
- const fullLine = lineEnd === -1 ? block.slice(lineStart) : block.slice(lineStart, lineEnd);
64
- const backtickMatch = /`([^`]*)`/.exec(fullLine);
65
- let result: string;
66
- if (backtickMatch) {
67
- result = `${toolName}: \`${backtickMatch[1]}\``;
68
- } else {
69
- result = toolName;
70
- }
71
- if (result.length > MAX_TOOL_LEN) {
72
- return result.slice(0, MAX_TOOL_LEN - 1) + '…';
73
- }
74
- return result;
75
- } catch {
76
- return null;
77
- }
78
- }
79
-
80
- export function detectPermissionPrompt(paneText: string): PermissionDetection {
81
- const allLines = paneText.split('\n');
82
- const nonEmpty = allLines.filter((line) => { return line.trim().length > 0; });
83
- const lines = nonEmpty.slice(-LAST_N_LINES);
84
-
85
- const pending = hasYesLine(lines) && hasNoLine(lines) && hasIntroPattern(lines);
86
-
87
- if (!pending) {
88
- return { pending: false, tool: null };
89
- }
90
-
91
- const tool = extractTool(lines);
92
- return { pending: true, tool };
93
- }
@@ -1,44 +0,0 @@
1
- export interface EmbeddedSessionLease {
2
- readonly sessionName: string;
3
- readonly token: number;
4
- }
5
-
6
- const embeddedSessionTokens = new Map<string, number>();
7
- const embeddedSessionQueues = new Map<string, Promise<void>>();
8
-
9
- export function claimEmbeddedSessionLease(
10
- sessionName: string,
11
- ): EmbeddedSessionLease {
12
- const token = (embeddedSessionTokens.get(sessionName) ?? 0) + 1;
13
- embeddedSessionTokens.set(sessionName, token);
14
- return { sessionName, token };
15
- }
16
-
17
- export function embeddedSessionLeaseIsCurrent(
18
- lease: EmbeddedSessionLease,
19
- ): boolean {
20
- return embeddedSessionTokens.get(lease.sessionName) === lease.token;
21
- }
22
-
23
- export async function runEmbeddedSessionOperation<T>(
24
- sessionName: string,
25
- operation: () => Promise<T>,
26
- ): Promise<T> {
27
- const previous = embeddedSessionQueues.get(sessionName) ?? Promise.resolve();
28
- let releaseQueue: (() => void) | undefined;
29
- const current = new Promise<void>((resolve) => {
30
- releaseQueue = resolve;
31
- });
32
- embeddedSessionQueues.set(sessionName, current);
33
-
34
- await previous;
35
-
36
- try {
37
- return await operation();
38
- } finally {
39
- releaseQueue?.();
40
- if (embeddedSessionQueues.get(sessionName) === current) {
41
- embeddedSessionQueues.delete(sessionName);
42
- }
43
- }
44
- }