@nemus-cli/nemus 0.2.1
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/CHANGELOG.md +86 -0
- package/LICENSE +21 -0
- package/README.md +275 -0
- package/assets/README.md +53 -0
- package/assets/banner.png +0 -0
- package/assets/banner.svg +66 -0
- package/assets/favicon-32.png +0 -0
- package/assets/favicon-64.png +0 -0
- package/assets/icon-256.png +0 -0
- package/assets/icon.svg +23 -0
- package/assets/logo-256.png +0 -0
- package/assets/logo-512.png +0 -0
- package/assets/logo-wordmark.svg +35 -0
- package/assets/logo.svg +32 -0
- package/bin/workspace.js +130 -0
- package/dist/cli/ai-prompt.js +421 -0
- package/dist/cli/dashboard/AgentList.js +61 -0
- package/dist/cli/dashboard/DashboardSidebar.js +280 -0
- package/dist/cli/dashboard/HelpFooter.js +73 -0
- package/dist/cli/dashboard/StatusBadge.js +50 -0
- package/dist/cli/dashboard/index.js +11 -0
- package/dist/cli/dashboard/types.js +2 -0
- package/dist/cli/tui.js +48 -0
- package/dist/commands/ai-prompt.js +22 -0
- package/dist/commands/analyze-deps.js +138 -0
- package/dist/commands/archive.js +113 -0
- package/dist/commands/branch/create.js +119 -0
- package/dist/commands/branch/index.js +104 -0
- package/dist/commands/branch/merge.js +90 -0
- package/dist/commands/branch/rebase.js +84 -0
- package/dist/commands/branch/switch.js +165 -0
- package/dist/commands/cache/index.js +80 -0
- package/dist/commands/cache/manager.js +198 -0
- package/dist/commands/cleanup.js +153 -0
- package/dist/commands/configure-claude.js +55 -0
- package/dist/commands/configure.js +231 -0
- package/dist/commands/create.js +204 -0
- package/dist/commands/dashboard/hook-handler.js +162 -0
- package/dist/commands/dashboard/index.js +258 -0
- package/dist/commands/dashboard/launcher.js +40 -0
- package/dist/commands/dashboard/session-picker.js +106 -0
- package/dist/commands/dashboard/sidebar.js +39 -0
- package/dist/commands/dashboard/workspace-picker.js +95 -0
- package/dist/commands/delete.js +194 -0
- package/dist/commands/deprecated-aliases.js +44 -0
- package/dist/commands/diff.js +129 -0
- package/dist/commands/doctor.js +141 -0
- package/dist/commands/generate-docs.js +77 -0
- package/dist/commands/ghq-status.js +72 -0
- package/dist/commands/go.js +154 -0
- package/dist/commands/history.js +124 -0
- package/dist/commands/list.js +191 -0
- package/dist/commands/mcp/index.js +69 -0
- package/dist/commands/migrate.js +296 -0
- package/dist/commands/remove-repo.js +217 -0
- package/dist/commands/report-bug.js +112 -0
- package/dist/commands/run.js +147 -0
- package/dist/commands/save-context.js +171 -0
- package/dist/commands/sessions.js +129 -0
- package/dist/commands/status.js +138 -0
- package/dist/commands/suite/create.js +212 -0
- package/dist/commands/suite/delete.js +64 -0
- package/dist/commands/suite/export.js +126 -0
- package/dist/commands/suite/import.js +128 -0
- package/dist/commands/suite/index.js +94 -0
- package/dist/commands/suite/list.js +49 -0
- package/dist/commands/suite/use.js +227 -0
- package/dist/commands/sync.js +117 -0
- package/dist/commands/update.js +203 -0
- package/dist/mcp/install.js +550 -0
- package/dist/mcp/server.js +398 -0
- package/dist/mcp/tools.js +922 -0
- package/dist/pi-extensions/permission-sync.ts.template +41 -0
- package/dist/pi-extensions/permissions-gate.ts.template +69 -0
- package/dist/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/dist/pi-extensions/review-reminder.ts.template +58 -0
- package/dist/pi-extensions/workspace-input-status.ts.template +919 -0
- package/dist/program.js +136 -0
- package/dist/scripts/workspace-table.py +394 -0
- package/dist/types/dashboard.js +11 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/agent-config.js +263 -0
- package/dist/utils/agent-state.js +178 -0
- package/dist/utils/banner.js +24 -0
- package/dist/utils/branch-operations.js +232 -0
- package/dist/utils/bug-report.js +355 -0
- package/dist/utils/cache.js +168 -0
- package/dist/utils/claude-integration.js +617 -0
- package/dist/utils/claude-sessions.js +260 -0
- package/dist/utils/cleanup-operations.js +120 -0
- package/dist/utils/colors.js +26 -0
- package/dist/utils/command-helpers.js +44 -0
- package/dist/utils/config.js +150 -0
- package/dist/utils/context-file.js +33 -0
- package/dist/utils/dashboard-hooks.js +172 -0
- package/dist/utils/dependency-analyzer.js +234 -0
- package/dist/utils/diff-operations.js +80 -0
- package/dist/utils/doc-generator.js +184 -0
- package/dist/utils/ghq-integration.js +332 -0
- package/dist/utils/git-operations.js +237 -0
- package/dist/utils/git-status.js +187 -0
- package/dist/utils/github.js +68 -0
- package/dist/utils/health-checks.js +345 -0
- package/dist/utils/history.js +150 -0
- package/dist/utils/hooks.js +100 -0
- package/dist/utils/logger.js +40 -0
- package/dist/utils/permission-sync.js +681 -0
- package/dist/utils/pi-extensions.js +224 -0
- package/dist/utils/progress.js +92 -0
- package/dist/utils/prompts.js +279 -0
- package/dist/utils/repo-resolver.js +260 -0
- package/dist/utils/retry.js +65 -0
- package/dist/utils/run-operations.js +30 -0
- package/dist/utils/suite.js +159 -0
- package/dist/utils/sync-operations.js +107 -0
- package/dist/utils/tmux-dashboard.js +184 -0
- package/dist/utils/validation.js +153 -0
- package/dist/utils/version-check.js +121 -0
- package/dist/utils/workspace-meta.js +185 -0
- package/install-shell-integration.sh +532 -0
- package/install.sh +401 -0
- package/package.json +90 -0
- package/remove-shell-block.awk +44 -0
- package/scripts/postinstall.js +85 -0
- package/skills/analyze-deps.md +23 -0
- package/skills/archive-workspace.md +26 -0
- package/skills/branch-create.md +18 -0
- package/skills/create-workspace.md +39 -0
- package/skills/delete-workspace.md +37 -0
- package/skills/list-org-repos.md +26 -0
- package/skills/list-suites.md +24 -0
- package/skills/list-workspaces.md +27 -0
- package/skills/refresh-workspace-docs.md +89 -0
- package/skills/remove-repo.md +25 -0
- package/skills/run-command.md +23 -0
- package/skills/save-context.md +68 -0
- package/skills/search-repos.md +31 -0
- package/skills/snapshot-list.md +18 -0
- package/skills/snapshot-save.md +12 -0
- package/skills/switch-branch.md +18 -0
- package/skills/update-cache.md +21 -0
- package/skills/update-workspace.md +66 -0
- package/skills/workspace-cleanup.md +18 -0
- package/skills/workspace-diff.md +23 -0
- package/skills/workspace-doctor.md +23 -0
- package/skills/workspace-info.md +45 -0
- package/skills/workspace-manager/SKILL.md +201 -0
- package/skills/workspace-manager/references/ai-prompt.md +44 -0
- package/skills/workspace-manager/references/analyze-deps.md +45 -0
- package/skills/workspace-manager/references/archive-workspace.md +38 -0
- package/skills/workspace-manager/references/branch-create.md +31 -0
- package/skills/workspace-manager/references/branch-merge.md +22 -0
- package/skills/workspace-manager/references/branch-rebase.md +22 -0
- package/skills/workspace-manager/references/cache-clear.md +18 -0
- package/skills/workspace-manager/references/cache-info.md +22 -0
- package/skills/workspace-manager/references/configure-claude.md +31 -0
- package/skills/workspace-manager/references/configure.md +35 -0
- package/skills/workspace-manager/references/create-workspace.md +45 -0
- package/skills/workspace-manager/references/dashboard.md +61 -0
- package/skills/workspace-manager/references/delete-workspace.md +33 -0
- package/skills/workspace-manager/references/generate-docs.md +36 -0
- package/skills/workspace-manager/references/ghq-status.md +28 -0
- package/skills/workspace-manager/references/go.md +33 -0
- package/skills/workspace-manager/references/history.md +41 -0
- package/skills/workspace-manager/references/list-org-repos.md +26 -0
- package/skills/workspace-manager/references/list-suites.md +21 -0
- package/skills/workspace-manager/references/list-workspaces.md +27 -0
- package/skills/workspace-manager/references/mcp.md +49 -0
- package/skills/workspace-manager/references/remove-repo.md +26 -0
- package/skills/workspace-manager/references/run-command.md +44 -0
- package/skills/workspace-manager/references/search-repos.md +24 -0
- package/skills/workspace-manager/references/sessions.md +27 -0
- package/skills/workspace-manager/references/suite-create.md +22 -0
- package/skills/workspace-manager/references/suite-delete.md +21 -0
- package/skills/workspace-manager/references/suite-export.md +19 -0
- package/skills/workspace-manager/references/suite-import.md +19 -0
- package/skills/workspace-manager/references/suite-use.md +27 -0
- package/skills/workspace-manager/references/switch-branch.md +25 -0
- package/skills/workspace-manager/references/update-cache.md +22 -0
- package/skills/workspace-manager/references/update-workspace.md +50 -0
- package/skills/workspace-manager/references/workspace-cleanup.md +33 -0
- package/skills/workspace-manager/references/workspace-diff.md +27 -0
- package/skills/workspace-manager/references/workspace-doctor.md +27 -0
- package/skills/workspace-manager/references/workspace-info.md +33 -0
- package/skills/workspace-manager/references/workspace-status.md +43 -0
- package/skills/workspace-manager/references/workspace-sync.md +26 -0
- package/skills/workspace-status.md +26 -0
- package/skills/workspace-sync.md +23 -0
- package/src/cli/ai-prompt.test.ts +667 -0
- package/src/cli/ai-prompt.ts +421 -0
- package/src/cli/dashboard/AgentList.tsx +44 -0
- package/src/cli/dashboard/DashboardSidebar.tsx +291 -0
- package/src/cli/dashboard/HelpFooter.tsx +22 -0
- package/src/cli/dashboard/StatusBadge.tsx +23 -0
- package/src/cli/dashboard/index.ts +5 -0
- package/src/cli/dashboard/types.ts +16 -0
- package/src/cli/tui.tsx +51 -0
- package/src/cli/workspace-cli.test.ts +30 -0
- package/src/commands/ai-prompt.ts +21 -0
- package/src/commands/analyze-deps.ts +119 -0
- package/src/commands/archive.ts +114 -0
- package/src/commands/branch/create.ts +94 -0
- package/src/commands/branch/index.ts +74 -0
- package/src/commands/branch/merge.ts +61 -0
- package/src/commands/branch/rebase.ts +54 -0
- package/src/commands/branch/switch.ts +150 -0
- package/src/commands/cache/index.ts +51 -0
- package/src/commands/cache/manager.ts +168 -0
- package/src/commands/cleanup.ts +132 -0
- package/src/commands/configure-claude.ts +55 -0
- package/src/commands/configure.ts +204 -0
- package/src/commands/create.ts +196 -0
- package/src/commands/dashboard/hook-handler.ts +138 -0
- package/src/commands/dashboard/index.ts +248 -0
- package/src/commands/dashboard/launcher.ts +42 -0
- package/src/commands/dashboard/session-picker.ts +73 -0
- package/src/commands/dashboard/sidebar.js +39 -0
- package/src/commands/dashboard/workspace-picker.ts +62 -0
- package/src/commands/delete.test.ts +250 -0
- package/src/commands/delete.ts +171 -0
- package/src/commands/deprecated-aliases.ts +55 -0
- package/src/commands/diff.ts +106 -0
- package/src/commands/doctor.ts +110 -0
- package/src/commands/generate-docs.ts +45 -0
- package/src/commands/ghq-status.ts +70 -0
- package/src/commands/go.ts +125 -0
- package/src/commands/history.ts +136 -0
- package/src/commands/list.test.ts +219 -0
- package/src/commands/list.ts +176 -0
- package/src/commands/mcp/index.ts +39 -0
- package/src/commands/migrate.ts +277 -0
- package/src/commands/remove-repo.ts +194 -0
- package/src/commands/report-bug.ts +88 -0
- package/src/commands/run.ts +125 -0
- package/src/commands/save-context.test.ts +113 -0
- package/src/commands/save-context.ts +141 -0
- package/src/commands/sessions.ts +99 -0
- package/src/commands/status.ts +111 -0
- package/src/commands/suite/create.ts +240 -0
- package/src/commands/suite/delete.ts +65 -0
- package/src/commands/suite/export.ts +97 -0
- package/src/commands/suite/import.ts +100 -0
- package/src/commands/suite/index.ts +66 -0
- package/src/commands/suite/list.ts +54 -0
- package/src/commands/suite/use.ts +232 -0
- package/src/commands/sync.ts +97 -0
- package/src/commands/update.ts +197 -0
- package/src/mcp/install.ts +529 -0
- package/src/mcp/server.ts +579 -0
- package/src/mcp/tools.test.ts +545 -0
- package/src/mcp/tools.ts +1019 -0
- package/src/mcp/update-workspace.test.ts +220 -0
- package/src/pi-extensions/permission-sync.ts.template +41 -0
- package/src/pi-extensions/permissions-gate.ts.template +69 -0
- package/src/pi-extensions/pr-fix-trigger.ts.template +124 -0
- package/src/pi-extensions/review-reminder.ts.template +58 -0
- package/src/pi-extensions/workspace-input-status.ts.template +919 -0
- package/src/program.ts +112 -0
- package/src/scripts/workspace-table.py +394 -0
- package/src/types/dashboard.ts +29 -0
- package/src/types/index.ts +109 -0
- package/src/types/ink.d.ts +171 -0
- package/src/utils/agent-config.test.ts +303 -0
- package/src/utils/agent-config.ts +264 -0
- package/src/utils/agent-state.test.ts +166 -0
- package/src/utils/agent-state.ts +135 -0
- package/src/utils/banner.ts +24 -0
- package/src/utils/branch-operations.ts +237 -0
- package/src/utils/bug-report.test.ts +174 -0
- package/src/utils/bug-report.ts +355 -0
- package/src/utils/cache.ts +150 -0
- package/src/utils/claude-integration.test.ts +381 -0
- package/src/utils/claude-integration.ts +649 -0
- package/src/utils/claude-sessions.test.ts +262 -0
- package/src/utils/claude-sessions.ts +251 -0
- package/src/utils/cleanup-operations.ts +94 -0
- package/src/utils/colors.ts +25 -0
- package/src/utils/command-helpers.ts +46 -0
- package/src/utils/config.test.ts +203 -0
- package/src/utils/config.ts +133 -0
- package/src/utils/context-file.ts +36 -0
- package/src/utils/dashboard-hooks.test.ts +168 -0
- package/src/utils/dashboard-hooks.ts +163 -0
- package/src/utils/dependency-analyzer.ts +243 -0
- package/src/utils/diff-operations.ts +59 -0
- package/src/utils/doc-generator.ts +187 -0
- package/src/utils/ghq-integration.test.ts +195 -0
- package/src/utils/ghq-integration.ts +317 -0
- package/src/utils/git-operations.test.ts +265 -0
- package/src/utils/git-operations.ts +233 -0
- package/src/utils/git-status.ts +171 -0
- package/src/utils/github.ts +74 -0
- package/src/utils/health-checks.ts +359 -0
- package/src/utils/history.ts +140 -0
- package/src/utils/hooks.ts +92 -0
- package/src/utils/logger.ts +37 -0
- package/src/utils/permission-sync.test.ts +593 -0
- package/src/utils/permission-sync.ts +720 -0
- package/src/utils/pi-extensions.test.ts +182 -0
- package/src/utils/pi-extensions.ts +226 -0
- package/src/utils/progress.ts +73 -0
- package/src/utils/prompts.test.ts +169 -0
- package/src/utils/prompts.ts +296 -0
- package/src/utils/repo-resolver.test.ts +313 -0
- package/src/utils/repo-resolver.ts +294 -0
- package/src/utils/retry.test.ts +124 -0
- package/src/utils/retry.ts +85 -0
- package/src/utils/run-operations.ts +38 -0
- package/src/utils/suite.test.ts +256 -0
- package/src/utils/suite.ts +144 -0
- package/src/utils/sync-operations.ts +87 -0
- package/src/utils/tmux-dashboard.ts +193 -0
- package/src/utils/validation.test.ts +139 -0
- package/src/utils/validation.ts +128 -0
- package/src/utils/version-check.test.ts +108 -0
- package/src/utils/version-check.ts +96 -0
- package/src/utils/workspace-meta.ts +173 -0
- package/sync-permissions.sh +137 -0
- package/tsconfig.json +20 -0
- package/uninstall.sh +162 -0
|
@@ -0,0 +1,291 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { spawnSync } from 'child_process';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { readAllAgentStates } from '../../utils/agent-state';
|
|
5
|
+
import { AgentState, DASHBOARD_DEFAULTS } from '../../types/dashboard';
|
|
6
|
+
import { InkComponents, Key } from './types';
|
|
7
|
+
import { AgentList } from './AgentList';
|
|
8
|
+
import { HelpFooter } from './HelpFooter';
|
|
9
|
+
import {
|
|
10
|
+
getAgentPanes,
|
|
11
|
+
getHiddenPanes,
|
|
12
|
+
findPaneForAgent,
|
|
13
|
+
sortAgentsByPaneOrder,
|
|
14
|
+
sortAgentsByPidOrder,
|
|
15
|
+
resetLayout,
|
|
16
|
+
restoreHiddenPanes,
|
|
17
|
+
zoomPane,
|
|
18
|
+
} from '../../utils/tmux-dashboard';
|
|
19
|
+
|
|
20
|
+
const { sessionName, pollIntervalMs } = DASHBOARD_DEFAULTS;
|
|
21
|
+
|
|
22
|
+
export interface DashboardSidebarProps extends InkComponents {
|
|
23
|
+
useInput: (handler: (input: string, key: Key) => void) => void;
|
|
24
|
+
useApp: () => { exit: (error?: Error) => void };
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const DashboardSidebar: React.FC<DashboardSidebarProps> = ({ Box, Text, useInput, useApp }) => {
|
|
28
|
+
const [agents, setAgents] = React.useState<AgentState[]>([]);
|
|
29
|
+
const [selectedIndex, setSelectedIndex] = React.useState(0);
|
|
30
|
+
const [isZoomed, setIsZoomed] = React.useState(false);
|
|
31
|
+
const { exit } = useApp();
|
|
32
|
+
|
|
33
|
+
// Refs for always-current values inside the useInput closure
|
|
34
|
+
const agentsRef = React.useRef<AgentState[]>([]);
|
|
35
|
+
const selectedIndexRef = React.useRef(0);
|
|
36
|
+
const isZoomedRef = React.useRef(false);
|
|
37
|
+
// Store original pane PID order before zoom so we can restore it
|
|
38
|
+
const originalPaneOrderRef = React.useRef<number[]>([]);
|
|
39
|
+
|
|
40
|
+
// Poll agent states every 2s
|
|
41
|
+
React.useEffect(() => {
|
|
42
|
+
function refresh() {
|
|
43
|
+
const visiblePanes = getAgentPanes();
|
|
44
|
+
const hiddenPanes = getHiddenPanes();
|
|
45
|
+
const allPanes = [...visiblePanes, ...hiddenPanes];
|
|
46
|
+
const panePids = new Set(allPanes.map(p => p.pid));
|
|
47
|
+
|
|
48
|
+
const allStates = readAllAgentStates().filter(s => {
|
|
49
|
+
if (s.status === 'stopped') return false;
|
|
50
|
+
return panePids.has(s.pid);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
// When zoomed, sort by the original pane order so the sidebar
|
|
54
|
+
// display stays stable (zoomed pane doesn't jump to the top)
|
|
55
|
+
const originalOrder = originalPaneOrderRef.current;
|
|
56
|
+
let sorted: AgentState[];
|
|
57
|
+
if (originalOrder.length > 0) {
|
|
58
|
+
sorted = sortAgentsByPidOrder(allStates, originalOrder);
|
|
59
|
+
} else {
|
|
60
|
+
sorted = sortAgentsByPaneOrder(allStates, allPanes);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
agentsRef.current = sorted;
|
|
64
|
+
setAgents(sorted);
|
|
65
|
+
}
|
|
66
|
+
refresh();
|
|
67
|
+
const timer = setInterval(refresh, pollIntervalMs);
|
|
68
|
+
return () => clearInterval(timer);
|
|
69
|
+
}, []);
|
|
70
|
+
|
|
71
|
+
// Keep selectedIndex ref in sync
|
|
72
|
+
React.useEffect(() => {
|
|
73
|
+
selectedIndexRef.current = selectedIndex;
|
|
74
|
+
}, [selectedIndex]);
|
|
75
|
+
|
|
76
|
+
// Keep isZoomed ref in sync
|
|
77
|
+
React.useEffect(() => {
|
|
78
|
+
isZoomedRef.current = isZoomed;
|
|
79
|
+
}, [isZoomed]);
|
|
80
|
+
|
|
81
|
+
// Clamp selection — handles both too-high and negative values
|
|
82
|
+
const clampedIndex = Math.min(Math.max(selectedIndex, 0), Math.max(0, agents.length - 1));
|
|
83
|
+
if (clampedIndex !== selectedIndex) {
|
|
84
|
+
setSelectedIndex(clampedIndex);
|
|
85
|
+
selectedIndexRef.current = clampedIndex;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Apply zoom to a specific agent index.
|
|
90
|
+
*/
|
|
91
|
+
function applyZoom(agentIndex: number): void {
|
|
92
|
+
const currentAgents = agentsRef.current;
|
|
93
|
+
if (agentIndex >= currentAgents.length) return;
|
|
94
|
+
|
|
95
|
+
if (isZoomedRef.current) {
|
|
96
|
+
// Already zoomed — restore panes in original order, then re-zoom
|
|
97
|
+
restoreHiddenPanes(originalPaneOrderRef.current);
|
|
98
|
+
} else {
|
|
99
|
+
// First zoom — capture the current pane order before hiding anything
|
|
100
|
+
const panes = getAgentPanes();
|
|
101
|
+
originalPaneOrderRef.current = panes.map(p => p.pid);
|
|
102
|
+
restoreHiddenPanes(); // no-op if nothing hidden
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
spawnSync('tmux', ['select-layout', '-t', sessionName, 'main-vertical'], { stdio: 'pipe' });
|
|
106
|
+
spawnSync('tmux', ['resize-pane', '-t', `${sessionName}:0.0`, '-x', '32'], { stdio: 'pipe' });
|
|
107
|
+
|
|
108
|
+
const panes = getAgentPanes();
|
|
109
|
+
const pane = findPaneForAgent(currentAgents[agentIndex], panes);
|
|
110
|
+
if (pane) {
|
|
111
|
+
zoomPane(pane);
|
|
112
|
+
setIsZoomed(true);
|
|
113
|
+
isZoomedRef.current = true;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Unzoom and reset layout, restoring original pane order.
|
|
119
|
+
*/
|
|
120
|
+
function unzoom(): void {
|
|
121
|
+
restoreHiddenPanes(originalPaneOrderRef.current);
|
|
122
|
+
originalPaneOrderRef.current = [];
|
|
123
|
+
spawnSync('tmux', ['select-layout', '-t', sessionName, 'main-vertical'], { stdio: 'pipe' });
|
|
124
|
+
spawnSync('tmux', ['resize-pane', '-t', `${sessionName}:0.0`, '-x', '32'], { stdio: 'pipe' });
|
|
125
|
+
spawnSync('tmux', ['select-pane', '-t', `${sessionName}:0.0`], { stdio: 'pipe' });
|
|
126
|
+
setIsZoomed(false);
|
|
127
|
+
isZoomedRef.current = false;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Keyboard handling — reads from refs for always-current data
|
|
131
|
+
useInput((input: string, key: Key) => {
|
|
132
|
+
const currentAgents = agentsRef.current;
|
|
133
|
+
const currentIdx = selectedIndexRef.current;
|
|
134
|
+
const currentZoomed = isZoomedRef.current;
|
|
135
|
+
const clampedIdx = Math.min(Math.max(currentIdx, 0), Math.max(0, currentAgents.length - 1));
|
|
136
|
+
|
|
137
|
+
// q — detach (session stays running in background, agents keep working)
|
|
138
|
+
if (input === 'q') {
|
|
139
|
+
restoreHiddenPanes();
|
|
140
|
+
spawnSync('tmux', ['detach-client', '-s', sessionName], { stdio: 'pipe' });
|
|
141
|
+
// Do NOT call exit() — the sidebar process must keep running so
|
|
142
|
+
// the session is alive when the user re-attaches with 'w dash'
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Q (shift+q) — kill session and all agents
|
|
147
|
+
if (input === 'Q') {
|
|
148
|
+
restoreHiddenPanes();
|
|
149
|
+
spawnSync('tmux', ['kill-session', '-t', sessionName], { stdio: 'pipe' });
|
|
150
|
+
exit();
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Navigation
|
|
155
|
+
if (key.upArrow || input === 'K') {
|
|
156
|
+
const next = Math.max(0, clampedIdx - 1);
|
|
157
|
+
setSelectedIndex(next);
|
|
158
|
+
selectedIndexRef.current = next;
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
if (key.downArrow || input === 'J') {
|
|
162
|
+
const next = Math.max(0, Math.min(currentAgents.length - 1, clampedIdx + 1));
|
|
163
|
+
setSelectedIndex(next);
|
|
164
|
+
selectedIndexRef.current = next;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Number keys 1-9
|
|
169
|
+
if (input >= '1' && input <= '9') {
|
|
170
|
+
const idx = parseInt(input, 10) - 1;
|
|
171
|
+
if (idx < currentAgents.length) {
|
|
172
|
+
setSelectedIndex(idx);
|
|
173
|
+
selectedIndexRef.current = idx;
|
|
174
|
+
}
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Focus — switch to agent pane
|
|
179
|
+
if (input === 'f' || key.return) {
|
|
180
|
+
if (clampedIdx < currentAgents.length) {
|
|
181
|
+
if (currentZoomed) {
|
|
182
|
+
applyZoom(clampedIdx);
|
|
183
|
+
const panes = getAgentPanes();
|
|
184
|
+
const pane = findPaneForAgent(currentAgents[clampedIdx], panes);
|
|
185
|
+
if (pane) {
|
|
186
|
+
spawnSync('tmux', ['select-pane', '-t', `${sessionName}:0.${pane.index}`], { stdio: 'pipe' });
|
|
187
|
+
}
|
|
188
|
+
} else {
|
|
189
|
+
const panes = getAgentPanes();
|
|
190
|
+
const pane = findPaneForAgent(currentAgents[clampedIdx], panes);
|
|
191
|
+
if (pane) {
|
|
192
|
+
spawnSync('tmux', ['select-pane', '-t', `${sessionName}:0.${pane.index}`], { stdio: 'pipe' });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Zoom toggle
|
|
200
|
+
if (input === 'z') {
|
|
201
|
+
if (currentZoomed) {
|
|
202
|
+
unzoom();
|
|
203
|
+
isZoomedRef.current = false;
|
|
204
|
+
} else {
|
|
205
|
+
applyZoom(clampedIdx);
|
|
206
|
+
}
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Reset layout
|
|
211
|
+
if (input === 'r') {
|
|
212
|
+
unzoom();
|
|
213
|
+
isZoomedRef.current = false;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// New agent
|
|
218
|
+
if (input === 'n') {
|
|
219
|
+
if (currentZoomed) {
|
|
220
|
+
unzoom();
|
|
221
|
+
isZoomedRef.current = false;
|
|
222
|
+
}
|
|
223
|
+
const pickerScript = path.join(__dirname, '..', '..', 'commands', 'dashboard', 'workspace-picker.js');
|
|
224
|
+
const popupResult = spawnSync('tmux', [
|
|
225
|
+
'display-popup', '-E', '-w', '60%', '-h', '60%',
|
|
226
|
+
'-t', sessionName,
|
|
227
|
+
`node "${pickerScript}"`,
|
|
228
|
+
], { stdio: 'pipe' });
|
|
229
|
+
|
|
230
|
+
if (popupResult.status !== 0) {
|
|
231
|
+
spawnSync('tmux', ['split-window', '-t', sessionName, '-v', `node "${pickerScript}"`], { stdio: 'pipe' });
|
|
232
|
+
}
|
|
233
|
+
setTimeout(() => resetLayout(), 500);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Resume session
|
|
238
|
+
if (input === 's') {
|
|
239
|
+
if (currentZoomed) {
|
|
240
|
+
unzoom();
|
|
241
|
+
isZoomedRef.current = false;
|
|
242
|
+
}
|
|
243
|
+
const sessionPickerScript = path.join(__dirname, '..', '..', 'commands', 'dashboard', 'session-picker.js');
|
|
244
|
+
const popupResult = spawnSync('tmux', [
|
|
245
|
+
'display-popup', '-E', '-w', '60%', '-h', '60%',
|
|
246
|
+
'-t', sessionName,
|
|
247
|
+
`node "${sessionPickerScript}"`,
|
|
248
|
+
], { stdio: 'pipe' });
|
|
249
|
+
|
|
250
|
+
if (popupResult.status !== 0) {
|
|
251
|
+
spawnSync('tmux', ['split-window', '-t', sessionName, '-v', `node "${sessionPickerScript}"`], { stdio: 'pipe' });
|
|
252
|
+
}
|
|
253
|
+
setTimeout(() => resetLayout(), 500);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Kill / close session
|
|
258
|
+
if (input === 'x') {
|
|
259
|
+
if (clampedIdx < currentAgents.length) {
|
|
260
|
+
if (currentZoomed) {
|
|
261
|
+
unzoom();
|
|
262
|
+
isZoomedRef.current = false;
|
|
263
|
+
}
|
|
264
|
+
const panes = getAgentPanes();
|
|
265
|
+
const pane = findPaneForAgent(currentAgents[clampedIdx], panes);
|
|
266
|
+
if (pane) {
|
|
267
|
+
spawnSync('tmux', ['kill-pane', '-t', `${sessionName}:0.${pane.index}`], { stdio: 'pipe' });
|
|
268
|
+
setTimeout(() => resetLayout(), 100);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
return (
|
|
276
|
+
<Box flexDirection="column">
|
|
277
|
+
<Box marginBottom={1}>
|
|
278
|
+
<Text bold color="cyan">{'─ Agent Dashboard ─'}</Text>
|
|
279
|
+
{isZoomed && <Text color="yellow"> [ZOOM]</Text>}
|
|
280
|
+
</Box>
|
|
281
|
+
<AgentList
|
|
282
|
+
agents={agents}
|
|
283
|
+
selectedIndex={clampedIndex}
|
|
284
|
+
onSelect={setSelectedIndex}
|
|
285
|
+
Box={Box}
|
|
286
|
+
Text={Text}
|
|
287
|
+
/>
|
|
288
|
+
<HelpFooter isZoomed={isZoomed} Box={Box} Text={Text} />
|
|
289
|
+
</Box>
|
|
290
|
+
);
|
|
291
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { InkComponents } from './types';
|
|
3
|
+
|
|
4
|
+
interface HelpFooterProps extends InkComponents {
|
|
5
|
+
isZoomed: boolean;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const HelpFooter: React.FC<HelpFooterProps> = ({ isZoomed, Box, Text }) => {
|
|
9
|
+
return (
|
|
10
|
+
<Box flexDirection="column" marginTop={1}>
|
|
11
|
+
<Text dimColor>───────────────────</Text>
|
|
12
|
+
<Text><Text color="yellow">↑↓</Text> select <Text color="yellow">f</Text>/⏎ focus</Text>
|
|
13
|
+
<Text><Text color="yellow">z</Text> {isZoomed ? 'unzoom' : 'zoom'} <Text color="yellow">x</Text> kill</Text>
|
|
14
|
+
<Text><Text color="yellow">n</Text> new <Text color="yellow">q</Text> detach</Text>
|
|
15
|
+
<Text><Text color="yellow">s</Text> resume <Text color="yellow">Q</Text> quit all</Text>
|
|
16
|
+
<Text><Text color="yellow">r</Text> reset layout</Text>
|
|
17
|
+
<Text dimColor>───────────────────</Text>
|
|
18
|
+
<Text dimColor>From agent pane:</Text>
|
|
19
|
+
<Text> <Text color="yellow">prefix+M</Text> sidebar/reset</Text>
|
|
20
|
+
</Box>
|
|
21
|
+
);
|
|
22
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { AgentStatus, InkComponents } from './types';
|
|
3
|
+
|
|
4
|
+
interface StatusBadgeProps extends InkComponents {
|
|
5
|
+
status: AgentStatus;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const STATUS_CONFIG: Record<AgentStatus, { icon: string; color: string; label: string }> = {
|
|
9
|
+
working: { icon: '●', color: 'green', label: 'working' },
|
|
10
|
+
waiting: { icon: '●', color: 'yellow', label: 'approve' },
|
|
11
|
+
idle: { icon: '◌', color: 'gray', label: 'idle' },
|
|
12
|
+
stopped: { icon: '✕', color: 'red', label: 'stopped' },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export const StatusBadge: React.FC<StatusBadgeProps> = ({ status, Box, Text }) => {
|
|
16
|
+
const config = STATUS_CONFIG[status] || STATUS_CONFIG.idle;
|
|
17
|
+
return (
|
|
18
|
+
<Box gap={1}>
|
|
19
|
+
<Text color={config.color}>{config.icon}</Text>
|
|
20
|
+
<Text color={config.color}>{config.label}</Text>
|
|
21
|
+
</Box>
|
|
22
|
+
);
|
|
23
|
+
};
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for ink dashboard components.
|
|
3
|
+
* ink components are passed as props to avoid direct ESM imports
|
|
4
|
+
* (ink v5 is ESM-only; the project uses CJS).
|
|
5
|
+
*/
|
|
6
|
+
import { FC } from 'react';
|
|
7
|
+
import { BoxProps, TextProps, Key } from 'ink';
|
|
8
|
+
import { AgentState, AgentStatus } from '../../types/dashboard';
|
|
9
|
+
|
|
10
|
+
/** ink components passed through from the dynamic import bridge */
|
|
11
|
+
export interface InkComponents {
|
|
12
|
+
Box: FC<BoxProps>;
|
|
13
|
+
Text: FC<TextProps>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export type { AgentState, AgentStatus, Key };
|
package/src/cli/tui.tsx
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// TUI implementation - simplified version without ink for now
|
|
3
|
+
// Full ink-based implementation can be added after dependencies are installed
|
|
4
|
+
|
|
5
|
+
import { listWorkspaces } from '../utils/workspace-meta';
|
|
6
|
+
import { logError, logInfo } from '../utils/logger';
|
|
7
|
+
import { colorize } from '../utils/colors';
|
|
8
|
+
|
|
9
|
+
export const main = async () => {
|
|
10
|
+
try {
|
|
11
|
+
logInfo('Interactive TUI mode');
|
|
12
|
+
console.log(colorize('\n╔══════════════════════════════════════════╗', 'cyan'));
|
|
13
|
+
console.log(colorize('║ Workspace Manager - Interactive UI ║', 'cyan'));
|
|
14
|
+
console.log(colorize('╚══════════════════════════════════════════╝\n', 'cyan'));
|
|
15
|
+
|
|
16
|
+
const workspaces = await listWorkspaces();
|
|
17
|
+
|
|
18
|
+
if (workspaces.length === 0) {
|
|
19
|
+
logError('No workspaces found');
|
|
20
|
+
console.log(colorize('Create a workspace with: workspace create', 'gray'));
|
|
21
|
+
process.exit(1);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log(colorize('Available Workspaces:', 'bright'));
|
|
25
|
+
for (let i = 0; i < workspaces.length; i++) {
|
|
26
|
+
const ws = workspaces[i];
|
|
27
|
+
const repoCount = ws.metadata?.repositories.length || 0;
|
|
28
|
+
console.log(` ${i + 1}. ${colorize(ws.name, 'cyan')} (${repoCount} repos)`);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log('\n' + colorize('Commands:', 'bright'));
|
|
32
|
+
console.log(' workspace status <name> - Check git status');
|
|
33
|
+
console.log(' workspace doctor <name> - Run health checks');
|
|
34
|
+
console.log(' workspace sync <name> - Pull latest changes');
|
|
35
|
+
console.log(' wgo <name> - Navigate to workspace');
|
|
36
|
+
console.log('');
|
|
37
|
+
|
|
38
|
+
logInfo('Full interactive TUI with ink will be available after running: npm install');
|
|
39
|
+
} catch (error) {
|
|
40
|
+
logError('TUI failed');
|
|
41
|
+
if (error instanceof Error) {
|
|
42
|
+
logError(error.message);
|
|
43
|
+
}
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
// Auto-execute only when run directly (not when imported by Commander)
|
|
49
|
+
if (require.main === module) {
|
|
50
|
+
main();
|
|
51
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { execSync } from 'child_process';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
|
|
5
|
+
const BIN = path.join(__dirname, '..', '..', 'bin', 'workspace.js');
|
|
6
|
+
|
|
7
|
+
describe('w CLI entry point', () => {
|
|
8
|
+
it('--version prints version number', () => {
|
|
9
|
+
const output = execSync(`node ${BIN} --version 2>/dev/null`).toString().trim();
|
|
10
|
+
expect(output).toMatch(/^\d+\.\d+\.\d+$/);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('-V prints version number', () => {
|
|
14
|
+
const output = execSync(`node ${BIN} -V 2>/dev/null`).toString().trim();
|
|
15
|
+
expect(output).toMatch(/^\d+\.\d+\.\d+$/);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it('--version and -V print the same version', () => {
|
|
19
|
+
const v1 = execSync(`node ${BIN} --version 2>/dev/null`).toString().trim();
|
|
20
|
+
const v2 = execSync(`node ${BIN} -V 2>/dev/null`).toString().trim();
|
|
21
|
+
expect(v1).toBe(v2);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('help banner includes version', () => {
|
|
25
|
+
const output = execSync(`node ${BIN} --help 2>/dev/null`).toString();
|
|
26
|
+
expect(output).toContain('multi-repo workspaces');
|
|
27
|
+
// Version should appear in format "vX.Y.Z"
|
|
28
|
+
expect(output).toMatch(/v\d+\.\d+\.\d+/);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { logError } from '../utils/logger';
|
|
2
|
+
import { colorize } from '../utils/colors';
|
|
3
|
+
import { run } from '../cli/ai-prompt';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Handle the `nemus -- <prompt>` command.
|
|
7
|
+
* This is called directly from bin/workspace.js before Commander parses,
|
|
8
|
+
* because Commander treats `--` as end-of-options.
|
|
9
|
+
*/
|
|
10
|
+
export async function handleAiPrompt(prompt: string): Promise<void> {
|
|
11
|
+
if (!prompt || !prompt.trim()) {
|
|
12
|
+
logError('No prompt provided');
|
|
13
|
+
console.log(`\n Usage: ${colorize('nemus -- <prompt>', 'green')}`);
|
|
14
|
+
console.log(` Example: ${colorize('nemus -- create a workspace for the payments team', 'gray')}\n`);
|
|
15
|
+
process.exit(1);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const exitCode = await run(prompt.trim());
|
|
20
|
+
process.exit(exitCode);
|
|
21
|
+
}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { WORKSPACES_DIR } from '../utils/config';
|
|
4
|
+
import { loadMetadata, saveMetadata } from '../utils/workspace-meta';
|
|
5
|
+
import {
|
|
6
|
+
analyzeDependencies,
|
|
7
|
+
detectCircularDependencies,
|
|
8
|
+
generateMermaidDiagram,
|
|
9
|
+
updateWorkspaceMetadata,
|
|
10
|
+
} from '../utils/dependency-analyzer';
|
|
11
|
+
import { logError, logInfo, logStep, logSuccess } from '../utils/logger';
|
|
12
|
+
import { colorize } from '../utils/colors';
|
|
13
|
+
import inquirer from 'inquirer';
|
|
14
|
+
import { resolveWorkspace } from '../utils/command-helpers';
|
|
15
|
+
|
|
16
|
+
const displayDependencyAnalysis = (analyses: Map<string, any>) => {
|
|
17
|
+
console.log('\n' + colorize('Dependency Analysis', 'bright'));
|
|
18
|
+
console.log(colorize('═'.repeat(100), 'gray'));
|
|
19
|
+
|
|
20
|
+
for (const [repoName, analysis] of analyses) {
|
|
21
|
+
console.log(`\n${colorize(repoName, 'cyan')}`);
|
|
22
|
+
if (analysis.dependencies.length > 0) {
|
|
23
|
+
console.log(` ${colorize('Depends on:', 'gray')} ${analysis.dependencies.join(', ')}`);
|
|
24
|
+
}
|
|
25
|
+
if (analysis.dependents.length > 0) {
|
|
26
|
+
console.log(` ${colorize('Depended by:', 'gray')} ${analysis.dependents.join(', ')}`);
|
|
27
|
+
}
|
|
28
|
+
if (analysis.missingDependencies.length > 0) {
|
|
29
|
+
console.log(` ${colorize('⚠ Missing deps:', 'yellow')} ${analysis.missingDependencies.join(', ')}`);
|
|
30
|
+
}
|
|
31
|
+
if (analysis.dependencies.length === 0 && analysis.dependents.length === 0 && analysis.missingDependencies.length === 0) {
|
|
32
|
+
console.log(` ${colorize('No dependencies detected', 'gray')}`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
console.log('\n' + colorize('═'.repeat(100), 'gray'));
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export function registerAnalyzeDepsCommand(parent: Command) {
|
|
40
|
+
parent
|
|
41
|
+
.command('analyze-deps')
|
|
42
|
+
.alias('ad')
|
|
43
|
+
.description('Analyze inter-repo dependencies')
|
|
44
|
+
.argument('[workspace]', 'Workspace name')
|
|
45
|
+
.action(async (workspace) => {
|
|
46
|
+
await handleAnalyzeDeps(workspace);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function handleAnalyzeDeps(workspaceArg?: string) {
|
|
51
|
+
try {
|
|
52
|
+
const selectedWorkspace = await resolveWorkspace(workspaceArg);
|
|
53
|
+
const workspacePath = path.join(WORKSPACES_DIR, selectedWorkspace);
|
|
54
|
+
const metadata = await loadMetadata(workspacePath);
|
|
55
|
+
|
|
56
|
+
if (!metadata) {
|
|
57
|
+
logError(`Workspace metadata not found for: ${selectedWorkspace}`);
|
|
58
|
+
process.exit(1);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
logStep(`Analyzing dependencies for workspace: ${colorize(selectedWorkspace, 'cyan')}`);
|
|
62
|
+
logInfo('Scanning package.json, Dockerfile, and docker-compose.yml files...');
|
|
63
|
+
|
|
64
|
+
const repoNames = metadata.repositories.map(r => r.name);
|
|
65
|
+
const analyses = await analyzeDependencies(workspacePath, repoNames);
|
|
66
|
+
|
|
67
|
+
displayDependencyAnalysis(analyses);
|
|
68
|
+
|
|
69
|
+
const cycles = detectCircularDependencies(analyses);
|
|
70
|
+
if (cycles.length > 0) {
|
|
71
|
+
console.log('\n' + colorize('⚠ Circular Dependencies Detected:', 'yellow'));
|
|
72
|
+
for (const cycle of cycles) {
|
|
73
|
+
console.log(` ${cycle.join(' → ')}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const diagram = generateMermaidDiagram(analyses);
|
|
78
|
+
console.log('\n' + colorize('Dependency Graph (Mermaid):', 'bright'));
|
|
79
|
+
console.log(diagram);
|
|
80
|
+
|
|
81
|
+
const missingDeps = new Set<string>();
|
|
82
|
+
for (const [, analysis] of analyses) {
|
|
83
|
+
for (const dep of analysis.missingDependencies) {
|
|
84
|
+
missingDeps.add(dep);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (missingDeps.size > 0) {
|
|
89
|
+
console.log('\n' + colorize('💡 Suggested Missing Repositories:', 'yellow'));
|
|
90
|
+
for (const dep of missingDeps) {
|
|
91
|
+
console.log(` • ${dep}`);
|
|
92
|
+
}
|
|
93
|
+
logInfo(`Consider adding these repositories with: workspace update ${selectedWorkspace}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if (process.stdout.isTTY) {
|
|
97
|
+
const { saveToMetadata } = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'confirm',
|
|
100
|
+
name: 'saveToMetadata',
|
|
101
|
+
message: 'Save dependency analysis to workspace metadata?',
|
|
102
|
+
default: true,
|
|
103
|
+
},
|
|
104
|
+
]);
|
|
105
|
+
|
|
106
|
+
if (saveToMetadata) {
|
|
107
|
+
const updatedMetadata = updateWorkspaceMetadata(metadata, analyses);
|
|
108
|
+
await saveMetadata(workspacePath, updatedMetadata);
|
|
109
|
+
logSuccess('Dependency analysis saved to metadata');
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
logError('Failed to analyze dependencies');
|
|
114
|
+
if (error instanceof Error) {
|
|
115
|
+
logError(error.message);
|
|
116
|
+
}
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
119
|
+
}
|