nfo-cli 0.0.4-improve-prompting → 0.0.6-a89844d-dev
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/claude-command.js +6 -1
- package/dist/claude-command.js.map +1 -1
- package/dist/claude-trust.js +46 -0
- package/dist/claude-trust.js.map +1 -0
- package/dist/cli.js +5 -4
- package/dist/cli.js.map +1 -1
- package/dist/commands/attach.js +8 -8
- package/dist/commands/attach.js.map +1 -1
- package/dist/commands/launch.js +3 -6
- package/dist/commands/launch.js.map +1 -1
- package/dist/commands/restore.js +6 -10
- package/dist/commands/restore.js.map +1 -1
- package/dist/commands/tui.js +17 -1
- package/dist/commands/tui.js.map +1 -1
- package/dist/mcp/handlers.js +5 -0
- package/dist/mcp/handlers.js.map +1 -1
- package/dist/mcp/tool-defs.js +10 -0
- package/dist/mcp/tool-defs.js.map +1 -1
- package/dist/musicians/dismiss.js +1 -1
- package/dist/musicians/dismiss.js.map +1 -1
- package/dist/musicians/reconcile.js +27 -0
- package/dist/musicians/reconcile.js.map +1 -0
- package/dist/musicians/roles.js +15 -0
- package/dist/musicians/roles.js.map +1 -0
- package/dist/musicians/spawn.js +53 -18
- package/dist/musicians/spawn.js.map +1 -1
- package/dist/permission.js +6 -0
- package/dist/permission.js.map +1 -1
- package/dist/prompts/musician-role.js +2 -1
- package/dist/prompts/musician-role.js.map +1 -1
- package/dist/prompts/orchestrator-role.js +18 -6
- package/dist/prompts/orchestrator-role.js.map +1 -1
- package/dist/prompts/tool-discipline.js +7 -3
- package/dist/prompts/tool-discipline.js.map +1 -1
- package/dist/tmux.js +23 -0
- package/dist/tmux.js.map +1 -1
- package/dist/tui/components/App.js +8 -12
- package/dist/tui/components/App.js.map +1 -1
- package/dist/tui/components/Help.js +1 -1
- package/dist/tui/components/Help.js.map +1 -1
- package/dist/tui/keymap.js +1 -1
- package/dist/tui/keymap.js.map +1 -1
- package/package.json +18 -8
- package/assets/agent-screen.png +0 -0
- package/assets/main-screen.png +0 -0
- package/assets/orche-clawd.png +0 -0
- package/dist/tui/App.js +0 -428
- package/dist/tui/App.js.map +0 -1
- package/dist/tui/AppView.js +0 -13
- package/dist/tui/AppView.js.map +0 -1
- package/dist/tui/Auditorium.js +0 -17
- package/dist/tui/Auditorium.js.map +0 -1
- package/dist/tui/ConcertHall.js +0 -11
- package/dist/tui/ConcertHall.js.map +0 -1
- package/dist/tui/Help.js +0 -49
- package/dist/tui/Help.js.map +0 -1
- package/dist/tui/OrchestratorPane.js +0 -34
- package/dist/tui/OrchestratorPane.js.map +0 -1
- package/dist/tui/SidebarHeader.js +0 -6
- package/dist/tui/SidebarHeader.js.map +0 -1
- package/dist/tui/StatusBar.js +0 -6
- package/dist/tui/StatusBar.js.map +0 -1
- package/docs/plans/2026-05-29-nfo-phase-1-bootstrap.md +0 -2152
- package/docs/plans/2026-05-29-nfo-phase-2-mcp-musicians.md +0 -2467
- package/docs/plans/2026-05-29-nfo-phase-3-ink-tui.md +0 -1611
- package/docs/plans/2026-05-29-nfo-phase-4-permission-prompts.md +0 -460
- package/docs/plans/2026-05-29-nfo-phase-5-help-and-notify.md +0 -933
- package/docs/specs/2026-05-29-nfo-design.md +0 -468
- package/plan-explorer-musician-hardening.md +0 -56
- package/src/claude-command.ts +0 -35
- package/src/claude-detect.ts +0 -42
- package/src/cli.ts +0 -197
- package/src/commands/attach.ts +0 -24
- package/src/commands/dashboard-window.ts +0 -33
- package/src/commands/kill.ts +0 -50
- package/src/commands/launch.ts +0 -134
- package/src/commands/list.ts +0 -43
- package/src/commands/mcp-server.ts +0 -18
- package/src/commands/notes.ts +0 -18
- package/src/commands/restore.ts +0 -153
- package/src/commands/tui.tsx +0 -22
- package/src/config.ts +0 -44
- package/src/dashboard.ts +0 -1
- package/src/mcp/config.ts +0 -39
- package/src/mcp/handlers.ts +0 -141
- package/src/mcp/server.ts +0 -50
- package/src/mcp/tool-defs.ts +0 -151
- package/src/musicians/dismiss.ts +0 -60
- package/src/musicians/ids.ts +0 -21
- package/src/musicians/lookup.ts +0 -13
- package/src/musicians/message-log.ts +0 -152
- package/src/musicians/message.ts +0 -99
- package/src/musicians/query.ts +0 -19
- package/src/musicians/spawn.ts +0 -139
- package/src/notes.ts +0 -39
- package/src/notify.ts +0 -62
- package/src/orchestrator/report-back.ts +0 -33
- package/src/permission.ts +0 -30
- package/src/project-key.ts +0 -12
- package/src/prompts/musician-role.ts +0 -22
- package/src/prompts/orchestrator-role.ts +0 -84
- package/src/prompts/tool-discipline.ts +0 -41
- package/src/repo.ts +0 -14
- package/src/shell-quote.ts +0 -7
- package/src/state-updaters.ts +0 -132
- package/src/state.ts +0 -49
- package/src/state.types.ts +0 -67
- package/src/tmux.ts +0 -226
- package/src/tui/activity-line.ts +0 -16
- package/src/tui/components/App.tsx +0 -534
- package/src/tui/components/AppView.tsx +0 -98
- package/src/tui/components/Auditorium.tsx +0 -56
- package/src/tui/components/ConcertHall.tsx +0 -31
- package/src/tui/components/Help.tsx +0 -63
- package/src/tui/components/OrchestratorPane.tsx +0 -98
- package/src/tui/components/SidebarHeader.tsx +0 -34
- package/src/tui/components/StatusBar.tsx +0 -42
- package/src/tui/detect-permission.ts +0 -93
- package/src/tui/embedded-session-lifecycle.ts +0 -44
- package/src/tui/embedded-terminal.ts +0 -325
- package/src/tui/format-time.ts +0 -25
- package/src/tui/keymap.ts +0 -104
- package/src/tui/poll-activity.ts +0 -25
- package/src/tui/poll-idle.ts +0 -149
- package/src/tui/poll-permission.ts +0 -50
- package/src/tui/status-icon.ts +0 -35
- package/src/tui/terminal-input.ts +0 -136
- package/src/tui/watch-state.ts +0 -43
- package/src/worktree.ts +0 -41
- package/tests/claude-command.test.ts +0 -30
- package/tests/claude-detect.test.ts +0 -14
- package/tests/commands/attach.test.ts +0 -60
- package/tests/commands/kill.test.ts +0 -66
- package/tests/commands/launch.test.ts +0 -75
- package/tests/commands/list.test.ts +0 -47
- package/tests/commands/notes.test.ts +0 -53
- package/tests/commands/restore.test.ts +0 -126
- package/tests/helpers/tmp-config.ts +0 -16
- package/tests/helpers/tmp-repo.ts +0 -29
- package/tests/integration/orchestrator-spawn.test.ts +0 -108
- package/tests/mcp/handlers.test.ts +0 -163
- package/tests/mcp/tool-defs.test.ts +0 -35
- package/tests/musicians/dismiss.test.ts +0 -102
- package/tests/musicians/message.test.ts +0 -159
- package/tests/musicians/query.test.ts +0 -65
- package/tests/musicians/spawn.test.ts +0 -125
- package/tests/notes.test.ts +0 -56
- package/tests/notify.test.ts +0 -80
- package/tests/orchestrator/report-back.test.ts +0 -18
- package/tests/permission.test.ts +0 -39
- package/tests/project-key.test.ts +0 -33
- package/tests/prompts/tool-discipline.test.ts +0 -25
- package/tests/repo.test.ts +0 -38
- package/tests/state-updaters.test.ts +0 -126
- package/tests/state.test.ts +0 -85
- package/tests/tmux.test.ts +0 -126
- package/tests/tui/AppView.test.tsx +0 -92
- package/tests/tui/Auditorium.test.tsx +0 -67
- package/tests/tui/ConcertHall.test.tsx +0 -22
- package/tests/tui/Help.test.tsx +0 -38
- package/tests/tui/OrchestratorPane.test.ts +0 -30
- package/tests/tui/SidebarHeader.test.tsx +0 -20
- package/tests/tui/StatusBar.test.tsx +0 -51
- package/tests/tui/activity-line.test.ts +0 -21
- package/tests/tui/detect-permission.test.ts +0 -92
- package/tests/tui/embedded-session-lifecycle.test.ts +0 -55
- package/tests/tui/embedded-terminal.test.ts +0 -80
- package/tests/tui/format-time.test.ts +0 -25
- package/tests/tui/keymap.test.ts +0 -93
- package/tests/tui/poll-activity.test.ts +0 -81
- package/tests/tui/poll-idle.test.ts +0 -159
- package/tests/tui/poll-permission.test.ts +0 -222
- package/tests/tui/status-icon.test.ts +0 -27
- package/tests/tui/terminal-input.test.ts +0 -113
- package/tests/tui/watch-state.test.ts +0 -54
- package/tests/worktree.test.ts +0 -73
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -12
package/src/commands/tui.tsx
DELETED
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
import { render } from "ink";
|
|
2
|
-
import { App } from "../tui/components/App.js";
|
|
3
|
-
import { readState } from "../state.js";
|
|
4
|
-
|
|
5
|
-
export interface RunTuiOptions {
|
|
6
|
-
orchestraId: string;
|
|
7
|
-
version: string;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export async function runTui(opts: RunTuiOptions): Promise<void> {
|
|
11
|
-
const state = await readState(opts.orchestraId);
|
|
12
|
-
if (!state) {
|
|
13
|
-
throw new Error(`Unknown orchestra: ${opts.orchestraId}`);
|
|
14
|
-
}
|
|
15
|
-
const instance = render(
|
|
16
|
-
<App orchestraId={opts.orchestraId} version={opts.version} />,
|
|
17
|
-
{
|
|
18
|
-
exitOnCtrlC: false,
|
|
19
|
-
},
|
|
20
|
-
);
|
|
21
|
-
await instance.waitUntilExit();
|
|
22
|
-
}
|
package/src/config.ts
DELETED
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { homedir } from 'node:os';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
|
|
4
|
-
export const STATE_VERSION = 1;
|
|
5
|
-
export const STATE_FILENAME = 'state.json';
|
|
6
|
-
export const NOTES_DIRNAME = 'notes';
|
|
7
|
-
export const LOGS_DIRNAME = 'logs';
|
|
8
|
-
export const MESSAGE_LOGS_DIRNAME = 'messages';
|
|
9
|
-
export const WORKTREES_DIRNAME = 'worktrees';
|
|
10
|
-
export const ARCHIVE_DIRNAME = 'archive';
|
|
11
|
-
|
|
12
|
-
export const getNFOHome = (): string =>
|
|
13
|
-
(process.env.NFO_HOME && process.env.NFO_HOME !== '')
|
|
14
|
-
? process.env.NFO_HOME
|
|
15
|
-
: join(homedir(), '.config', 'nfo');
|
|
16
|
-
|
|
17
|
-
export const getProjectsDir = (): string => join(getNFOHome(), 'projects');
|
|
18
|
-
export const getGlobalConfigFile = (): string => join(getNFOHome(), 'config.json');
|
|
19
|
-
|
|
20
|
-
// Keep backward-compatible aliases (resolved at call time)
|
|
21
|
-
export const NFO_HOME = getNFOHome();
|
|
22
|
-
export const PROJECTS_DIR = getProjectsDir();
|
|
23
|
-
export const GLOBAL_CONFIG_FILE = getGlobalConfigFile();
|
|
24
|
-
|
|
25
|
-
export const orchestraDir = (projectKey: string): string =>
|
|
26
|
-
join(getProjectsDir(), projectKey);
|
|
27
|
-
|
|
28
|
-
export const stateFile = (projectKey: string): string =>
|
|
29
|
-
join(orchestraDir(projectKey), STATE_FILENAME);
|
|
30
|
-
|
|
31
|
-
export const notesDir = (projectKey: string): string =>
|
|
32
|
-
join(orchestraDir(projectKey), NOTES_DIRNAME);
|
|
33
|
-
|
|
34
|
-
export const logsDir = (projectKey: string): string =>
|
|
35
|
-
join(orchestraDir(projectKey), LOGS_DIRNAME);
|
|
36
|
-
|
|
37
|
-
export const messageLogsDir = (projectKey: string): string =>
|
|
38
|
-
join(orchestraDir(projectKey), MESSAGE_LOGS_DIRNAME);
|
|
39
|
-
|
|
40
|
-
export const worktreesDir = (projectKey: string): string =>
|
|
41
|
-
join(orchestraDir(projectKey), WORKTREES_DIRNAME);
|
|
42
|
-
|
|
43
|
-
export const archiveDir = (projectKey: string): string =>
|
|
44
|
-
join(orchestraDir(projectKey), ARCHIVE_DIRNAME);
|
package/src/dashboard.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export const DASHBOARD_WINDOW_NAME = 'nfo-dashboard';
|
package/src/mcp/config.ts
DELETED
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import { writeFile } from 'node:fs/promises';
|
|
2
|
-
import { join } from 'node:path';
|
|
3
|
-
import { orchestraDir } from '../config.js';
|
|
4
|
-
|
|
5
|
-
export function orchestratorMcpConfigPath(orchestraId: string): string {
|
|
6
|
-
return join(orchestraDir(orchestraId), 'mcp-config.json');
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function musicianMcpConfigPath(orchestraId: string, musicianId: string): string {
|
|
10
|
-
return join(orchestraDir(orchestraId), `mcp-${musicianId}.json`);
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export async function writeOrchestratorMcpConfig(orchestraId: string): Promise<string> {
|
|
14
|
-
const path = orchestratorMcpConfigPath(orchestraId);
|
|
15
|
-
await writeMcpConfig(path, orchestraId);
|
|
16
|
-
return path;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export async function writeMusicianMcpConfig(orchestraId: string, musicianId: string): Promise<string> {
|
|
20
|
-
const path = musicianMcpConfigPath(orchestraId, musicianId);
|
|
21
|
-
await writeMcpConfig(path, orchestraId, musicianId);
|
|
22
|
-
return path;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
async function writeMcpConfig(path: string, orchestraId: string, callerMusicianId?: string): Promise<void> {
|
|
26
|
-
const args = ['mcp-server', '--orchestra-id', orchestraId];
|
|
27
|
-
if (callerMusicianId) {
|
|
28
|
-
args.push('--caller-musician-id', callerMusicianId);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
await writeFile(path, JSON.stringify({
|
|
32
|
-
mcpServers: {
|
|
33
|
-
nfo: {
|
|
34
|
-
command: 'nfo',
|
|
35
|
-
args,
|
|
36
|
-
},
|
|
37
|
-
},
|
|
38
|
-
}, null, 2), 'utf8');
|
|
39
|
-
}
|
package/src/mcp/handlers.ts
DELETED
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { createMusician } from "../musicians/spawn.js";
|
|
2
|
-
import {
|
|
3
|
-
drainQueuedMusicianMessages,
|
|
4
|
-
messageMusician,
|
|
5
|
-
} from "../musicians/message.js";
|
|
6
|
-
import { queryMusician } from "../musicians/query.js";
|
|
7
|
-
import { dismissMusician } from "../musicians/dismiss.js";
|
|
8
|
-
import { noteRead, noteWrite, noteList } from "../notes.js";
|
|
9
|
-
import { readState } from "../state.js";
|
|
10
|
-
import {
|
|
11
|
-
setMusicianLatestReport,
|
|
12
|
-
setMusicianStatus,
|
|
13
|
-
} from "../state-updaters.js";
|
|
14
|
-
import { findMusicianStrict } from "../musicians/lookup.js";
|
|
15
|
-
import { notifyOrchestratorOfDoneReport } from "../orchestrator/report-back.js";
|
|
16
|
-
|
|
17
|
-
export interface DispatchOptions {
|
|
18
|
-
dryRun?: boolean;
|
|
19
|
-
callerMusicianId?: string;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export async function dispatch(
|
|
23
|
-
orchestraId: string,
|
|
24
|
-
toolName: string,
|
|
25
|
-
args: Record<string, unknown>,
|
|
26
|
-
opts: DispatchOptions = {},
|
|
27
|
-
): Promise<Record<string, unknown>> {
|
|
28
|
-
switch (toolName) {
|
|
29
|
-
case "spawn_musician": {
|
|
30
|
-
const r = await createMusician({
|
|
31
|
-
orchestraId,
|
|
32
|
-
name: String(args.name),
|
|
33
|
-
task: String(args.task),
|
|
34
|
-
worktree:
|
|
35
|
-
typeof args.worktree === "boolean" ? args.worktree : undefined,
|
|
36
|
-
branchFrom:
|
|
37
|
-
typeof args.branch_from === "string" ? args.branch_from : undefined,
|
|
38
|
-
model:
|
|
39
|
-
typeof args.model === "string"
|
|
40
|
-
? (args.model as "sonnet" | "haiku")
|
|
41
|
-
: "sonnet",
|
|
42
|
-
dryRun: opts.dryRun,
|
|
43
|
-
});
|
|
44
|
-
return r;
|
|
45
|
-
}
|
|
46
|
-
case "message_musician": {
|
|
47
|
-
const result = await messageMusician({
|
|
48
|
-
orchestraId,
|
|
49
|
-
musicianId: String(args.musician_id),
|
|
50
|
-
message: String(args.message),
|
|
51
|
-
});
|
|
52
|
-
return { ...result };
|
|
53
|
-
}
|
|
54
|
-
case "query_musician": {
|
|
55
|
-
const text = await queryMusician({
|
|
56
|
-
orchestraId,
|
|
57
|
-
musicianId: String(args.musician_id),
|
|
58
|
-
lines: typeof args.lines === "number" ? args.lines : undefined,
|
|
59
|
-
});
|
|
60
|
-
return { content: text };
|
|
61
|
-
}
|
|
62
|
-
case "list_musicians": {
|
|
63
|
-
const state = await readState(orchestraId);
|
|
64
|
-
if (!state) {
|
|
65
|
-
throw new Error(`Unknown orchestra: ${orchestraId}`);
|
|
66
|
-
}
|
|
67
|
-
return { musicians: state.musicians };
|
|
68
|
-
}
|
|
69
|
-
case "dismiss_musician": {
|
|
70
|
-
await dismissMusician({
|
|
71
|
-
orchestraId,
|
|
72
|
-
musicianId: String(args.musician_id),
|
|
73
|
-
archiveWorktree:
|
|
74
|
-
typeof args.archive_worktree === "boolean"
|
|
75
|
-
? args.archive_worktree
|
|
76
|
-
: undefined,
|
|
77
|
-
summary: typeof args.summary === "string" ? args.summary : null,
|
|
78
|
-
});
|
|
79
|
-
return { ok: true };
|
|
80
|
-
}
|
|
81
|
-
case "report_done": {
|
|
82
|
-
const state = await readState(orchestraId);
|
|
83
|
-
if (!state) {
|
|
84
|
-
throw new Error(`Unknown orchestra: ${orchestraId}`);
|
|
85
|
-
}
|
|
86
|
-
const summary = typeof args.summary === "string" ? args.summary : "";
|
|
87
|
-
const nextSteps =
|
|
88
|
-
typeof args.next_steps === "string" ? args.next_steps : null;
|
|
89
|
-
const callerId =
|
|
90
|
-
typeof args._from_musician_id === "string"
|
|
91
|
-
? args._from_musician_id
|
|
92
|
-
: opts.callerMusicianId;
|
|
93
|
-
if (!callerId) {
|
|
94
|
-
throw new Error("report_done: no caller musician id");
|
|
95
|
-
}
|
|
96
|
-
const musician = findMusicianStrict(state, callerId);
|
|
97
|
-
const reportedAt = new Date().toISOString();
|
|
98
|
-
await setMusicianLatestReport(orchestraId, callerId, {
|
|
99
|
-
summary,
|
|
100
|
-
next_steps: nextSteps,
|
|
101
|
-
reported_at: reportedAt,
|
|
102
|
-
});
|
|
103
|
-
await setMusicianStatus(orchestraId, callerId, "idle");
|
|
104
|
-
const deliveredMessages = await drainQueuedMusicianMessages(
|
|
105
|
-
orchestraId,
|
|
106
|
-
callerId,
|
|
107
|
-
);
|
|
108
|
-
if (deliveredMessages > 0) {
|
|
109
|
-
await setMusicianStatus(orchestraId, callerId, "working");
|
|
110
|
-
} else {
|
|
111
|
-
await notifyOrchestratorOfDoneReport(orchestraId, {
|
|
112
|
-
musicianId: callerId,
|
|
113
|
-
musicianName: musician.name,
|
|
114
|
-
summary,
|
|
115
|
-
nextSteps,
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
return {
|
|
119
|
-
ok: true,
|
|
120
|
-
recorded: summary,
|
|
121
|
-
delivered_messages: deliveredMessages,
|
|
122
|
-
notified_orchestrator: deliveredMessages === 0,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
case "note_write": {
|
|
126
|
-
await noteWrite(orchestraId, String(args.filename), String(args.content));
|
|
127
|
-
return { ok: true };
|
|
128
|
-
}
|
|
129
|
-
case "note_read": {
|
|
130
|
-
const content = await noteRead(orchestraId, String(args.filename));
|
|
131
|
-
return { content };
|
|
132
|
-
}
|
|
133
|
-
case "note_list": {
|
|
134
|
-
const files = await noteList(orchestraId);
|
|
135
|
-
return { files };
|
|
136
|
-
}
|
|
137
|
-
default: {
|
|
138
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
}
|
package/src/mcp/server.ts
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
2
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
-
import {
|
|
4
|
-
CallToolRequestSchema,
|
|
5
|
-
ListToolsRequestSchema,
|
|
6
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
7
|
-
import { NFO_TOOLS } from "./tool-defs.js";
|
|
8
|
-
import { dispatch } from "./handlers.js";
|
|
9
|
-
|
|
10
|
-
export interface RunServerOptions {
|
|
11
|
-
orchestraId: string;
|
|
12
|
-
callerMusicianId?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export async function runServer(opts: RunServerOptions): Promise<void> {
|
|
16
|
-
const server = new Server(
|
|
17
|
-
{ name: "nfo-mcp", version: "0.0.0" },
|
|
18
|
-
{ capabilities: { tools: {} } },
|
|
19
|
-
);
|
|
20
|
-
|
|
21
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
22
|
-
return { tools: NFO_TOOLS };
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
26
|
-
const { name, arguments: args } = request.params;
|
|
27
|
-
try {
|
|
28
|
-
const result = await dispatch(
|
|
29
|
-
opts.orchestraId,
|
|
30
|
-
name,
|
|
31
|
-
(args ?? {}) as Record<string, unknown>,
|
|
32
|
-
{
|
|
33
|
-
callerMusicianId: opts.callerMusicianId,
|
|
34
|
-
},
|
|
35
|
-
);
|
|
36
|
-
return {
|
|
37
|
-
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
38
|
-
};
|
|
39
|
-
} catch (err) {
|
|
40
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
41
|
-
return {
|
|
42
|
-
content: [{ type: "text", text: `Error: ${msg}` }],
|
|
43
|
-
isError: true,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
const transport = new StdioServerTransport();
|
|
49
|
-
await server.connect(transport);
|
|
50
|
-
}
|
package/src/mcp/tool-defs.ts
DELETED
|
@@ -1,151 +0,0 @@
|
|
|
1
|
-
export interface NfoToolDef {
|
|
2
|
-
name: string;
|
|
3
|
-
description: string;
|
|
4
|
-
inputSchema: {
|
|
5
|
-
type: "object";
|
|
6
|
-
properties: Record<string, unknown>;
|
|
7
|
-
required?: string[];
|
|
8
|
-
additionalProperties?: boolean;
|
|
9
|
-
};
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export const NFO_TOOLS: NfoToolDef[] = [
|
|
13
|
-
{
|
|
14
|
-
name: "spawn_musician",
|
|
15
|
-
description:
|
|
16
|
-
"Spawn a new Musician (a Claude Code subagent) to work on a task in an isolated git worktree. Use this for delegation instead of describing work in chat. Returns the musician_id. Haiku is perfect for trivial tasks while Sonnett is better for complex ones; both are strong at code. If you don't know which to choose, sonnet is a good default.",
|
|
17
|
-
inputSchema: {
|
|
18
|
-
type: "object",
|
|
19
|
-
properties: {
|
|
20
|
-
name: {
|
|
21
|
-
type: "string",
|
|
22
|
-
description: 'Human-friendly identifier (e.g. "test-writer")',
|
|
23
|
-
},
|
|
24
|
-
task: {
|
|
25
|
-
type: "string",
|
|
26
|
-
description: "Initial task prompt sent as the first message",
|
|
27
|
-
},
|
|
28
|
-
worktree: {
|
|
29
|
-
type: "boolean",
|
|
30
|
-
description:
|
|
31
|
-
"Default true. Pass false for trivially-isolated work that does not need a worktree.",
|
|
32
|
-
},
|
|
33
|
-
branch_from: {
|
|
34
|
-
type: "string",
|
|
35
|
-
description: "Optional base ref (defaults to HEAD).",
|
|
36
|
-
},
|
|
37
|
-
model: {
|
|
38
|
-
type: "string",
|
|
39
|
-
enum: ["sonnet", "haiku"],
|
|
40
|
-
description: "Optional subagent model (defaults to sonnet).",
|
|
41
|
-
},
|
|
42
|
-
},
|
|
43
|
-
required: ["name", "task"],
|
|
44
|
-
additionalProperties: false,
|
|
45
|
-
},
|
|
46
|
-
},
|
|
47
|
-
{
|
|
48
|
-
name: "message_musician",
|
|
49
|
-
description:
|
|
50
|
-
"Send a message to a Musician. Use this for iteration/follow-up instead of plain chat. If they are idle it is delivered now; otherwise it is queued and auto-delivered on the next idle boundary.",
|
|
51
|
-
inputSchema: {
|
|
52
|
-
type: "object",
|
|
53
|
-
properties: {
|
|
54
|
-
musician_id: { type: "string" },
|
|
55
|
-
message: { type: "string" },
|
|
56
|
-
},
|
|
57
|
-
required: ["musician_id", "message"],
|
|
58
|
-
additionalProperties: false,
|
|
59
|
-
},
|
|
60
|
-
},
|
|
61
|
-
{
|
|
62
|
-
name: "query_musician",
|
|
63
|
-
description:
|
|
64
|
-
"Read the most recent visible output from a Musician's tmux pane. Returns the captured text.",
|
|
65
|
-
inputSchema: {
|
|
66
|
-
type: "object",
|
|
67
|
-
properties: {
|
|
68
|
-
musician_id: { type: "string" },
|
|
69
|
-
lines: { type: "integer", description: "Default 80." },
|
|
70
|
-
},
|
|
71
|
-
required: ["musician_id"],
|
|
72
|
-
additionalProperties: false,
|
|
73
|
-
},
|
|
74
|
-
},
|
|
75
|
-
{
|
|
76
|
-
name: "list_musicians",
|
|
77
|
-
description:
|
|
78
|
-
"List all currently-active Musicians with their status and metadata.",
|
|
79
|
-
inputSchema: {
|
|
80
|
-
type: "object",
|
|
81
|
-
properties: {},
|
|
82
|
-
additionalProperties: false,
|
|
83
|
-
},
|
|
84
|
-
},
|
|
85
|
-
{
|
|
86
|
-
name: "dismiss_musician",
|
|
87
|
-
description:
|
|
88
|
-
"Tear down a Musician. Use this to accept and close out work rather than saying the Musician is done in prose. Archives the worktree by default (branch preserved); pass archive_worktree=false to drop everything.",
|
|
89
|
-
inputSchema: {
|
|
90
|
-
type: "object",
|
|
91
|
-
properties: {
|
|
92
|
-
musician_id: { type: "string" },
|
|
93
|
-
archive_worktree: { type: "boolean" },
|
|
94
|
-
summary: { type: "string" },
|
|
95
|
-
},
|
|
96
|
-
required: ["musician_id"],
|
|
97
|
-
additionalProperties: false,
|
|
98
|
-
},
|
|
99
|
-
},
|
|
100
|
-
{
|
|
101
|
-
name: "report_done",
|
|
102
|
-
description:
|
|
103
|
-
"Called by a Musician to hand work back to the Orchestrator and mark itself as idle/done. Musicians must use this instead of plain-text completion messages. Queued follow-up messages are delivered automatically; otherwise the completion report is pushed back to the Orchestrator for dismiss-vs-iterate triage.",
|
|
104
|
-
inputSchema: {
|
|
105
|
-
type: "object",
|
|
106
|
-
properties: {
|
|
107
|
-
summary: { type: "string" },
|
|
108
|
-
next_steps: { type: "string" },
|
|
109
|
-
},
|
|
110
|
-
required: ["summary"],
|
|
111
|
-
additionalProperties: false,
|
|
112
|
-
},
|
|
113
|
-
},
|
|
114
|
-
{
|
|
115
|
-
name: "note_write",
|
|
116
|
-
description:
|
|
117
|
-
"Write (or replace) a note file under the orchestra's notes/ directory.",
|
|
118
|
-
inputSchema: {
|
|
119
|
-
type: "object",
|
|
120
|
-
properties: {
|
|
121
|
-
filename: {
|
|
122
|
-
type: "string",
|
|
123
|
-
description: 'Filename with no path separators, e.g. "overview.md".',
|
|
124
|
-
},
|
|
125
|
-
content: { type: "string" },
|
|
126
|
-
},
|
|
127
|
-
required: ["filename", "content"],
|
|
128
|
-
additionalProperties: false,
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
{
|
|
132
|
-
name: "note_read",
|
|
133
|
-
description:
|
|
134
|
-
"Read a note file from the orchestra's notes/ directory. Returns the contents, or empty string if missing.",
|
|
135
|
-
inputSchema: {
|
|
136
|
-
type: "object",
|
|
137
|
-
properties: { filename: { type: "string" } },
|
|
138
|
-
required: ["filename"],
|
|
139
|
-
additionalProperties: false,
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
name: "note_list",
|
|
144
|
-
description: "List all files in the orchestra's notes/ directory.",
|
|
145
|
-
inputSchema: {
|
|
146
|
-
type: "object",
|
|
147
|
-
properties: {},
|
|
148
|
-
additionalProperties: false,
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
];
|
package/src/musicians/dismiss.ts
DELETED
|
@@ -1,60 +0,0 @@
|
|
|
1
|
-
import { mkdir } from 'node:fs/promises';
|
|
2
|
-
import { dirname, join } from 'node:path';
|
|
3
|
-
import { execa } from 'execa';
|
|
4
|
-
import { archiveMusician } from '../state-updaters.js';
|
|
5
|
-
import { readState } from '../state.js';
|
|
6
|
-
import { findMusicianStrict } from './lookup.js';
|
|
7
|
-
import { archiveDir } from '../config.js';
|
|
8
|
-
import { sessionName } from '../tmux.js';
|
|
9
|
-
import { removeWorktree, deleteBranch } from '../worktree.js';
|
|
10
|
-
|
|
11
|
-
export interface DismissMusicianOptions {
|
|
12
|
-
orchestraId: string;
|
|
13
|
-
musicianId: string;
|
|
14
|
-
archiveWorktree?: boolean; // default true
|
|
15
|
-
summary?: string | null;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export async function dismissMusician(opts: DismissMusicianOptions): Promise<void> {
|
|
19
|
-
const state = await readState(opts.orchestraId);
|
|
20
|
-
if (!state) {
|
|
21
|
-
throw new Error(`Unknown orchestra: ${opts.orchestraId}`);
|
|
22
|
-
}
|
|
23
|
-
const musician = findMusicianStrict(state, opts.musicianId);
|
|
24
|
-
const archivedSummary = opts.summary !== undefined
|
|
25
|
-
? opts.summary
|
|
26
|
-
: (musician.status === 'idle' ? musician.latest_report?.summary ?? null : null);
|
|
27
|
-
|
|
28
|
-
const archive = opts.archiveWorktree !== false;
|
|
29
|
-
|
|
30
|
-
// 1. Best-effort graceful shutdown of claude, then kill the window.
|
|
31
|
-
const target = `${sessionName(opts.orchestraId)}:${musician.tmux_window_id}`;
|
|
32
|
-
await execa('tmux', ['send-keys', '-l', '-t', target, '--', '/quit'], { reject: false });
|
|
33
|
-
await execa('tmux', ['send-keys', '-t', target, 'Enter'], { reject: false });
|
|
34
|
-
await new Promise((r) => { setTimeout(r, 200); });
|
|
35
|
-
await execa('tmux', ['kill-window', '-t', target], { reject: false });
|
|
36
|
-
|
|
37
|
-
// 2. Worktree handling.
|
|
38
|
-
if (musician.worktree_path) {
|
|
39
|
-
if (archive) {
|
|
40
|
-
const dest = join(archiveDir(opts.orchestraId), opts.musicianId, 'worktree');
|
|
41
|
-
await mkdir(dirname(dest), { recursive: true });
|
|
42
|
-
const moved = await execa('git', ['worktree', 'move', musician.worktree_path, dest], {
|
|
43
|
-
cwd: state.project_path, reject: false,
|
|
44
|
-
});
|
|
45
|
-
if (moved.exitCode !== 0) {
|
|
46
|
-
await removeWorktree({ repoRoot: state.project_path, path: musician.worktree_path, force: true });
|
|
47
|
-
}
|
|
48
|
-
} else {
|
|
49
|
-
await removeWorktree({ repoRoot: state.project_path, path: musician.worktree_path, force: true });
|
|
50
|
-
if (musician.branch) {
|
|
51
|
-
await deleteBranch(state.project_path, musician.branch);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 3. Move state row.
|
|
57
|
-
await archiveMusician(opts.orchestraId, opts.musicianId, {
|
|
58
|
-
summary: archivedSummary,
|
|
59
|
-
});
|
|
60
|
-
}
|
package/src/musicians/ids.ts
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import type { OrchestraState } from '../state.types.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Generate the next musician id. Format: `mus-NNN` where NNN is zero-padded
|
|
5
|
-
* to 3 digits and counts active + archived musicians. Never reuses an id even
|
|
6
|
-
* after a musician is archived (avoids confusion in logs).
|
|
7
|
-
*/
|
|
8
|
-
export function nextMusicianId(state: OrchestraState): string {
|
|
9
|
-
const used = new Set<string>();
|
|
10
|
-
for (const m of state.musicians) {
|
|
11
|
-
used.add(m.id);
|
|
12
|
-
}
|
|
13
|
-
for (const m of state.archived_musicians) {
|
|
14
|
-
used.add(m.id);
|
|
15
|
-
}
|
|
16
|
-
let n = used.size + 1;
|
|
17
|
-
while (used.has(`mus-${String(n).padStart(3, '0')}`)) {
|
|
18
|
-
n++;
|
|
19
|
-
}
|
|
20
|
-
return `mus-${String(n).padStart(3, '0')}`;
|
|
21
|
-
}
|
package/src/musicians/lookup.ts
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import type { Musician, OrchestraState } from '../state.types.js';
|
|
2
|
-
|
|
3
|
-
export function findMusician(state: OrchestraState, id: string): Musician | undefined {
|
|
4
|
-
return state.musicians.find((m) => { return m.id === id; });
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function findMusicianStrict(state: OrchestraState, id: string): Musician {
|
|
8
|
-
const m = findMusician(state, id);
|
|
9
|
-
if (!m) {
|
|
10
|
-
throw new Error(`Unknown musician: ${id}`);
|
|
11
|
-
}
|
|
12
|
-
return m;
|
|
13
|
-
}
|